Add TabPresenter host component (#66)

* Init TabBar

* Update TabExample

* Add ref to TabBar

* Add TabBarControllerBox

* Remove Router.swift

* Merge master in tab-bar

* Add TabBarControllerDelegate subclass

* Add hook to TabBarDelegate

* Fix SwiftLint warnings

* Fix TabBarController

* Fix TabBarExample

* Comment out counter example

* Remove Roter Presenters

* Remove addChild from AppKit

* Rename TabController to TabPresenter

* Add function to delete tab in TabBarExample

* Add `parent` parameter to unmount

* Add ability to delete TabItem in TabBarExample

* Add badge, badgeColor, image, selectedImage to TabItem

* Fix unmount functions

* Remove force cast

* Comment out counter example

* Move repeated style to constant

* Rename TokamakTabPresenter to TokamakTabController

* Add TabContent component

* Fix MountedHostComponent mount

* Fix TabBarExample

* Fix TabBarExample

* Fix TabBarExample strings

* Fix TabBarExample

* Fix TabItem

* Fix TabBarExample variable name
This commit is contained in:
matvii 2019-03-25 18:55:31 +02:00 committed by GitHub
parent c815e87f0c
commit d26a41de38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 474 additions and 23 deletions

View File

@ -19,8 +19,10 @@
A62AC65F22244A98009B3B25 /* Snake.swift in Sources */ = {isa = PBXBuildFile; fileRef = A62AC65E22244A98009B3B25 /* Snake.swift */; };
A6654358222690DA00F01C04 /* Gamepad.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6654357222690DA00F01C04 /* Gamepad.swift */; };
A665435A22269F9F00F01C04 /* StartGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = A665435922269F9F00F01C04 /* StartGame.swift */; };
A675A5972239010D005173E5 /* TabExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = A675A5962239010D005173E5 /* TabExample.swift */; };
A67717202226DC7C0028A6F3 /* Gameboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A677171F2226DC7C0028A6F3 /* Gameboard.swift */; };
A67717222226E7FD0028A6F3 /* Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67717212226E7FD0028A6F3 /* Menu.swift */; };
A69E588322490836000874F8 /* TabContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69E588222490836000874F8 /* TabContent.swift */; };
A6D5AF87221B131400DBF186 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D5AF86221B131400DBF186 /* Image.swift */; };
A6D6538122312263007FA886 /* CollectionExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D6538022312263007FA886 /* CollectionExample.swift */; };
A6FEF7952227C1CC008BB292 /* Scroll.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6FEF7942227C1CC008BB292 /* Scroll.swift */; };
@ -68,8 +70,10 @@
A62AC65E22244A98009B3B25 /* Snake.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Snake.swift; sourceTree = "<group>"; };
A6654357222690DA00F01C04 /* Gamepad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Gamepad.swift; sourceTree = "<group>"; };
A665435922269F9F00F01C04 /* StartGame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartGame.swift; sourceTree = "<group>"; };
A675A5962239010D005173E5 /* TabExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabExample.swift; sourceTree = "<group>"; };
A677171F2226DC7C0028A6F3 /* Gameboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Gameboard.swift; sourceTree = "<group>"; };
A67717212226E7FD0028A6F3 /* Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Menu.swift; sourceTree = "<group>"; };
A69E588222490836000874F8 /* TabContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabContent.swift; sourceTree = "<group>"; };
A6D5AF86221B131400DBF186 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = "<group>"; };
A6D6538022312263007FA886 /* CollectionExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExample.swift; sourceTree = "<group>"; };
A6FEF7942227C1CC008BB292 /* Scroll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scroll.swift; sourceTree = "<group>"; };
@ -232,7 +236,9 @@
D1BB3D2F2223F6B400C30062 /* Animation.swift */,
A6FEF7942227C1CC008BB292 /* Scroll.swift */,
A6D6538022312263007FA886 /* CollectionExample.swift */,
A675A5962239010D005173E5 /* TabExample.swift */,
D1B9056F224258AC0077CD5E /* Throbber.swift */,
A69E588222490836000874F8 /* TabContent.swift */,
);
path = Components;
sourceTree = "<group>";
@ -405,10 +411,12 @@
D1F2C3272214407B008358DC /* TableModal.swift in Sources */,
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */,
A62AC6542223F5CD009B3B25 /* TextField.swift in Sources */,
A69E588322490836000874F8 /* TabContent.swift in Sources */,
D1F7185F2215A5D0004E5951 /* Constraints.swift in Sources */,
A62AC65922243DCB009B3B25 /* Cell.swift in Sources */,
D11DB6432219C03000013FC3 /* Timer.swift in Sources */,
A6FEF7952227C1CC008BB292 /* Scroll.swift in Sources */,
A675A5972239010D005173E5 /* TabExample.swift in Sources */,
A6D6538122312263007FA886 /* CollectionExample.swift in Sources */,
A62AC65622243CC3009B3B25 /* SnakeGame.swift in Sources */,
D1F7185322159E09004E5951 /* Controls.swift in Sources */,

