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:
parent
c815e87f0c
commit
d26a41de38
|
@ -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 */,
|
||||
|
|
|
@ -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)),
|
||||
]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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] })
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 () -> ()
|
||||
)
|
||||
|
|
|
@ -97,6 +97,7 @@ final class AppKitRenderer: Renderer {
|
|||
|
||||
func unmount(
|
||||
target: NSTarget,
|
||||
from parent: NSTarget,
|
||||
with component: AppKitRenderer.MountedHost,
|
||||
completion: @escaping () -> ()
|
||||
) {
|
||||
|
|
|
@ -44,6 +44,7 @@ public final class TestRenderer: Renderer {
|
|||
|
||||
public func unmount(
|
||||
target: TestView,
|
||||
from parent: TestView,
|
||||
with component: TestRenderer.MountedHost,
|
||||
completion: () -> ()
|
||||
) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue