{"id":19313647,"url":"https://github.com/geektree0101/knot","last_synced_at":"2025-04-22T16:31:18.070Z","repository":{"id":56917993,"uuid":"203000466","full_name":"GeekTree0101/Knot","owner":"GeekTree0101","description":"Knot is lightweight \u0026 predictable state driven node extension library for Texture(AsyncDisplayKit)","archived":false,"fork":false,"pushed_at":"2019-08-23T00:28:17.000Z","size":187,"stargazers_count":11,"open_issues_count":1,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-10-11T07:13:40.040Z","etag":null,"topics":["asyncdisplaykit","rxswift","rxswift-extensions","texture"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GeekTree0101.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-08-18T12:30:25.000Z","updated_at":"2022-04-28T08:23:53.000Z","dependencies_parsed_at":"2022-08-20T21:20:33.041Z","dependency_job_id":null,"html_url":"https://github.com/GeekTree0101/Knot","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeekTree0101%2FKnot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeekTree0101%2FKnot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeekTree0101%2FKnot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeekTree0101%2FKnot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GeekTree0101","download_url":"https://codeload.github.com/GeekTree0101/Knot/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223900316,"owners_count":17222028,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asyncdisplaykit","rxswift","rxswift-extensions","texture"],"created_at":"2024-11-10T00:40:34.288Z","updated_at":"2024-11-10T00:40:34.913Z","avatar_url":"https://github.com/GeekTree0101.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"https://github.com/GeekTree0101/Knot/blob/master/screenshot/banner.png\" /\u003e\n\n[![CI Status](https://img.shields.io/travis/Geektree0101/Knot.svg?style=flat)](https://travis-ci.org/Geektree0101/Knot)\n[![Version](https://img.shields.io/cocoapods/v/Knot.svg?style=flat)](https://cocoapods.org/pods/Knot)\n[![License](https://img.shields.io/cocoapods/l/Knot.svg?style=flat)](https://cocoapods.org/pods/Knot)\n[![Platform](https://img.shields.io/cocoapods/p/Knot.svg?style=flat)](https://cocoapods.org/pods/Knot)\n\n## Intro\n- Lightweight \u0026 Predictable (Rx dep only)\n- You can easy to separate presentation logic from business logic. \n- KnotState will make your presentation logic as reusability.\n- Support a disposeBag (Just inherit knotable, you don't needs make a disposeBag property :) )\n- Efficient updating node layout with state stream.\n\n## Quick Example\n\n\u003e Node\n```swift\nclass Node: ASDisplayNode \u0026 Knotable {\n  \n  struct State: KnotState {\n    \n    var title: String\n    var subTitle: String\n    \n    static func defaultState() -\u003e State {\n      return .init(title: \"-\", subTitle: \"-\")\n    }\n  }\n  \n  private enum Const {\n    static let titleStyle: StringStyle = .init(.font(UIFont.boldSystemFont(ofSize: 30.0)), .color(.gray))\n    static let subTitleStyle: StringStyle = .init(.font(UIFont.boldSystemFont(ofSize: 20.0)), .color(.lightGray))\n  }\n  \n  let titleNode = ASTextNode.init()\n  let subTitleNode = ASTextNode.init()\n  \n  override init() {\n    super.init()\n    automaticallyManagesSubnodes = true\n  }\n  \n  public func update(_ state: State) {\n    \n    titleNode.update({\n      $0.attributedText = state.title.styled(with: Const.titleStyle)\n    })\n    \n    subTitleNode.update({\n      $0.attributedText = state.subTitle.styled(with: Const.subTitleStyle)\n    })\n  }\n  \n  override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -\u003e ASLayoutSpec {\n    \n    let stackLayout = ASStackLayoutSpec.init(\n      direction: .vertical,\n      spacing: 20.0,\n      justifyContent: .center,\n      alignItems: .center,\n      children: [\n        titleNode,\n        subTitleNode\n      ]\n    )\n    \n    return ASInsetLayoutSpec.init(\n      insets: .zero,\n      child: stackLayout\n    )\n  }\n}\n```\n\n\u003e Controller\n```swift\n\nfinal class ViewController: ASViewController\u003cASDisplayNode\u003e {\n  \n  let testNode = Node.init()\n  \n  let disposeBag = DisposeBag()\n  \n  init() {\n    super.init(node: .init())\n    self.title = \"Knot\"\n    self.node.backgroundColor = .white\n    self.node.automaticallyManagesSubnodes = true\n    self.node.layoutSpecBlock = { [weak self] (_, _) -\u003e ASLayoutSpec in\n      guard let self = self else { return ASLayoutSpec() }\n      return ASCenterLayoutSpec.init(\n        centeringOptions: .XY,\n        sizingOptions: [],\n        child: self.testNode\n      )\n    }\n    \n    Observable\u003cInt\u003e\n      .interval(DispatchTimeInterval.milliseconds(100), scheduler: MainScheduler.instance)\n      .delaySubscription(DispatchTimeInterval.seconds(1), scheduler: MainScheduler.instance)\n      .pipe(to: testNode, {\n        var (integer, state) = $0\n        state.title = \"\\(integer)\"\n        return state\n      })\n      .disposed(by: disposeBag)\n    \n    Observable\u003cInt\u003e\n      .interval(DispatchTimeInterval.milliseconds(10), scheduler: MainScheduler.instance)\n      .delaySubscription(DispatchTimeInterval.seconds(1), scheduler: MainScheduler.instance)\n      .filter(with: testNode, {\n        return $0.0 \u003c 100\n      })\n      .pipe(to: testNode, {\n        var (integer, state) = $0\n        state.subTitle = \"\\(integer)\"\n        return state\n      })\n      .disposed(by: disposeBag)\n  }\n  \n  required init?(coder aDecoder: NSCoder) {\n    fatalError(\"init(coder:) has not been implemented\")\n  }\n}\n```\n\n## API Guide\n\n### Knotable\nKnotable will make your node as **predictable state driven node** \n\n#### Knotable \u0026 KnotState\nBy inheriting **Knotable**, you can design as a responsive node.\n\n\u003e Example\n```swift\n\nclass Node: ASDisplayNode \u0026 Knotable {\n\n  let titleNode = ASTextNode()\n   \n  struct State: KnotState { // 1. inherit Knot State protocol\n  \n    var displayTitle: String\n  \n    static func defaultState() -\u003e State {\n      // 2. return defaultState from static defaultState method\n    }\n  }\n  \n\n  // 3. Use a state with updateBlock or as you know\n  public func update(_ state: State) {\n    \n    // using updateBlock\n    titleNode.update({\n      $0.attributedText = NSAttributedString(string: state.displayTitle)\n    })\n    \n    // or as you know\n    titleNode.attributedText = NSAttributedString(string: state.displayTitle)\n    \n    // you don't needs call setNeedsLayout: :)\n  }\n}\n```\n\nState objects can be **separated to the outside**.\n\u003e Example\n```swift\nstruct SomeState: KnotState {\n  \n    var displayTitle: String\n  \n    static func defaultState() -\u003e State {\n      return .init(displayTitle: \"-\")\n    }\n}\n\nclass Node: ASDisplayNode \u0026 Knotable {\n\n  typealias State = SomeState\n\n  let titleNode = ASTextNode()\n\n  public func update(_ state: State) {\n  \n    titleNode.update({\n      $0.attributedText = NSAttributedString(string: state.displayTitle)\n    })\n  }\n}\n```\n\n#### Sink\nYou can set state directly as sink:\n```swift\nlet node = KnotableNode()\nnode.sink(State.init(...))\n```\n\n#### Stream\nYou can set state from observable with stream property. In this case, you don't needs call setNeedsLayout :)\n```swift\nObservable.just(State.init(...)).bind(to: node.stream)\n```\n\n### ObservableType convenience extension APIs\n\n#### pipe(to: KnotableNode)\nIf observable or subject element is KnotSate then you can just use **pipe(to:)**\nit equal to **bind(to: knotableNode.stream)**\n```swift\n  let node: Knotable \u0026 SomeNode = .init(...)\n\n  Observable.just(State.init(...))\n    .pipe(to: node)\n    .disposed(by: disposeBag)\n    \n  // equal \n  \n  Observable.just(State.init(...))\n    .bind(to: node.stream)\n    .disposed(by: disposeBag)\n```\n\nAlternatively, You can reduce knotable node state with event\n```swift\n  Observable.just(100)\n    .pipe(to: node, {\n      var (event, state) = $0\n      state.count = event\n      return state\n    })\n    .disposed(by: disposeBag)\n```\n  \n#### filter(with: KnotableNode)\nYou can filter event with node state\n```swift\n  Observable.just(100)\n    .filter(with: node, { (event, state) -\u003e Bool in\n      return event == state.count\n    }\n    //...\n ```\n \n### withState(from: KnotableNode)\nYou can get state with event\n```swift\n  Observable.just(100).withState(from: node) // 100, state\n```\n\n#### state(from: KnotableNode)\nYou can get state without event\n```swift\n  Observable.just(100).state(from: node) // state\n```\n\n\u003e Example\n```swift\nlet testNode = TestNode()\n\ntestNode.rx.tap\n   .state(from: testNode)\n   .subscribe(onNext: { state in \n     // TODO\n   })\n   .disposed(by: testNode.disposeBag)\n```\n\n## Requirements\n- Xcode 10.x\n- Swift 5.x\n- RxSwift/Cocoa 5.x\n\n## Installation\n\nKnot is available through [CocoaPods](https://cocoapods.org). To install\nit, simply add the following line to your Podfile:\n\n```ruby\npod 'Knot'\n```\n\n## Author\n\nGeektree0101, h2s1880@gmail.com\n\n## License\n\nKnot is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeektree0101%2Fknot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeektree0101%2Fknot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeektree0101%2Fknot/lists"}