View File

@ -0,0 +1,39 @@
//
// TabContent.swift
// TokamakDemo-iOS
//
// Created by Matvii Hodovaniuk on 3/25/19.
// Copyright © 2019 Tokamak contributors. Tokamak is available under the
// Apache 2.0 license. See the LICENSE file for more info.
//
import Tokamak
struct TabContent: LeafComponent {
struct Props: Equatable {
let name: String
let clickHandler: Handler<()>
}
static func render(props: Props, hooks: Hooks) -> AnyNode {
let stackViewStyle = StackView.Props(
Edges.equal(to: .safeArea),
alignment: .center,
axis: .vertical,
distribution: .fillEqually
)
return TabItem.node(
.init(title: props.name.capitalized),
StackView.node(
stackViewStyle,
[
Button.node(.init(
onPress: props.clickHandler,
text: "Remove \(props.name) tab"
)),
Label.node(.init(alignment: .center, text: props.name.capitalized)),
]
)
)
}
}

View File

@ -0,0 +1,58 @@
//
// TabExample.swift
// TokamakDemo-iOS
//
// Created by Matvii Hodovaniuk on 3/13/19.
// Copyright © 2019 Tokamak contributors. Tokamak is available under
// the Apache 2.0
// license. See the LICENSE file for more info.
//
import Tokamak
struct TabExample: LeafComponent {
typealias Props = Null
static func render(props: Props, hooks: Hooks) -> AnyNode {
let selectedIndex = hooks.state(0)
let tabsToDisplay = hooks.state([0, 1, 2])
let stackViewStyle = StackView.Props(
Edges.equal(to: .safeArea),
alignment: .center,
axis: .vertical,
distribution: .fillEqually
)
func removeTab(id: Int) -> [Int] {
if tabsToDisplay.value.count > 1 {
var oldList = tabsToDisplay.value
if let index = oldList.firstIndex(of: id) {
oldList.remove(at: index)
}
return oldList
} else {
return tabsToDisplay.value
}
}
let tabs = [
TabContent.node(.init(
name: "first",
clickHandler: Handler { tabsToDisplay.set(removeTab(id: 0)) }
)),
TabContent.node(.init(
name: "second",
clickHandler: Handler { tabsToDisplay.set(removeTab(id: 1)) }
)),
TabContent.node(.init(
name: "third",
clickHandler: Handler { tabsToDisplay.set(removeTab(id: 2)) }
)),
]
return TabPresenter.node(
.init(isAnimated: true, selectedIndex: selectedIndex),
Array(tabsToDisplay.value.map { tabs[$0] })
)
}
}

View File

@ -25,6 +25,7 @@ enum AppRoute: String, CaseIterable {
case snakeGame = "Snake Game"
case scrollView = "Scroll"
case collection = "Collection View"
case tab = "Tab Example"
case throbber
}
@ -77,6 +78,8 @@ struct Router: NavigationRouter {
result = ScrollViewExample.node()
case .collection:
result = CollectionExample.node()
case .tab:
result = TabExample.node()
case .throbber:
result = ThrobberExample.node()
}

View File

@ -0,0 +1,23 @@
//
// TabPresenter.swift
// Tokamak
//
// Created by Matvii Hodovaniuk on 3/13/19.
//
public struct TabPresenter: HostComponent {
public typealias Children = [AnyNode]
public struct Props: Equatable {
public let isAnimated: Bool
public let selectedIndex: State<Int>?
public init(
isAnimated: Bool = false,
selectedIndex: State<Int>? = nil
) {
self.isAnimated = isAnimated
self.selectedIndex = selectedIndex
}
}
}

View File

@ -5,10 +5,28 @@
// Created by Max Desiatov on 04/02/2019.
//
public struct TabItem: Equatable {
public let title: String?
public struct TabItem: HostComponent {
public struct Props: Equatable {
public let badgeColor: Color?
public let badgeValue: String?
public let image: Image?
public let selectedImage: Image?
public let title: String?
public init(title: String? = nil) {
self.title = title
public init(
badgeColor: Color? = nil,
badgeValue: String? = nil,
image: Image? = nil,
selectedImage: Image? = nil,
title: String? = nil
) {
self.badgeColor = badgeColor
self.badgeValue = badgeValue
self.image = image
self.selectedImage = selectedImage
self.title = title
}
}
public typealias Children = AnyNode
}

View File

@ -64,7 +64,11 @@ public final class MountedHostComponent<R: Renderer>: MountedComponent<R> {
override func unmount(with reconciler: StackReconciler<R>) {
guard let target = target else { return }
reconciler.renderer?.unmount(target: target, with: self) {
reconciler.renderer?.unmount(
target: target,
from: parentTarget,
with: self
) {
self.mountedChildren.forEach { $0.unmount(with: reconciler) }
}
}
@ -145,11 +149,19 @@ public final class MountedHostComponent<R: Renderer>: MountedComponent<R> {
case let node as AnyNode:
if let child = mountedChildren.first {
child.node = node
child.update(with: reconciler)
if child.node.type == node.type {
child.node = node
child.update(with: reconciler)
} else {
child.unmount(with: reconciler)
let child: MountedComponent<R> = node.makeMountedComponent(target)
child.mount(with: reconciler)
mountedChildren = [child]
}
} else {
let child: MountedComponent<R> = node.makeMountedComponent(target)
child.mount(with: reconciler)
mountedChildren = [child]
}
// child type that can't be rendered, but still makes sense as a child

View File

@ -65,11 +65,13 @@ public protocol Renderer: class {
/** Function called by a reconciler when an existing target instance should be
unmounted: removed from the parent and most likely destroyed.
- parameter target: Existing target instance to be unmounted.
- parameter parent: Parent of target to direct interaction with parent.
- parameter component: Type of the host component that renders to the
updated target.
*/
func unmount(
target: TargetType,
from parent: TargetType,
with component: MountedHost,
completion: @escaping () -> ()
)

View File

@ -97,6 +97,7 @@ final class AppKitRenderer: Renderer {
func unmount(
target: NSTarget,
from parent: NSTarget,
with component: AppKitRenderer.MountedHost,
completion: @escaping () -> ()
) {

View File

@ -44,6 +44,7 @@ public final class TestRenderer: Renderer {
public func unmount(
target: TestView,
from parent: TestView,
with component: TestRenderer.MountedHost,
completion: () -> ()
) {

View File

@ -0,0 +1,44 @@
////
//// TabBarControllBox.swift
//// TokamakUIKit
////
//// Created by Matvii Hodovaniuk on 3/15/19.
////
import Tokamak
import UIKit
private final class Delegate<T: UITabBarController>:
NSObject,
UITabBarControllerDelegate {
private let selectedIndex: State<Int>?
func tabBarController(
_ tabBarController: UITabBarController,
didSelect viewController: UIViewController
) {
selectedIndex?.set(
(tabBarController.viewControllers?.firstIndex(of: viewController)!)!
)
}
init(_ props: TabPresenter.Props) {
selectedIndex = props.selectedIndex
}
}
class TabBarControllerBox: ViewControllerBox<UITabBarController> {
// this delegate stays as a constant and doesn't create a reference cycle
// swiftlint:disable:next weak_delegate
private let delegate: Delegate<UITabBarController>
init(
_ node: AnyNode,
_ props: TabPresenter.Props,
_ viewController: TokamakTabController
) {
delegate = Delegate(props)
viewController.delegate = delegate
super.init(viewController, node)
}
}

View File

@ -23,4 +23,13 @@ class ViewBox<T: UIView>: ViewControllerBox<UIViewController> {
override var refTarget: Any {
return view
}
func addChild<T>(_ vc: ViewControllerBox<T>) {
vc.viewController.willMove(toParent: viewController)
// FIXME: replace with auto layout constraints
vc.viewController.view.frame = view.frame
view.addSubview(vc.viewController.view)
viewController.addChild(vc.viewController)
vc.viewController.didMove(toParent: viewController)
}
}

View File

@ -20,7 +20,11 @@ extension ModalPresenter: UIHostComponent {
// FIXME: update presentation-related props on the target here
}
static func unmount(target: UITarget, completion: @escaping () -> ()) {
static func unmount(
target: UITarget,
from parent: UITarget,
completion: @escaping () -> ()
) {
target.viewController.dismiss(animated: true) { completion() }
}
}

View File

@ -96,7 +96,11 @@ extension NavigationController: UIHostComponent {
static func update(target: UITarget, node: AnyNode) {}
static func unmount(target: UITarget, completion: () -> ()) {
static func unmount(
target: UITarget,
from parent: UITarget,
completion: () -> ()
) {
completion()
}
}

View File

@ -66,5 +66,9 @@ extension NavigationItem: UIHostComponent {
item.largeTitleDisplayMode = .init(mode: props.titleMode)
}
static func unmount(target: UITarget, completion: () -> ()) { completion() }
static func unmount(
target: UITarget,
from parent: UITarget,
completion: () -> ()
) { completion() }
}

View File

@ -64,4 +64,12 @@ extension ScrollView: UIViewComponent {
view.showsHorizontalScrollIndicator = props.showsHorizontalScrollIndicator
view.zoomScale = CGFloat(props.zoomScale)
}
static func unmount(
target: UITarget,
from parent: UITarget,
completion: @escaping () -> ()
) {
completion()
}
}

View File

@ -0,0 +1,93 @@
//
// TabItem.swift
// TokamakUIKit
//
// Created by Matvii Hodovaniuk on 3/14/19.
//
import Tokamak
import UIKit
extension TabItem: UIHostComponent {
static func mountTarget(to parent: UITarget,
component: UIKitRenderer.MountedHost,
_: UIKitRenderer) -> UITarget? {
guard
let parent = parent as? ViewControllerBox<UITabBarController>
else {
parentAssertionFailure()
return nil
}
guard
let parentProps = parent.node.props.value as? TabPresenter.Props
else {
propsAssertionFailure()
return nil
}
let viewController = UIViewController()
let result = ViewControllerBox(viewController, component.node)
if var viewControllers = parent.containerViewController.viewControllers {
viewControllers.append(viewController)
parent.containerViewController.setViewControllers(
viewControllers,
animated: parentProps.isAnimated
)
} else {
parent.containerViewController.setViewControllers(
[viewController],
animated: parentProps.isAnimated
)
}
return result
}
static func update(target: UITarget, node: AnyNode) {
guard let target = target as? ViewControllerBox<UIViewController> else {
targetAssertionFailure()
return
}
guard let props = node.props.value as? TabItem.Props else {
propsAssertionFailure()
return
}
guard let item = target.viewController.tabBarItem else {
return
}
item.badgeColor = props.badgeColor.flatMap { UIColor($0) }
item.badgeValue = props.badgeValue
item.image = props.image.flatMap { UIImage.from(image: $0) }
item.selectedImage = props.selectedImage.flatMap { UIImage.from(image: $0) }
item.title = props.title
}
static func unmount(
target: UITarget,
from parent: UITarget,
completion: @escaping () -> ()
) {
guard let tabBarController = parent.viewController as? TokamakTabController else {
parentAssertionFailure()
return
}
if let indexToRemove = tabBarController.viewControllers?
.firstIndex(of: target.viewController) {
if indexToRemove < (tabBarController.viewControllers?.count)! {
var viewControllers = tabBarController.viewControllers
viewControllers?.remove(at: indexToRemove)
tabBarController.viewControllers = viewControllers
}
}
completion()
}
}

View File

@ -0,0 +1,74 @@
//
// TabPresenter.swift
// TokamakUIKit
//
// Created by Matvii Hodovaniuk on 3/13/19.
//
import Tokamak
import UIKit
final class TokamakTabController: UITabBarController {
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension TabPresenter: UIHostComponent, RefComponent {
public typealias RefTarget = UITabBarController
static func mountTarget(to parent: UITarget,
component: UIKitRenderer.MountedHost,
_: UIKitRenderer) -> UITarget? {
let props = component.node.props.value as? TabPresenter.Props
let result = TabBarControllerBox(
component.node,
props!,
TokamakTabController()
)
switch parent {
case let box as ViewControllerBox<UIViewController>
where parent.node.isSubtypeOf(ModalPresenter.self):
guard let props = parent.node.props.value as? ModalPresenter.Props else {
propsAssertionFailure()
return nil
}
// allow children nodes to be mounted first before presenting
DispatchQueue.main.async {
box.viewController.present(result.viewController,
animated: props.presentAnimated,
completion: nil)
}
case let box as ViewBox<TokamakView>:
box.addChild(result)
case let box as ViewBox<UIView>:
box.addChild(result)
default:
parentAssertionFailure()
}
if let selectedIndex = props?.selectedIndex {
let index = selectedIndex.value
DispatchQueue.main.async {
result.containerViewController.selectedIndex = index
}
}
return result
}
static func update(target: UITarget, node: AnyNode) {}
static func unmount(
target: UITarget,
from parent: UITarget,
completion: @escaping () -> ()
) {
completion()
}
}

View File

@ -23,7 +23,11 @@ protocol UIHostComponent: AnyHostComponent {
static func update(target: UITarget, node: AnyNode)
static func unmount(target: UITarget, completion: @escaping () -> ())
static func unmount(
target: UITarget,
from parent: UITarget,
completion: @escaping () -> ()
)
}
extension UIHostComponent {

View File

@ -94,7 +94,8 @@ extension UIViewComponent where Target == Target.DefaultValue,
let parentRequiresViewController = parent.node.isSubtypeOf(
ModalPresenter.self,
or: NavigationController.self
or: NavigationController.self,
or: TabPresenter.self
)
// UIViewController parent target can't present a bare `ViewBox` target,
@ -136,6 +137,29 @@ extension UIViewComponent where Target == Target.DefaultValue,
result.viewController,
animated: props.pushAnimated
)
case let box as ViewControllerBox<TokamakTabController>
where parent.node.isSubtypeOf(TabPresenter.self):
guard let props = parent.node.props.value
as? TabPresenter.Props else {
propsAssertionFailure()
return nil
}
if var viewControllers = box.containerViewController.viewControllers {
viewControllers.append(result.viewController)
box.containerViewController.setViewControllers(
viewControllers,
animated: props.isAnimated
)
} else {
box.containerViewController.setViewControllers(
[result.viewController],
animated: props.isAnimated
)
}
case let box as ViewControllerBox<UIViewController>
where parent.node.isSubtypeOf(TabItem.self):
box.viewController.view.addSubview(target)
case let box as ViewControllerBox<UIViewController>
where parent.node.isSubtypeOf(ModalPresenter.self):
guard
@ -178,7 +202,11 @@ extension UIViewComponent where Target == Target.DefaultValue,
update(view: target, props, children)
}
static func unmount(target: UITarget, completion: () -> ()) {
static func unmount(
target: UITarget,
from parent: UITarget,
completion: () -> ()
) {
switch target {
case let target as ViewBox<Target>:
target.view.removeFromSuperview()

View File

@ -14,6 +14,7 @@ let _modalPresenterWitnessTableHack: UIHostComponent.Type = ModalPresenter.self
let _stackControllerWitnessTableHack: UIHostComponent.Type =
NavigationController.self
let _navigationItemWitnessTableHack: UIHostComponent.Type = NavigationItem.self
let _tabItemWitnessTableHack: UIHostComponent.Type = TabItem.self
let _listViewWitnessTableHack: UIHostComponent.Type =
ListView<HackyProvider>.self
let _collectionViewWitnessTableHack: UIHostComponent.Type =
@ -95,6 +96,7 @@ final class UIKitRenderer: Renderer {
func unmount(
target: UITarget,
from parent: UITarget,
with component: UIKitRenderer.MountedHost,
completion: @escaping () -> ()
) {
@ -103,6 +105,10 @@ final class UIKitRenderer: Renderer {
return
}
rendererComponent.unmount(target: target, completion: completion)
rendererComponent.unmount(
target: target,
from: parent,
completion: completion
)
}
}

View File

@ -21,6 +21,10 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
A675A59922390218005173E5 /* TabPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A675A59822390218005173E5 /* TabPresenter.swift */; };
A675A59B223910E1005173E5 /* TabPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A675A59A223910E1005173E5 /* TabPresenter.swift */; };
A675A59D223AE721005173E5 /* TabItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A675A59C223AE721005173E5 /* TabItem.swift */; };
A675A59F223C2EDE005173E5 /* TabBarControllerBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = A675A59E223C2EDE005173E5 /* TabBarControllerBox.swift */; };
D15E04332235829000BB0571 /* Rectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D15E04322235829000BB0571 /* Rectangle.swift */; };
D15E0447223582FD00BB0571 /* FirstBaseline.swift in Sources */ = {isa = PBXBuildFile; fileRef = D15E0435223582EE00BB0571 /* FirstBaseline.swift */; };
D15E0448223582FD00BB0571 /* CenterY.swift in Sources */ = {isa = PBXBuildFile; fileRef = D15E0436223582EE00BB0571 /* CenterY.swift */; };
@ -245,6 +249,10 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
A675A59822390218005173E5 /* TabPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPresenter.swift; sourceTree = "<group>"; };
A675A59A223910E1005173E5 /* TabPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPresenter.swift; sourceTree = "<group>"; };
A675A59C223AE721005173E5 /* TabItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabItem.swift; sourceTree = "<group>"; };
A675A59E223C2EDE005173E5 /* TabBarControllerBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarControllerBox.swift; sourceTree = "<group>"; };
D15E042D223578BC00BB0571 /* TokamakUIKit.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = TokamakUIKit.podspec; sourceTree = "<group>"; };
D15E042E223578BC00BB0571 /* Tokamak.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Tokamak.podspec; sourceTree = "<group>"; };
D15E042F223578BC00BB0571 /* TokamakAppKit.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = TokamakAppKit.podspec; sourceTree = "<group>"; };
@ -418,7 +426,7 @@
"Tokamak::TokamakDemo::Product" /* TokamakDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TokamakDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; };
"Tokamak::TokamakTestRenderer::Product" /* TokamakTestRenderer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = TokamakTestRenderer.framework; sourceTree = BUILT_PRODUCTS_DIR; };
"Tokamak::TokamakTests::Product" /* TokamakTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = TokamakTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
"Tokamak::TokamakUIKit::Product" /* TokamakUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TokamakUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
"Tokamak::TokamakUIKit::Product" /* TokamakUIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = TokamakUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -567,6 +575,7 @@
OBJ_28 /* Switch.swift */,
OBJ_29 /* TextField.swift */,
OBJ_30 /* View.swift */,
A675A59822390218005173E5 /* TabPresenter.swift */,
D1B90571224259FA0077CD5E /* Throbber.swift */,
);
path = Host;
@ -846,6 +855,7 @@
OBJ_88 /* ValueControlBox.swift */,
OBJ_89 /* ViewBox.swift */,
OBJ_90 /* ViewControllerBox.swift */,
A675A59E223C2EDE005173E5 /* TabBarControllerBox.swift */,
);
path = Boxes;
sourceTree = "<group>";
@ -880,6 +890,8 @@
OBJ_107 /* Switch.swift */,
OBJ_108 /* TextField.swift */,
OBJ_109 /* View.swift */,
A675A59A223910E1005173E5 /* TabPresenter.swift */,
A675A59C223AE721005173E5 /* TabItem.swift */,
D1B9057322425CED0077CD5E /* Throbber.swift */,
);
path = Host;
@ -1080,6 +1092,7 @@
OBJ_230 /* Leading.swift in Sources */,
OBJ_231 /* Left.swift in Sources */,
OBJ_232 /* Right.swift in Sources */,
A675A59922390218005173E5 /* TabPresenter.swift in Sources */,
OBJ_233 /* SizeConstraint.swift in Sources */,
OBJ_234 /* Top.swift in Sources */,
OBJ_235 /* Trailing.swift in Sources */,
@ -1206,6 +1219,7 @@
OBJ_346 /* ListView.swift in Sources */,
OBJ_347 /* ModalPresenter.swift in Sources */,
OBJ_348 /* NavigationController.swift in Sources */,
A675A59B223910E1005173E5 /* TabPresenter.swift in Sources */,
OBJ_349 /* NavigationItem.swift in Sources */,
OBJ_350 /* ScrollView.swift in Sources */,
OBJ_351 /* SegmentedControl.swift in Sources */,
@ -1226,9 +1240,11 @@
OBJ_365 /* FirstBaseline.swift in Sources */,
OBJ_366 /* Height.swift in Sources */,
OBJ_367 /* LastBaseline.swift in Sources */,
A675A59F223C2EDE005173E5 /* TabBarControllerBox.swift in Sources */,
OBJ_368 /* Leading.swift in Sources */,
OBJ_369 /* Left.swift in Sources */,
OBJ_370 /* OwnConstraint.swift in Sources */,
A675A59D223AE721005173E5 /* TabItem.swift in Sources */,
OBJ_371 /* Right.swift in Sources */,
OBJ_372 /* Top.swift in Sources */,
OBJ_373 /* Trailing.swift in Sources */,
@ -1359,7 +1375,6 @@
"$(TOOLCHAIN_DIR)/usr/lib/swift/macosx",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
@ -1367,7 +1382,6 @@
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
SWIFT_VERSION = 4.2;
TARGET_NAME = TokamakAppKit;
@ -1389,7 +1403,6 @@
"$(TOOLCHAIN_DIR)/usr/lib/swift/macosx",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
@ -1397,7 +1410,6 @@
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
SWIFT_VERSION = 4.2;
TARGET_NAME = TokamakAppKit;
@ -1633,7 +1645,6 @@
"$(inherited)",
"$(TOOLCHAIN_DIR)/usr/lib/swift/macosx",
);
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
@ -1641,7 +1652,6 @@
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
SWIFT_VERSION = 4.2;
TARGET_NAME = TokamakUIKit;
@ -1662,7 +1672,6 @@
"$(inherited)",
"$(TOOLCHAIN_DIR)/usr/lib/swift/macosx",
);
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
@ -1670,7 +1679,6 @@
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
SWIFT_VERSION = 4.2;
TARGET_NAME = TokamakUIKit;