简介
- 轻量级 & 可预测(仅依赖 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 文件。