Define and set RefTarget in TokamakUIKit (#54)
Also add `UIView.animate` animation effect to example code. Also removes redundant `Updatable` protocol. Resolves #49 * Define and set RefTarget in TokamakUIKit * Add animation example * Rename backgroundColor to currentColor * Add comments to Animation example
This commit is contained in:
parent
5fabe139dd
commit
923ffd02fd
|
@ -15,6 +15,7 @@
|
||||||
A6D5AF87221B131400DBF186 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D5AF86221B131400DBF186 /* Image.swift */; };
|
A6D5AF87221B131400DBF186 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D5AF86221B131400DBF186 /* Image.swift */; };
|
||||||
C449B806DFEE55B6CEE6478C /* libPods-TokamakDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B96B435A9D67621D318616E /* libPods-TokamakDemo.a */; };
|
C449B806DFEE55B6CEE6478C /* libPods-TokamakDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B96B435A9D67621D318616E /* libPods-TokamakDemo.a */; };
|
||||||
D11DB6432219C03000013FC3 /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D11DB6422219C03000013FC3 /* Timer.swift */; };
|
D11DB6432219C03000013FC3 /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D11DB6422219C03000013FC3 /* Timer.swift */; };
|
||||||
|
D1BB3D302223F6B400C30062 /* Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BB3D2F2223F6B400C30062 /* Animation.swift */; };
|
||||||
D1BFAF772215795900845EA0 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF762215795900845EA0 /* Router.swift */; };
|
D1BFAF772215795900845EA0 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF762215795900845EA0 /* Router.swift */; };
|
||||||
D1BFAF792215800A00845EA0 /* Counter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF782215800A00845EA0 /* Counter.swift */; };
|
D1BFAF792215800A00845EA0 /* Counter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF782215800A00845EA0 /* Counter.swift */; };
|
||||||
D1BFAF7B22158B4000845EA0 /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF7A22158B4000845EA0 /* List.swift */; };
|
D1BFAF7B22158B4000845EA0 /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF7A22158B4000845EA0 /* List.swift */; };
|
||||||
|
@ -43,6 +44,7 @@
|
||||||
A9EEF813955DAEEFE1D52ED4 /* Pods-TokamakDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TokamakDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TokamakDemo/Pods-TokamakDemo.debug.xcconfig"; sourceTree = "<group>"; };
|
A9EEF813955DAEEFE1D52ED4 /* Pods-TokamakDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TokamakDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TokamakDemo/Pods-TokamakDemo.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
C6DA99382B6892EAB361742F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
|
C6DA99382B6892EAB361742F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
|
||||||
D11DB6422219C03000013FC3 /* Timer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timer.swift; sourceTree = "<group>"; };
|
D11DB6422219C03000013FC3 /* Timer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timer.swift; sourceTree = "<group>"; };
|
||||||
|
D1BB3D2F2223F6B400C30062 /* Animation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Animation.swift; sourceTree = "<group>"; };
|
||||||
D1BFAF762215795900845EA0 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
|
D1BFAF762215795900845EA0 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
|
||||||
D1BFAF782215800A00845EA0 /* Counter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Counter.swift; sourceTree = "<group>"; };
|
D1BFAF782215800A00845EA0 /* Counter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Counter.swift; sourceTree = "<group>"; };
|
||||||
D1BFAF7A22158B4000845EA0 /* List.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = List.swift; sourceTree = "<group>"; };
|
D1BFAF7A22158B4000845EA0 /* List.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = List.swift; sourceTree = "<group>"; };
|
||||||
|
@ -162,6 +164,7 @@
|
||||||
D1F2C3262214407B008358DC /* TableModal.swift */,
|
D1F2C3262214407B008358DC /* TableModal.swift */,
|
||||||
D11DB6422219C03000013FC3 /* Timer.swift */,
|
D11DB6422219C03000013FC3 /* Timer.swift */,
|
||||||
A6D5AF86221B131400DBF186 /* Image.swift */,
|
A6D5AF86221B131400DBF186 /* Image.swift */,
|
||||||
|
D1BB3D2F2223F6B400C30062 /* Animation.swift */,
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -283,6 +286,7 @@
|
||||||
D1F7185D2215A4A1004E5951 /* LayerProps.swift in Sources */,
|
D1F7185D2215A4A1004E5951 /* LayerProps.swift in Sources */,
|
||||||
D1BFAF792215800A00845EA0 /* Counter.swift in Sources */,
|
D1BFAF792215800A00845EA0 /* Counter.swift in Sources */,
|
||||||
D1F718612215A617004E5951 /* Modals.swift in Sources */,
|
D1F718612215A617004E5951 /* Modals.swift in Sources */,
|
||||||
|
D1BB3D302223F6B400C30062 /* Animation.swift in Sources */,
|
||||||
D1F7185522159EAD004E5951 /* DatePickers.swift in Sources */,
|
D1F7185522159EAD004E5951 /* DatePickers.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
//
|
||||||
|
// Animation.swift
|
||||||
|
// TokamakDemo
|
||||||
|
//
|
||||||
|
// Created by Max Desiatov on 25/02/2019.
|
||||||
|
// Copyright © 2019 Tokamak. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Tokamak
|
||||||
|
|
||||||
|
struct Animation: CompositeComponent {
|
||||||
|
private static let colors: [(Color, String)] = [
|
||||||
|
(.white, "white"),
|
||||||
|
(.red, "red"),
|
||||||
|
(.green, "green"),
|
||||||
|
(.blue, "blue"),
|
||||||
|
]
|
||||||
|
|
||||||
|
typealias Props = Null
|
||||||
|
typealias Children = [AnyNode]
|
||||||
|
|
||||||
|
static func render(props: Null, children: Children, hooks: Hooks) -> AnyNode {
|
||||||
|
let previousColor = hooks.state(0)
|
||||||
|
let currentColor = hooks.state(0)
|
||||||
|
let ref = hooks.ref(type: UIView.self)
|
||||||
|
|
||||||
|
hooks.effect(currentColor.value) {
|
||||||
|
guard let view = ref.value else { return }
|
||||||
|
|
||||||
|
guard currentColor.value != previousColor.value else {
|
||||||
|
view.backgroundColor = UIColor(colors[currentColor.value].0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
UIView.animate(withDuration: 0.5, animations: {
|
||||||
|
view.backgroundColor = UIColor(colors[currentColor.value].0)
|
||||||
|
}, completion: { _ in
|
||||||
|
previousColor.set(currentColor.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return View.node(
|
||||||
|
.init(Style(
|
||||||
|
Edges.equal(to: .parent),
|
||||||
|
backgroundColor: .white
|
||||||
|
)),
|
||||||
|
StackView.node(.init(
|
||||||
|
Edges.equal(to: .safeArea),
|
||||||
|
axis: .vertical,
|
||||||
|
distribution: .fillEqually
|
||||||
|
), [
|
||||||
|
View.node(ref: ref, .init(), children),
|
||||||
|
SegmentedControl.node(
|
||||||
|
.init(
|
||||||
|
value: currentColor.value,
|
||||||
|
valueHandler: Handler {
|
||||||
|
// sometimes UISegmentedControl allows deselecting all segments
|
||||||
|
guard $0 >= 0 else { return }
|
||||||
|
currentColor.set($0)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
colors.map { $0.1 }
|
||||||
|
),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,42 +29,18 @@ struct NavigationModal: PureLeafComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SimpleModal: LeafComponent {
|
struct SimpleModal: PureLeafComponent {
|
||||||
struct Props: Equatable {
|
struct Props: Equatable {
|
||||||
let isPresented: State<Bool>
|
let isPresented: State<Bool>
|
||||||
}
|
}
|
||||||
|
|
||||||
private static let colors: [(Color, String)] = [
|
static func render(props: Props) -> AnyNode {
|
||||||
(.white, "white"),
|
|
||||||
(.red, "red"),
|
|
||||||
(.green, "green"),
|
|
||||||
(.blue, "blue"),
|
|
||||||
]
|
|
||||||
|
|
||||||
static func render(props: Props, hooks: Hooks) -> AnyNode {
|
|
||||||
let backgroundColor = hooks.state(0)
|
|
||||||
|
|
||||||
return props.isPresented.value ? ModalPresenter.node(
|
return props.isPresented.value ? ModalPresenter.node(
|
||||||
View.node(
|
Animation.node(Null(),
|
||||||
.init(Style(
|
Button.node(.init(
|
||||||
Edges.equal(to: .parent),
|
Style(Center.equal(to: .parent)),
|
||||||
backgroundColor: colors[backgroundColor.value].0
|
onPress: Handler { props.isPresented.set(false) }
|
||||||
)),
|
), "Close Modal"))
|
||||||
StackView.node(.init(
|
|
||||||
Edges.equal(to: .parent),
|
|
||||||
axis: .vertical,
|
|
||||||
distribution: .fillEqually
|
|
||||||
), [
|
|
||||||
Button.node(.init(
|
|
||||||
onPress: Handler { props.isPresented.set(false) }
|
|
||||||
), "Close Modal"),
|
|
||||||
SegmentedControl.node(
|
|
||||||
.init(value: backgroundColor.value,
|
|
||||||
valueHandler: Handler(backgroundColor.set)),
|
|
||||||
colors.map { $0.1 }
|
|
||||||
),
|
|
||||||
])
|
|
||||||
)
|
|
||||||
) : Null.node()
|
) : Null.node()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ struct TimerCounter: LeafComponent {
|
||||||
let timer = hooks.ref(type: Timer.self)
|
let timer = hooks.ref(type: Timer.self)
|
||||||
let interval = hooks.state(1.0)
|
let interval = hooks.state(1.0)
|
||||||
|
|
||||||
hooks.effect(interval.value) { () -> () -> () in
|
hooks.finalizedEffect(interval.value) {
|
||||||
timer.value = Timer.scheduledTimer(
|
timer.value = Timer.scheduledTimer(
|
||||||
withTimeInterval: interval.value,
|
withTimeInterval: interval.value,
|
||||||
repeats: true
|
repeats: true
|
||||||
|
|
|
@ -18,6 +18,7 @@ enum AppRoute: String, CaseIterable {
|
||||||
case layerProps = "Layer Props"
|
case layerProps = "Layer Props"
|
||||||
case timer
|
case timer
|
||||||
case image
|
case image
|
||||||
|
case animation
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppRoute: CustomStringConvertible {
|
extension AppRoute: CustomStringConvertible {
|
||||||
|
@ -59,6 +60,8 @@ struct Router: NavigationRouter {
|
||||||
result = TimerCounter.node()
|
result = TimerCounter.node()
|
||||||
case .image:
|
case .image:
|
||||||
result = ImageExample.node()
|
result = ImageExample.node()
|
||||||
|
case .animation:
|
||||||
|
result = Animation.node()
|
||||||
}
|
}
|
||||||
|
|
||||||
return NavigationItem.node(
|
return NavigationItem.node(
|
||||||
|
|
|
@ -77,6 +77,12 @@ extension Component where Children == [AnyNode] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Component where Props == Null, Children == [AnyNode] {
|
||||||
|
public static func node() -> AnyNode {
|
||||||
|
return node(Null(), [])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension Component where Props: Default, Props.DefaultValue == Props,
|
extension Component where Props: Default, Props.DefaultValue == Props,
|
||||||
Children == [AnyNode] {
|
Children == [AnyNode] {
|
||||||
public static func node(_ child: AnyNode) -> AnyNode {
|
public static func node(_ child: AnyNode) -> AnyNode {
|
||||||
|
@ -127,3 +133,23 @@ extension RefComponent {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension RefComponent where Children == [AnyNode] {
|
||||||
|
public static func node(
|
||||||
|
ref: Ref<RefTarget?>,
|
||||||
|
_ props: Props,
|
||||||
|
_ child: AnyNode
|
||||||
|
) -> AnyNode {
|
||||||
|
return node(ref: ref, props, [child])
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func node(ref: Ref<RefTarget?>, _ props: Props) -> AnyNode {
|
||||||
|
return node(ref: ref, props, [])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension RefComponent where Children == Null {
|
||||||
|
public static func node(ref: Ref<RefTarget?>, _ props: Props) -> AnyNode {
|
||||||
|
return node(ref: ref, props, Null())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ extension Hooks {
|
||||||
closure should return a cleanup closure to be executed before the next
|
closure should return a cleanup closure to be executed before the next
|
||||||
call to `render` or when a component is unmounted.
|
call to `render` or when a component is unmounted.
|
||||||
*/
|
*/
|
||||||
public func effect(closure: @escaping () -> () -> ()) {
|
public func finalizedEffect(closure: @escaping () -> () -> ()) {
|
||||||
scheduleEffect(nil, closure)
|
scheduleEffect(nil, closure)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,10 @@ extension Hooks {
|
||||||
trigger effect execution (unsubscribing from updates on old user ID and
|
trigger effect execution (unsubscribing from updates on old user ID and
|
||||||
subscribing for new user ID) when the ID has changed.
|
subscribing for new user ID) when the ID has changed.
|
||||||
*/
|
*/
|
||||||
public func effect<T>(_ observed: T, closure: @escaping () -> () -> ())
|
public func finalizedEffect<T>(
|
||||||
|
_ observed: T,
|
||||||
|
closure: @escaping () -> () -> ()
|
||||||
|
)
|
||||||
where T: Equatable {
|
where T: Equatable {
|
||||||
scheduleEffect(AnyEquatable(observed), closure)
|
scheduleEffect(AnyEquatable(observed), closure)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,6 @@
|
||||||
// Created by Max Desiatov on 09/02/2019.
|
// Created by Max Desiatov on 09/02/2019.
|
||||||
//
|
//
|
||||||
|
|
||||||
/// Formalizes an update to a value with a given action. Note that `update`
|
|
||||||
/// function is mutating here to allow efficient in-place updates. As long as
|
|
||||||
/// `Updatable` is implemented on a value type, this still allows freezing it
|
|
||||||
/// into an immutable value when needed.
|
|
||||||
public protocol Updatable {
|
|
||||||
associatedtype Action
|
|
||||||
|
|
||||||
mutating func update(_ action: Action)
|
|
||||||
}
|
|
||||||
|
|
||||||
typealias Updater<T> = (inout T) -> ()
|
typealias Updater<T> = (inout T) -> ()
|
||||||
|
|
||||||
/** Note that `set` functions are not `mutating`, they never update the
|
/** Note that `set` functions are not `mutating`, they never update the
|
||||||
|
@ -54,14 +44,6 @@ public struct State<T> {
|
||||||
|
|
||||||
extension State: Equatable where T: Equatable {}
|
extension State: Equatable where T: Equatable {}
|
||||||
|
|
||||||
extension State where T: Updatable {
|
|
||||||
/// For any `Reduceable` state you can dispatch an `Action` to reduce that
|
|
||||||
/// state to a different value.
|
|
||||||
public func set(_ action: T.Action) {
|
|
||||||
updateHandler.value { $0.update(action) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Hooks {
|
extension Hooks {
|
||||||
/** Allows a component to have its own state and to be updated when the state
|
/** Allows a component to have its own state and to be updated when the state
|
||||||
changes. Returns a very simple state container, which on initial call of
|
changes. Returns a very simple state container, which on initial call of
|
||||||
|
|
|
@ -42,6 +42,8 @@ public final class MountedHostComponent<R: Renderer>: MountedComponent<R> {
|
||||||
|
|
||||||
self.target = target
|
self.target = target
|
||||||
|
|
||||||
|
reconciler.renderer?.update(target: target, with: self)
|
||||||
|
|
||||||
switch node.children.value {
|
switch node.children.value {
|
||||||
case let nodes as [AnyNode]:
|
case let nodes as [AnyNode]:
|
||||||
mountedChildren = nodes.map { $0.makeMountedComponent(target) }
|
mountedChildren = nodes.map { $0.makeMountedComponent(target) }
|
||||||
|
|
|
@ -28,8 +28,6 @@ public final class TestRenderer: Renderer {
|
||||||
let result = TestView(component.node)
|
let result = TestView(component.node)
|
||||||
parent.add(subview: result)
|
parent.add(subview: result)
|
||||||
|
|
||||||
update(target: result, with: component)
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,4 +19,8 @@ class ViewBox<T: UIView>: ViewControllerBox<UIViewController> {
|
||||||
|
|
||||||
super.init(viewController, node)
|
super.init(viewController, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var refTarget: Any {
|
||||||
|
return view
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,4 +19,8 @@ class ViewControllerBox<T: UIViewController>: UITarget {
|
||||||
override var viewController: UIViewController {
|
override var viewController: UIViewController {
|
||||||
return containerViewController
|
return containerViewController
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var refTarget: Any {
|
||||||
|
return containerViewController
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ final class TokamakButton: UIButton, Default {
|
||||||
|
|
||||||
extension Button: UIControlComponent {
|
extension Button: UIControlComponent {
|
||||||
typealias Target = TokamakButton
|
typealias Target = TokamakButton
|
||||||
|
public typealias RefTarget = UIButton
|
||||||
|
|
||||||
static func update(control box: ControlBox<TokamakButton>,
|
static func update(control box: ControlBox<TokamakButton>,
|
||||||
_ props: Button.Props,
|
_ props: Button.Props,
|
||||||
|
|
|
@ -27,6 +27,7 @@ final class TokamakDatePicker: UIDatePicker, Default, ValueStorage {
|
||||||
|
|
||||||
extension DatePicker: UIValueComponent {
|
extension DatePicker: UIValueComponent {
|
||||||
typealias Target = TokamakDatePicker
|
typealias Target = TokamakDatePicker
|
||||||
|
public typealias RefTarget = UIDatePicker
|
||||||
|
|
||||||
static func update(valueBox: ValueControlBox<TokamakDatePicker>,
|
static func update(valueBox: ValueControlBox<TokamakDatePicker>,
|
||||||
_ props: DatePicker.Props,
|
_ props: DatePicker.Props,
|
||||||
|
|
|
@ -28,6 +28,8 @@ extension UIImage.RenderingMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Image: UIViewComponent {
|
extension Image: UIViewComponent {
|
||||||
|
public typealias RefTarget = UIImageView
|
||||||
|
|
||||||
static func update(
|
static func update(
|
||||||
view box: ViewBox<TokamakImage>,
|
view box: ViewBox<TokamakImage>,
|
||||||
_ props: Image.Props,
|
_ props: Image.Props,
|
||||||
|
|
|
@ -32,6 +32,8 @@ extension NSTextAlignment {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Label: UIViewComponent {
|
extension Label: UIViewComponent {
|
||||||
|
public typealias RefTarget = UILabel
|
||||||
|
|
||||||
static func update(view box: ViewBox<TokamakLabel>,
|
static func update(view box: ViewBox<TokamakLabel>,
|
||||||
_ props: Label.Props,
|
_ props: Label.Props,
|
||||||
_ children: String) {
|
_ children: String) {
|
||||||
|
|
|
@ -9,6 +9,8 @@ import Tokamak
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension ListView: UIViewComponent {
|
extension ListView: UIViewComponent {
|
||||||
|
public typealias RefTarget = UITableView
|
||||||
|
|
||||||
static func box(
|
static func box(
|
||||||
for view: TokamakTableView,
|
for view: TokamakTableView,
|
||||||
_ viewController: UIViewController,
|
_ viewController: UIViewController,
|
||||||
|
|
|
@ -41,7 +41,6 @@ extension NavigationItem: UIHostComponent {
|
||||||
|
|
||||||
let viewController = UIViewController()
|
let viewController = UIViewController()
|
||||||
let result = ViewControllerBox(viewController, component.node)
|
let result = ViewControllerBox(viewController, component.node)
|
||||||
update(target: result, node: component.node)
|
|
||||||
|
|
||||||
parent.containerViewController.pushViewController(
|
parent.containerViewController.pushViewController(
|
||||||
viewController,
|
viewController,
|
||||||
|
|
|
@ -25,6 +25,7 @@ final class TokamakSegmentedControl: UISegmentedControl, Default, ValueStorage {
|
||||||
|
|
||||||
extension SegmentedControl: UIValueComponent {
|
extension SegmentedControl: UIValueComponent {
|
||||||
typealias Target = TokamakSegmentedControl
|
typealias Target = TokamakSegmentedControl
|
||||||
|
public typealias RefTarget = UISegmentedControl
|
||||||
|
|
||||||
static func update(valueBox: ValueControlBox<TokamakSegmentedControl>,
|
static func update(valueBox: ValueControlBox<TokamakSegmentedControl>,
|
||||||
_ props: SegmentedControl.Props,
|
_ props: SegmentedControl.Props,
|
||||||
|
|
|
@ -16,6 +16,7 @@ final class TokamakSlider: UISlider, Default, ValueStorage {
|
||||||
|
|
||||||
extension Slider: UIValueComponent {
|
extension Slider: UIValueComponent {
|
||||||
typealias Target = TokamakSlider
|
typealias Target = TokamakSlider
|
||||||
|
public typealias RefTarget = UISlider
|
||||||
|
|
||||||
static func update(valueBox: ValueControlBox<TokamakSlider>,
|
static func update(valueBox: ValueControlBox<TokamakSlider>,
|
||||||
_ props: Slider.Props,
|
_ props: Slider.Props,
|
||||||
|
|
|
@ -60,6 +60,8 @@ extension UIStackView.Distribution {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StackView: UIViewComponent {
|
extension StackView: UIViewComponent {
|
||||||
|
public typealias RefTarget = UIStackView
|
||||||
|
|
||||||
static func update(view box: ViewBox<TokamakStackView>,
|
static func update(view box: ViewBox<TokamakStackView>,
|
||||||
_ props: StackView.Props,
|
_ props: StackView.Props,
|
||||||
_: [AnyNode]) {
|
_: [AnyNode]) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ final class TokamakStepper: UIStepper, Default, ValueStorage {
|
||||||
|
|
||||||
extension Stepper: UIValueComponent {
|
extension Stepper: UIValueComponent {
|
||||||
typealias Target = TokamakStepper
|
typealias Target = TokamakStepper
|
||||||
|
public typealias RefTarget = UIStepper
|
||||||
|
|
||||||
static func update(valueBox: ValueControlBox<TokamakStepper>,
|
static func update(valueBox: ValueControlBox<TokamakStepper>,
|
||||||
_ props: Stepper.Props,
|
_ props: Stepper.Props,
|
||||||
|
|
|
@ -27,6 +27,7 @@ final class TokamakSwitch: UISwitch, Default, ValueStorage {
|
||||||
|
|
||||||
extension Switch: UIValueComponent {
|
extension Switch: UIValueComponent {
|
||||||
typealias Target = TokamakSwitch
|
typealias Target = TokamakSwitch
|
||||||
|
public typealias RefTarget = UISwitch
|
||||||
|
|
||||||
static func update(valueBox: ValueControlBox<TokamakSwitch>,
|
static func update(valueBox: ValueControlBox<TokamakSwitch>,
|
||||||
_ props: Switch.Props,
|
_ props: Switch.Props,
|
||||||
|
|
|
@ -15,6 +15,8 @@ final class TokamakView: UIView, Default {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension View: UIViewComponent {
|
extension View: UIViewComponent {
|
||||||
|
public typealias RefTarget = UIView
|
||||||
|
|
||||||
static func update(view: ViewBox<TokamakView>,
|
static func update(view: ViewBox<TokamakView>,
|
||||||
_ props: View.Props,
|
_ props: View.Props,
|
||||||
_: [AnyNode]) {}
|
_: [AnyNode]) {}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Tokamak
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension UIColor {
|
extension UIColor {
|
||||||
convenience init(_ color: Color) {
|
public convenience init(_ color: Color) {
|
||||||
switch color.space {
|
switch color.space {
|
||||||
case .sRGB:
|
case .sRGB:
|
||||||
self.init(red: CGFloat(color.red),
|
self.init(red: CGFloat(color.red),
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Tokamak
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension UIControl.Event {
|
extension UIControl.Event {
|
||||||
init(_ value: Event) {
|
public init(_ value: Event) {
|
||||||
switch value {
|
switch value {
|
||||||
case .touchDown:
|
case .touchDown:
|
||||||
self = .touchDown
|
self = .touchDown
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import Tokamak
|
import Tokamak
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
protocol UIViewComponent: UIHostComponent, HostComponent {
|
protocol UIViewComponent: UIHostComponent, RefComponent {
|
||||||
associatedtype Target: UIView & Default
|
associatedtype Target: UIView & Default
|
||||||
|
|
||||||
static func update(view box: ViewBox<Target>,
|
static func update(view box: ViewBox<Target>,
|
||||||
|
@ -94,16 +94,6 @@ extension UIViewComponent where Target == Target.DefaultValue,
|
||||||
component: UIKitRenderer.MountedHost,
|
component: UIKitRenderer.MountedHost,
|
||||||
_ renderer: UIKitRenderer
|
_ renderer: UIKitRenderer
|
||||||
) -> UITarget? {
|
) -> UITarget? {
|
||||||
guard let children = component.node.children.value as? Children else {
|
|
||||||
childrenAssertionFailure()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let props = component.node.props.value as? Props else {
|
|
||||||
propsAssertionFailure()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let target = Target.defaultValue
|
let target = Target.defaultValue
|
||||||
let result: ViewBox<Target>
|
let result: ViewBox<Target>
|
||||||
|
|
||||||
|
@ -167,9 +157,6 @@ extension UIViewComponent where Target == Target.DefaultValue,
|
||||||
parentAssertionFailure()
|
parentAssertionFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStyle(result, props)
|
|
||||||
update(view: result, props, children)
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,11 @@ struct HackyProvider: SimpleCellProvider {
|
||||||
|
|
||||||
class UITarget: Target {
|
class UITarget: Target {
|
||||||
var viewController: UIViewController {
|
var viewController: UIViewController {
|
||||||
fatalError("viewController should be overriden in UITarget subclass")
|
fatalError("\(#function) should be overriden in UITarget subclass")
|
||||||
|
}
|
||||||
|
|
||||||
|
var refTarget: Any {
|
||||||
|
fatalError("\(#function) should be overriden in UITarget subclass")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +83,12 @@ final class UIKitRenderer: Renderer {
|
||||||
|
|
||||||
rendererComponent.update(target: target,
|
rendererComponent.update(target: target,
|
||||||
node: component.node)
|
node: component.node)
|
||||||
|
|
||||||
|
guard
|
||||||
|
let componentType = component.type as? AnyRefComponent.Type,
|
||||||
|
let anyRef = component.node.ref else { return }
|
||||||
|
|
||||||
|
componentType.update(ref: anyRef, with: target.refTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmount(
|
func unmount(
|
||||||
|
|
|
@ -14,22 +14,6 @@ extension Button: RefComponent {
|
||||||
public typealias RefTarget = TestView
|
public typealias RefTarget = TestView
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Int: Updatable {
|
|
||||||
public enum Action {
|
|
||||||
case increment
|
|
||||||
case decrement
|
|
||||||
}
|
|
||||||
|
|
||||||
public mutating func update(_ action: Int.Action) {
|
|
||||||
switch action {
|
|
||||||
case .decrement:
|
|
||||||
self -= 1
|
|
||||||
case .increment:
|
|
||||||
self += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension Hooks {
|
private extension Hooks {
|
||||||
func custom() -> State<Int> {
|
func custom() -> State<Int> {
|
||||||
return state(42)
|
return state(42)
|
||||||
|
@ -42,7 +26,6 @@ struct Test: LeafComponent {
|
||||||
static func render(props: Null, hooks: Hooks) -> AnyNode {
|
static func render(props: Null, hooks: Hooks) -> AnyNode {
|
||||||
let state1 = hooks.custom()
|
let state1 = hooks.custom()
|
||||||
let state2 = hooks.custom()
|
let state2 = hooks.custom()
|
||||||
let state3 = hooks.custom()
|
|
||||||
let ref = hooks.ref(type: TestView.self)
|
let ref = hooks.ref(type: TestView.self)
|
||||||
|
|
||||||
return StackView.node([
|
return StackView.node([
|
||||||
|
@ -55,9 +38,6 @@ struct Test: LeafComponent {
|
||||||
Button.node(.init(onPress: Handler { state2.set { $0 + 1 } }),
|
Button.node(.init(onPress: Handler { state2.set { $0 + 1 } }),
|
||||||
"Increment"),
|
"Increment"),
|
||||||
Label.node("\(state2.value)"),
|
Label.node("\(state2.value)"),
|
||||||
Button.node(.init(onPress: Handler { state3.set(.increment) }),
|
|
||||||
"Increment"),
|
|
||||||
Label.node("\(state3.value)"),
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,8 +54,6 @@ final class HooksTests: XCTestCase {
|
||||||
let button1Handler = button1Props.handlers[.touchUpInside]?.value,
|
let button1Handler = button1Props.handlers[.touchUpInside]?.value,
|
||||||
let button2Props = stack.subviews[2].props(Button.Props.self),
|
let button2Props = stack.subviews[2].props(Button.Props.self),
|
||||||
let button2Handler = button2Props.handlers[.touchUpInside]?.value,
|
let button2Handler = button2Props.handlers[.touchUpInside]?.value,
|
||||||
let button3Props = stack.subviews[4].props(Button.Props.self),
|
|
||||||
let button3Handler = button3Props.handlers[.touchUpInside]?.value,
|
|
||||||
let button1Ref = stack.subviews[0].node.ref as? Ref<TestView?>
|
let button1Ref = stack.subviews[0].node.ref as? Ref<TestView?>
|
||||||
else {
|
else {
|
||||||
XCTAssert(false, "components have no handlers")
|
XCTAssert(false, "components have no handlers")
|
||||||
|
@ -89,16 +67,11 @@ final class HooksTests: XCTestCase {
|
||||||
button2Handler(())
|
button2Handler(())
|
||||||
button2Handler(())
|
button2Handler(())
|
||||||
|
|
||||||
button3Handler(())
|
|
||||||
button3Handler(())
|
|
||||||
button3Handler(())
|
|
||||||
|
|
||||||
let e = expectation(description: "rerender")
|
let e = expectation(description: "rerender")
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
XCTAssertEqual(stack.subviews[1].node.children, AnyEquatable("43"))
|
XCTAssertEqual(stack.subviews[1].node.children, AnyEquatable("43"))
|
||||||
XCTAssertEqual(stack.subviews[3].node.children, AnyEquatable("44"))
|
XCTAssertEqual(stack.subviews[3].node.children, AnyEquatable("44"))
|
||||||
XCTAssertEqual(stack.subviews[5].node.children, AnyEquatable("45"))
|
|
||||||
|
|
||||||
e.fulfill()
|
e.fulfill()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue