CI Status Version License Platform
- Lightweight & Predictable (Rx dep only)
- You can easy to separate presentation logic from business logic.
- KnotState will make your presentation logic as reusability.
- Support a disposeBag (Just inherit knotable, you don't needs make a disposeBag property :) )
- Efficient updating node layout with state stream.
Node
class Node: ASDisplayNode & Knotable { struct State: KnotState { var title: String var subTitle: String static func defaultState() -> State { return .init(title: "-", subTitle: "-") } } private enum Const { static let titleStyle: StringStyle = .init(.font(UIFont.boldSystemFont(ofSize: 30.0)), .color(.gray)) static let subTitleStyle: StringStyle = .init(.font(UIFont.boldSystemFont(ofSize: 20.0)), .color(.lightGray)) } let titleNode = ASTextNode.init() let subTitleNode = ASTextNode.init() override init() { super.init() automaticallyManagesSubnodes = true } public func update(_ state: State) { titleNode.update({ 0ドル.attributedText = state.title.styled(with: Const.titleStyle) }) subTitleNode.update({ 0ドル.attributedText = state.subTitle.styled(with: Const.subTitleStyle) }) } override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { let stackLayout = ASStackLayoutSpec.init( direction: .vertical, spacing: 20.0, justifyContent: .center, alignItems: .center, children: [ titleNode, subTitleNode ] ) return ASInsetLayoutSpec.init( insets: .zero, child: stackLayout ) } }
Controller
final class ViewController: ASViewController<ASDisplayNode> { let testNode = Node.init() let disposeBag = DisposeBag() init() { super.init(node: .init()) self.title = "Knot" self.node.backgroundColor = .white self.node.automaticallyManagesSubnodes = true self.node.layoutSpecBlock = { [weak self] (_, _) -> ASLayoutSpec in guard let self = self else { return ASLayoutSpec() } return ASCenterLayoutSpec.init( centeringOptions: .XY, sizingOptions: [], child: self.testNode ) } Observable<Int> .interval(DispatchTimeInterval.milliseconds(100), scheduler: MainScheduler.instance) .delaySubscription(DispatchTimeInterval.seconds(1), scheduler: MainScheduler.instance) .pipe(to: testNode, { var (integer, state) = 0ドル state.title = "\(integer)" return state }) .disposed(by: disposeBag) Observable<Int> .interval(DispatchTimeInterval.milliseconds(10), scheduler: MainScheduler.instance) .delaySubscription(DispatchTimeInterval.seconds(1), scheduler: MainScheduler.instance) .filter(with: testNode, { return 0ドル.0 < 100 }) .pipe(to: testNode, { var (integer, state) = 0ドル state.subTitle = "\(integer)" return state }) .disposed(by: disposeBag) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
Knotable will make your node as predictable state driven node
By inheriting Knotable, you can design as a responsive node.
Example
class Node: ASDisplayNode & Knotable { let titleNode = ASTextNode() struct State: KnotState { // 1. inherit Knot State protocol var displayTitle: String static func defaultState() -> State { // 2. return defaultState from static defaultState method } } // 3. Use a state with updateBlock or as you know public func update(_ state: State) { // using updateBlock titleNode.update({ 0ドル.attributedText = NSAttributedString(string: state.displayTitle) }) // or as you know titleNode.attributedText = NSAttributedString(string: state.displayTitle) // you don't needs call setNeedsLayout: :) } }
State objects can be separated to the outside.
Example
struct SomeState: KnotState { var displayTitle: String static func defaultState() -> State { return .init(displayTitle: "-") } } class Node: ASDisplayNode & Knotable { typealias State = SomeState let titleNode = ASTextNode() public func update(_ state: State) { titleNode.update({ 0ドル.attributedText = NSAttributedString(string: state.displayTitle) }) } }
You can set state directly as sink:
let node = KnotableNode() node.sink(State.init(...))
You can set state from observable with stream property. In this case, you don't needs call setNeedsLayout :)
Observable.just(State.init(...)).bind(to: node.stream)
If observable or subject element is KnotSate then you can just use pipe(to:) it equal to bind(to: knotableNode.stream)
let node: Knotable & SomeNode = .init(...) Observable.just(State.init(...)) .pipe(to: node) .disposed(by: disposeBag) // equal Observable.just(State.init(...)) .bind(to: node.stream) .disposed(by: disposeBag)
Alternatively, You can reduce knotable node state with event
Observable.just(100) .pipe(to: node, { var (event, state) = 0ドル state.count = event return state }) .disposed(by: disposeBag)
You can filter event with node state
Observable.just(100) .filter(with: node, { (event, state) -> Bool in return event == state.count } //...
You can get state with event
Observable.just(100).withState(from: node) // 100, state
You can get state without event
Observable.just(100).state(from: node) // state
Example
let testNode = TestNode() testNode.rx.tap .state(from: testNode) .subscribe(onNext: { state in // TODO }) .disposed(by: testNode.disposeBag)
- Xcode 10.x
- Swift 5.x
- RxSwift/Cocoa 5.x
Knot is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'Knot'
Geektree0101, h2s1880@gmail.com
Knot is available under the MIT license. See the LICENSE file for more info.