结点 1.2.0

Knot 1.2.0

Geektree0101 维护。



 
依赖
RxSwift~> 5.0
RxCocoa~> 5.0
Texture~> 2.8
 

Knot 1.2.0

  • Geektree0101

CI Status Version License Platform

简介

  • 轻量级 & 可预测(仅依赖 Rx)
  • 您可以轻松地分离展示逻辑和业务逻辑。
  • KnotState 将使您的展示逻辑更加可重用。
  • 支持 disposeBag(只需继承 knotable,您不需要创建 disposeBag 属性 :))
  • 使用状态流高效更新节点布局。

快速示例

节点

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
    )
  }
}

控制器

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")
  }
}

API 指南

Knotable

Knotable 将使您的节点成为 可预测的状态驱动节点

Knotable & KnotState

通过继承 Knotable,您可以设计响应式的节点。

示例

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: :)
  }
}

状态对象可以被 分离到外部

示例

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)
    })
  }
}

Sink

您可以直接将状态设置为Sink。

let node = KnotableNode()
node.sink(State.init(...))

Stream

您可以通过具有stream属性的可观察对象设置状态。在这种情况下,您不需要调用setNeedsLayout :)

Observable.just(State.init(...)).bind(to: node.stream)

ObservableType便捷扩展API

pipe(to: KnotableNode)

如果可观察对象或Subject元素是KnotState,则可以直接使用pipe(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)

或者,您可以使用事件来减少Knotable节点状态

  Observable.just(100)
    .pipe(to: node, {
      var (event, state) = $0
      state.count = event
      return state
    })
    .disposed(by: disposeBag)

filter(with: KnotableNode)

您可以使用节点状态过滤事件

  Observable.just(100)
    .filter(with: node, { (event, state) -> Bool in
      return event == state.count
    }
    //...

withState(from: KnotableNode)

您可以通过事件获取状态

  Observable.just(100).withState(from: node) // 100, state

state(from: KnotableNode)

您可以用没有事件的方式来获取状态

  Observable.just(100).state(from: node) // state

示例

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 可通过 CocoaPods 获取。要安装,只需将以下行添加到您的 Podfile 中

pod 'Knot'

作者

Geektree0101, [email protected]

许可证

Knot 在 MIT 许可下可用。更多信息请参阅 LICENSE 文件。