├── .gitignore ├── .travis.yml ├── Example ├── .gitignore ├── Podfile ├── RxCocoa-Texture.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── RxCocoa-Texture-Example.xcscheme ├── RxCocoa-Texture.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── RxCocoa-Texture │ ├── ASBinderTestNode.swift │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── ButtonTestNode.swift │ ├── Decoder+extension.swift │ ├── EditableTextTestNode.swift │ ├── ImageDownloader.swift │ ├── ImageTestNode.swift │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── texture_icon.imageset │ │ │ ├── Contents.json │ │ │ └── texture_icon.png │ ├── Info.plist │ ├── Network.swift │ ├── NetworkImageTestNode.swift │ ├── RepoProvider.swift │ ├── RepoService.swift │ ├── Repository.swift │ ├── RepositoryListCellNode.swift │ ├── RepositoryViewController.swift │ ├── RepositoryViewModel.swift │ ├── RepositoryViewModel2.swift │ ├── TextTestNode.swift │ └── User.swift └── Tests │ ├── ASButtonNode+RxSpec.swift │ ├── ASControlNode+RxSpec.swift │ ├── ASDisplayNode+RxSpec.swift │ ├── ASEditableTextNode+RxSpec.swift │ ├── ASImageNode+RxSpec.swift │ ├── ASNetworkImageNode+RxSpec.swift │ ├── ASTextNode+RxSpec.swift │ ├── ASTextNode2+RxSpec.swift │ ├── BinderAndDriver+RxSpec.swift │ └── Info.plist ├── LICENSE ├── README.md ├── RxCocoa-Texture.podspec ├── RxCocoa-Texture ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── ASBinder.swift │ ├── ASButtonNode+Rx.swift │ ├── ASControlNode+Rx.swift │ ├── ASControlTarget.swift │ ├── ASDisplayNode+Rx.swift │ ├── ASEditableTextNode+Rx.swift │ ├── ASImageNode+Rx.swift │ ├── ASNetworkImageNode+Rx.swift │ ├── ASTextNode+Rx.swift │ ├── ASTextNode2+Rx.swift │ ├── Driver+ASSubscription.swift │ └── RxASEditableTextNodeDelegateProxy.swift ├── _Pods.xcodeproj └── resources ├── ASMVI.png ├── asbinder_workflow.png ├── badcase.png ├── badcase2.png ├── expect.png ├── expect2.png ├── flow.png └── logo.png /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 26 | # Carthage/Checkouts 27 | 28 | Carthage/Build 29 | 30 | # We recommend against adding the Pods directory to your .gitignore. However 31 | # you should judge for yourself, the pros and cons are mentioned at: 32 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 33 | # 34 | # Note: if you ignore the Pods directory, make sure to uncomment 35 | # `pod install` in .travis.yml 36 | # 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode11 3 | 4 | cache: cocoapods 5 | podfile: Example/Podfile 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | before_install: 12 | - gem install cocoapods 13 | - pod install --repo-update --project-directory=Example 14 | 15 | before_script: 16 | - set -o pipefail 17 | 18 | script: 19 | - xcodebuild clean build test 20 | -workspace Example/RxCocoa-Texture.xcworkspace 21 | -scheme RxCocoa-Texture-Example 22 | -sdk iphonesimulator 23 | -destination 'platform=iOS Simulator,name=iPhone 11 Pro,OS=13.0' 24 | -configuration Debug 25 | -enableCodeCoverage YES 26 | CODE_SIGNING_REQUIRED=NO | xcpretty 27 | -------------------------------------------------------------------------------- /Example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .Trashes 3 | *.lock 4 | Pods/ 5 | build/**/* 6 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'RxCocoa-Texture_Example' do 4 | pod 'RxCocoa-Texture', :path => '../' 5 | pod 'RxAlamofire' 6 | 7 | target 'RxCocoa-Texture_Tests' do 8 | inherit! :search_paths 9 | pod 'RxTest' 10 | pod 'RxBlocking' 11 | pod 'Quick' 12 | pod 'Nimble' 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 11 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 12 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 13 | 9B0E1603210DA3CE0013EC86 /* ASImageNode+RxSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E15FC210DA33F0013EC86 /* ASImageNode+RxSpec.swift */; }; 14 | 9B0E1605210DA3E30013EC86 /* ASButtonNode+RxSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E15F7210DA33F0013EC86 /* ASButtonNode+RxSpec.swift */; }; 15 | 9B0E1606210DA3E50013EC86 /* ASControlNode+RxSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E15F8210DA33F0013EC86 /* ASControlNode+RxSpec.swift */; }; 16 | 9B0E1607210DA3E80013EC86 /* ASEditableTextNode+RxSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E15FA210DA33F0013EC86 /* ASEditableTextNode+RxSpec.swift */; }; 17 | 9B0E1608210DA3ED0013EC86 /* ASNetworkImageNode+RxSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E15FB210DA33F0013EC86 /* ASNetworkImageNode+RxSpec.swift */; }; 18 | 9B0E1609210DA3F00013EC86 /* ASTextNode+RxSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E15F9210DA33F0013EC86 /* ASTextNode+RxSpec.swift */; }; 19 | 9B0E161D210DAD550013EC86 /* RepositoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E161B210DAD540013EC86 /* RepositoryViewController.swift */; }; 20 | 9B0E1622210DAD6E0013EC86 /* RepoProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E1620210DAD6C0013EC86 /* RepoProvider.swift */; }; 21 | 9B0E1626210DAD7B0013EC86 /* Decoder+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E1625210DAD7B0013EC86 /* Decoder+extension.swift */; }; 22 | 9B0E162A210DAD890013EC86 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E1628210DAD880013EC86 /* Repository.swift */; }; 23 | 9B0E162B210DAD890013EC86 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E1629210DAD890013EC86 /* User.swift */; }; 24 | 9B0E162F210DAD9D0013EC86 /* RepoService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E162D210DAD9C0013EC86 /* RepoService.swift */; }; 25 | 9B0E1630210DAD9D0013EC86 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E162E210DAD9D0013EC86 /* Network.swift */; }; 26 | 9B0E1639210DADB70013EC86 /* RepositoryListCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E1632210DADB50013EC86 /* RepositoryListCellNode.swift */; }; 27 | 9B0E163A210DADB70013EC86 /* TextTestNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E1633210DADB50013EC86 /* TextTestNode.swift */; }; 28 | 9B0E163B210DADB70013EC86 /* NetworkImageTestNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E1634210DADB50013EC86 /* NetworkImageTestNode.swift */; }; 29 | 9B0E163C210DADB70013EC86 /* ButtonTestNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E1635210DADB60013EC86 /* ButtonTestNode.swift */; }; 30 | 9B0E163D210DADB70013EC86 /* EditableTextTestNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E1636210DADB60013EC86 /* EditableTextTestNode.swift */; }; 31 | 9B0E163E210DADB70013EC86 /* ImageTestNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E1637210DADB60013EC86 /* ImageTestNode.swift */; }; 32 | 9B0E163F210DADB70013EC86 /* ASBinderTestNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E1638210DADB70013EC86 /* ASBinderTestNode.swift */; }; 33 | 9B0E1642210DADCF0013EC86 /* RepositoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0E1641210DADCE0013EC86 /* RepositoryViewModel.swift */; }; 34 | 9B6C23932124EB4200129E2F /* RepositoryViewModel2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B6C23922124EB4200129E2F /* RepositoryViewModel2.swift */; }; 35 | 9BB3ECFE22784059007CE252 /* BinderAndDriver+RxSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB3ECFC22783FB7007CE252 /* BinderAndDriver+RxSpec.swift */; }; 36 | 9BB3ED02227842C5007CE252 /* ASTextNode2+RxSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB3ED01227842C5007CE252 /* ASTextNode2+RxSpec.swift */; }; 37 | 9BE3DFE3211E780600816FD9 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE3DFE2211E780500816FD9 /* ImageDownloader.swift */; }; 38 | BA5756B9222CC72D00D8CBD9 /* ASDisplayNode+RxSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA5756B7222CC72200D8CBD9 /* ASDisplayNode+RxSpec.swift */; }; 39 | C2B9E94A4FCBBCFCA86BBE1B /* Pods_RxCocoa_Texture_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30C467A356AD9535ACE740C9 /* Pods_RxCocoa_Texture_Tests.framework */; }; 40 | F77E2B9F7CE27BC62474DD2B /* Pods_RxCocoa_Texture_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B64F85DE41A757B8E1C3E68C /* Pods_RxCocoa_Texture_Example.framework */; }; 41 | /* End PBXBuildFile section */ 42 | 43 | /* Begin PBXContainerItemProxy section */ 44 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { 45 | isa = PBXContainerItemProxy; 46 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */; 47 | proxyType = 1; 48 | remoteGlobalIDString = 607FACCF1AFB9204008FA782; 49 | remoteInfo = "RxCocoa-Texture"; 50 | }; 51 | /* End PBXContainerItemProxy section */ 52 | 53 | /* Begin PBXFileReference section */ 54 | 30C467A356AD9535ACE740C9 /* Pods_RxCocoa_Texture_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxCocoa_Texture_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 4850A96BAD26591DCD13654D /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 56 | 607FACD01AFB9204008FA782 /* RxCocoa-Texture_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "RxCocoa-Texture_Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 59 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 60 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 61 | 607FACE51AFB9204008FA782 /* RxCocoa-Texture_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "RxCocoa-Texture_Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | 64EB5963EE48A355A0DC1490 /* RxCocoa-Texture.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = "RxCocoa-Texture.podspec"; path = "../RxCocoa-Texture.podspec"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 64 | 9B0E15F7210DA33F0013EC86 /* ASButtonNode+RxSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ASButtonNode+RxSpec.swift"; sourceTree = ""; }; 65 | 9B0E15F8210DA33F0013EC86 /* ASControlNode+RxSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ASControlNode+RxSpec.swift"; sourceTree = ""; }; 66 | 9B0E15F9210DA33F0013EC86 /* ASTextNode+RxSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ASTextNode+RxSpec.swift"; sourceTree = ""; }; 67 | 9B0E15FA210DA33F0013EC86 /* ASEditableTextNode+RxSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ASEditableTextNode+RxSpec.swift"; sourceTree = ""; }; 68 | 9B0E15FB210DA33F0013EC86 /* ASNetworkImageNode+RxSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ASNetworkImageNode+RxSpec.swift"; sourceTree = ""; }; 69 | 9B0E15FC210DA33F0013EC86 /* ASImageNode+RxSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ASImageNode+RxSpec.swift"; sourceTree = ""; }; 70 | 9B0E161B210DAD540013EC86 /* RepositoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryViewController.swift; sourceTree = ""; }; 71 | 9B0E1620210DAD6C0013EC86 /* RepoProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepoProvider.swift; sourceTree = ""; }; 72 | 9B0E1625210DAD7B0013EC86 /* Decoder+extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Decoder+extension.swift"; sourceTree = ""; }; 73 | 9B0E1628210DAD880013EC86 /* Repository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = ""; }; 74 | 9B0E1629210DAD890013EC86 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 75 | 9B0E162D210DAD9C0013EC86 /* RepoService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepoService.swift; sourceTree = ""; }; 76 | 9B0E162E210DAD9D0013EC86 /* Network.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; 77 | 9B0E1632210DADB50013EC86 /* RepositoryListCellNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryListCellNode.swift; sourceTree = ""; }; 78 | 9B0E1633210DADB50013EC86 /* TextTestNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextTestNode.swift; sourceTree = ""; }; 79 | 9B0E1634210DADB50013EC86 /* NetworkImageTestNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkImageTestNode.swift; sourceTree = ""; }; 80 | 9B0E1635210DADB60013EC86 /* ButtonTestNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonTestNode.swift; sourceTree = ""; }; 81 | 9B0E1636210DADB60013EC86 /* EditableTextTestNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditableTextTestNode.swift; sourceTree = ""; }; 82 | 9B0E1637210DADB60013EC86 /* ImageTestNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageTestNode.swift; sourceTree = ""; }; 83 | 9B0E1638210DADB70013EC86 /* ASBinderTestNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASBinderTestNode.swift; sourceTree = ""; }; 84 | 9B0E1641210DADCE0013EC86 /* RepositoryViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryViewModel.swift; sourceTree = ""; }; 85 | 9B6C23922124EB4200129E2F /* RepositoryViewModel2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryViewModel2.swift; sourceTree = ""; }; 86 | 9BB3ECFC22783FB7007CE252 /* BinderAndDriver+RxSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BinderAndDriver+RxSpec.swift"; sourceTree = ""; }; 87 | 9BB3ED01227842C5007CE252 /* ASTextNode2+RxSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ASTextNode2+RxSpec.swift"; sourceTree = ""; }; 88 | 9BE3DFE2211E780500816FD9 /* ImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = ""; }; 89 | 9E1C79DA561D40B89763472E /* Pods-RxCocoa-Texture_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxCocoa-Texture_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RxCocoa-Texture_Tests/Pods-RxCocoa-Texture_Tests.debug.xcconfig"; sourceTree = ""; }; 90 | A700DE1AE94F129E06542938 /* Pods-RxCocoa-Texture_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxCocoa-Texture_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-RxCocoa-Texture_Example/Pods-RxCocoa-Texture_Example.release.xcconfig"; sourceTree = ""; }; 91 | B64F85DE41A757B8E1C3E68C /* Pods_RxCocoa_Texture_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxCocoa_Texture_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 92 | BA5756B7222CC72200D8CBD9 /* ASDisplayNode+RxSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ASDisplayNode+RxSpec.swift"; sourceTree = ""; wrapsLines = 0; }; 93 | CB878FDCA4BF09167F15A871 /* Pods-RxCocoa-Texture_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxCocoa-Texture_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RxCocoa-Texture_Tests/Pods-RxCocoa-Texture_Tests.release.xcconfig"; sourceTree = ""; }; 94 | D34921410F2445031C571EB1 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 95 | F4C2FBFBDA0CE9B43659AD60 /* Pods-RxCocoa-Texture_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxCocoa-Texture_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RxCocoa-Texture_Example/Pods-RxCocoa-Texture_Example.debug.xcconfig"; sourceTree = ""; }; 96 | /* End PBXFileReference section */ 97 | 98 | /* Begin PBXFrameworksBuildPhase section */ 99 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 100 | isa = PBXFrameworksBuildPhase; 101 | buildActionMask = 2147483647; 102 | files = ( 103 | F77E2B9F7CE27BC62474DD2B /* Pods_RxCocoa_Texture_Example.framework in Frameworks */, 104 | ); 105 | runOnlyForDeploymentPostprocessing = 0; 106 | }; 107 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 108 | isa = PBXFrameworksBuildPhase; 109 | buildActionMask = 2147483647; 110 | files = ( 111 | C2B9E94A4FCBBCFCA86BBE1B /* Pods_RxCocoa_Texture_Tests.framework in Frameworks */, 112 | ); 113 | runOnlyForDeploymentPostprocessing = 0; 114 | }; 115 | /* End PBXFrameworksBuildPhase section */ 116 | 117 | /* Begin PBXGroup section */ 118 | 24A6E5B9210924076F6C4FBC /* Pods */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | F4C2FBFBDA0CE9B43659AD60 /* Pods-RxCocoa-Texture_Example.debug.xcconfig */, 122 | A700DE1AE94F129E06542938 /* Pods-RxCocoa-Texture_Example.release.xcconfig */, 123 | 9E1C79DA561D40B89763472E /* Pods-RxCocoa-Texture_Tests.debug.xcconfig */, 124 | CB878FDCA4BF09167F15A871 /* Pods-RxCocoa-Texture_Tests.release.xcconfig */, 125 | ); 126 | name = Pods; 127 | sourceTree = ""; 128 | }; 129 | 607FACC71AFB9204008FA782 = { 130 | isa = PBXGroup; 131 | children = ( 132 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 133 | 607FACD21AFB9204008FA782 /* Example for RxCocoa-Texture */, 134 | 607FACE81AFB9204008FA782 /* Tests */, 135 | 607FACD11AFB9204008FA782 /* Products */, 136 | 24A6E5B9210924076F6C4FBC /* Pods */, 137 | DEF87B7647FEB19F408929F0 /* Frameworks */, 138 | ); 139 | sourceTree = ""; 140 | }; 141 | 607FACD11AFB9204008FA782 /* Products */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 607FACD01AFB9204008FA782 /* RxCocoa-Texture_Example.app */, 145 | 607FACE51AFB9204008FA782 /* RxCocoa-Texture_Tests.xctest */, 146 | ); 147 | name = Products; 148 | sourceTree = ""; 149 | }; 150 | 607FACD21AFB9204008FA782 /* Example for RxCocoa-Texture */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 9B0E1640210DADBE0013EC86 /* viewModels */, 154 | 9B0E1631210DADA50013EC86 /* nodes */, 155 | 9B0E162C210DAD930013EC86 /* networks */, 156 | 9B0E1627210DAD820013EC86 /* models */, 157 | 9B0E1624210DAD730013EC86 /* extensions */, 158 | 9B0E161F210DAD640013EC86 /* dataProvider */, 159 | 9B0E161A210DAD4C0013EC86 /* controllers */, 160 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 161 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 162 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, 163 | 607FACD31AFB9204008FA782 /* Supporting Files */, 164 | ); 165 | name = "Example for RxCocoa-Texture"; 166 | path = "RxCocoa-Texture"; 167 | sourceTree = ""; 168 | }; 169 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 607FACD41AFB9204008FA782 /* Info.plist */, 173 | ); 174 | name = "Supporting Files"; 175 | sourceTree = ""; 176 | }; 177 | 607FACE81AFB9204008FA782 /* Tests */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 9B0E15F7210DA33F0013EC86 /* ASButtonNode+RxSpec.swift */, 181 | 9B0E15F8210DA33F0013EC86 /* ASControlNode+RxSpec.swift */, 182 | BA5756B7222CC72200D8CBD9 /* ASDisplayNode+RxSpec.swift */, 183 | 9B0E15FA210DA33F0013EC86 /* ASEditableTextNode+RxSpec.swift */, 184 | 9B0E15FC210DA33F0013EC86 /* ASImageNode+RxSpec.swift */, 185 | 9B0E15FB210DA33F0013EC86 /* ASNetworkImageNode+RxSpec.swift */, 186 | 9B0E15F9210DA33F0013EC86 /* ASTextNode+RxSpec.swift */, 187 | 9BB3ECFC22783FB7007CE252 /* BinderAndDriver+RxSpec.swift */, 188 | 9BB3ED01227842C5007CE252 /* ASTextNode2+RxSpec.swift */, 189 | 607FACE91AFB9204008FA782 /* Supporting Files */, 190 | ); 191 | path = Tests; 192 | sourceTree = ""; 193 | }; 194 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | 607FACEA1AFB9204008FA782 /* Info.plist */, 198 | ); 199 | name = "Supporting Files"; 200 | sourceTree = ""; 201 | }; 202 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 203 | isa = PBXGroup; 204 | children = ( 205 | 64EB5963EE48A355A0DC1490 /* RxCocoa-Texture.podspec */, 206 | 4850A96BAD26591DCD13654D /* README.md */, 207 | D34921410F2445031C571EB1 /* LICENSE */, 208 | ); 209 | name = "Podspec Metadata"; 210 | sourceTree = ""; 211 | }; 212 | 9B0E161A210DAD4C0013EC86 /* controllers */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | 9B0E161B210DAD540013EC86 /* RepositoryViewController.swift */, 216 | ); 217 | name = controllers; 218 | sourceTree = ""; 219 | }; 220 | 9B0E161F210DAD640013EC86 /* dataProvider */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | 9B0E1620210DAD6C0013EC86 /* RepoProvider.swift */, 224 | ); 225 | name = dataProvider; 226 | sourceTree = ""; 227 | }; 228 | 9B0E1624210DAD730013EC86 /* extensions */ = { 229 | isa = PBXGroup; 230 | children = ( 231 | 9B0E1625210DAD7B0013EC86 /* Decoder+extension.swift */, 232 | ); 233 | name = extensions; 234 | sourceTree = ""; 235 | }; 236 | 9B0E1627210DAD820013EC86 /* models */ = { 237 | isa = PBXGroup; 238 | children = ( 239 | 9B0E1628210DAD880013EC86 /* Repository.swift */, 240 | 9B0E1629210DAD890013EC86 /* User.swift */, 241 | ); 242 | name = models; 243 | sourceTree = ""; 244 | }; 245 | 9B0E162C210DAD930013EC86 /* networks */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | 9B0E162E210DAD9D0013EC86 /* Network.swift */, 249 | 9B0E162D210DAD9C0013EC86 /* RepoService.swift */, 250 | 9BE3DFE2211E780500816FD9 /* ImageDownloader.swift */, 251 | ); 252 | name = networks; 253 | sourceTree = ""; 254 | }; 255 | 9B0E1631210DADA50013EC86 /* nodes */ = { 256 | isa = PBXGroup; 257 | children = ( 258 | 9B0E1638210DADB70013EC86 /* ASBinderTestNode.swift */, 259 | 9B0E1635210DADB60013EC86 /* ButtonTestNode.swift */, 260 | 9B0E1636210DADB60013EC86 /* EditableTextTestNode.swift */, 261 | 9B0E1637210DADB60013EC86 /* ImageTestNode.swift */, 262 | 9B0E1634210DADB50013EC86 /* NetworkImageTestNode.swift */, 263 | 9B0E1632210DADB50013EC86 /* RepositoryListCellNode.swift */, 264 | 9B0E1633210DADB50013EC86 /* TextTestNode.swift */, 265 | ); 266 | name = nodes; 267 | sourceTree = ""; 268 | }; 269 | 9B0E1640210DADBE0013EC86 /* viewModels */ = { 270 | isa = PBXGroup; 271 | children = ( 272 | 9B0E1641210DADCE0013EC86 /* RepositoryViewModel.swift */, 273 | 9B6C23922124EB4200129E2F /* RepositoryViewModel2.swift */, 274 | ); 275 | name = viewModels; 276 | sourceTree = ""; 277 | }; 278 | DEF87B7647FEB19F408929F0 /* Frameworks */ = { 279 | isa = PBXGroup; 280 | children = ( 281 | B64F85DE41A757B8E1C3E68C /* Pods_RxCocoa_Texture_Example.framework */, 282 | 30C467A356AD9535ACE740C9 /* Pods_RxCocoa_Texture_Tests.framework */, 283 | ); 284 | name = Frameworks; 285 | sourceTree = ""; 286 | }; 287 | /* End PBXGroup section */ 288 | 289 | /* Begin PBXNativeTarget section */ 290 | 607FACCF1AFB9204008FA782 /* RxCocoa-Texture_Example */ = { 291 | isa = PBXNativeTarget; 292 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "RxCocoa-Texture_Example" */; 293 | buildPhases = ( 294 | 8813B7A7B288A002A2FB4B40 /* [CP] Check Pods Manifest.lock */, 295 | 607FACCC1AFB9204008FA782 /* Sources */, 296 | 607FACCD1AFB9204008FA782 /* Frameworks */, 297 | 607FACCE1AFB9204008FA782 /* Resources */, 298 | EA022F805800BF651B2CBFA1 /* [CP] Embed Pods Frameworks */, 299 | ); 300 | buildRules = ( 301 | ); 302 | dependencies = ( 303 | ); 304 | name = "RxCocoa-Texture_Example"; 305 | productName = "RxCocoa-Texture"; 306 | productReference = 607FACD01AFB9204008FA782 /* RxCocoa-Texture_Example.app */; 307 | productType = "com.apple.product-type.application"; 308 | }; 309 | 607FACE41AFB9204008FA782 /* RxCocoa-Texture_Tests */ = { 310 | isa = PBXNativeTarget; 311 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "RxCocoa-Texture_Tests" */; 312 | buildPhases = ( 313 | 1BFBDB7C8D81C13C5C0F0050 /* [CP] Check Pods Manifest.lock */, 314 | 607FACE11AFB9204008FA782 /* Sources */, 315 | 607FACE21AFB9204008FA782 /* Frameworks */, 316 | 607FACE31AFB9204008FA782 /* Resources */, 317 | D970F5DD18A8BCB1792CB2DC /* [CP] Embed Pods Frameworks */, 318 | ); 319 | buildRules = ( 320 | ); 321 | dependencies = ( 322 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */, 323 | ); 324 | name = "RxCocoa-Texture_Tests"; 325 | productName = Tests; 326 | productReference = 607FACE51AFB9204008FA782 /* RxCocoa-Texture_Tests.xctest */; 327 | productType = "com.apple.product-type.bundle.unit-test"; 328 | }; 329 | /* End PBXNativeTarget section */ 330 | 331 | /* Begin PBXProject section */ 332 | 607FACC81AFB9204008FA782 /* Project object */ = { 333 | isa = PBXProject; 334 | attributes = { 335 | LastSwiftUpdateCheck = 0830; 336 | LastUpgradeCheck = 1000; 337 | ORGANIZATIONNAME = CocoaPods; 338 | TargetAttributes = { 339 | 607FACCF1AFB9204008FA782 = { 340 | CreatedOnToolsVersion = 6.3.1; 341 | DevelopmentTeam = JTEXWEH4CZ; 342 | LastSwiftMigration = 0900; 343 | ProvisioningStyle = Automatic; 344 | }; 345 | 607FACE41AFB9204008FA782 = { 346 | CreatedOnToolsVersion = 6.3.1; 347 | DevelopmentTeam = JTEXWEH4CZ; 348 | LastSwiftMigration = 0900; 349 | TestTargetID = 607FACCF1AFB9204008FA782; 350 | }; 351 | }; 352 | }; 353 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "RxCocoa-Texture" */; 354 | compatibilityVersion = "Xcode 3.2"; 355 | developmentRegion = English; 356 | hasScannedForEncodings = 0; 357 | knownRegions = ( 358 | English, 359 | en, 360 | Base, 361 | ); 362 | mainGroup = 607FACC71AFB9204008FA782; 363 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 364 | projectDirPath = ""; 365 | projectRoot = ""; 366 | targets = ( 367 | 607FACCF1AFB9204008FA782 /* RxCocoa-Texture_Example */, 368 | 607FACE41AFB9204008FA782 /* RxCocoa-Texture_Tests */, 369 | ); 370 | }; 371 | /* End PBXProject section */ 372 | 373 | /* Begin PBXResourcesBuildPhase section */ 374 | 607FACCE1AFB9204008FA782 /* Resources */ = { 375 | isa = PBXResourcesBuildPhase; 376 | buildActionMask = 2147483647; 377 | files = ( 378 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, 379 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 380 | ); 381 | runOnlyForDeploymentPostprocessing = 0; 382 | }; 383 | 607FACE31AFB9204008FA782 /* Resources */ = { 384 | isa = PBXResourcesBuildPhase; 385 | buildActionMask = 2147483647; 386 | files = ( 387 | ); 388 | runOnlyForDeploymentPostprocessing = 0; 389 | }; 390 | /* End PBXResourcesBuildPhase section */ 391 | 392 | /* Begin PBXShellScriptBuildPhase section */ 393 | 1BFBDB7C8D81C13C5C0F0050 /* [CP] Check Pods Manifest.lock */ = { 394 | isa = PBXShellScriptBuildPhase; 395 | buildActionMask = 2147483647; 396 | files = ( 397 | ); 398 | inputPaths = ( 399 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 400 | "${PODS_ROOT}/Manifest.lock", 401 | ); 402 | name = "[CP] Check Pods Manifest.lock"; 403 | outputPaths = ( 404 | "$(DERIVED_FILE_DIR)/Pods-RxCocoa-Texture_Tests-checkManifestLockResult.txt", 405 | ); 406 | runOnlyForDeploymentPostprocessing = 0; 407 | shellPath = /bin/sh; 408 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 409 | showEnvVarsInLog = 0; 410 | }; 411 | 8813B7A7B288A002A2FB4B40 /* [CP] Check Pods Manifest.lock */ = { 412 | isa = PBXShellScriptBuildPhase; 413 | buildActionMask = 2147483647; 414 | files = ( 415 | ); 416 | inputPaths = ( 417 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 418 | "${PODS_ROOT}/Manifest.lock", 419 | ); 420 | name = "[CP] Check Pods Manifest.lock"; 421 | outputPaths = ( 422 | "$(DERIVED_FILE_DIR)/Pods-RxCocoa-Texture_Example-checkManifestLockResult.txt", 423 | ); 424 | runOnlyForDeploymentPostprocessing = 0; 425 | shellPath = /bin/sh; 426 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 427 | showEnvVarsInLog = 0; 428 | }; 429 | D970F5DD18A8BCB1792CB2DC /* [CP] Embed Pods Frameworks */ = { 430 | isa = PBXShellScriptBuildPhase; 431 | buildActionMask = 2147483647; 432 | files = ( 433 | ); 434 | inputPaths = ( 435 | "${PODS_ROOT}/Target Support Files/Pods-RxCocoa-Texture_Tests/Pods-RxCocoa-Texture_Tests-frameworks.sh", 436 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", 437 | "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", 438 | "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", 439 | "${BUILT_PRODUCTS_DIR}/RxBlocking/RxBlocking.framework", 440 | "${BUILT_PRODUCTS_DIR}/RxTest/RxTest.framework", 441 | ); 442 | name = "[CP] Embed Pods Frameworks"; 443 | outputPaths = ( 444 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", 445 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", 446 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", 447 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxBlocking.framework", 448 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxTest.framework", 449 | ); 450 | runOnlyForDeploymentPostprocessing = 0; 451 | shellPath = /bin/sh; 452 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RxCocoa-Texture_Tests/Pods-RxCocoa-Texture_Tests-frameworks.sh\"\n"; 453 | showEnvVarsInLog = 0; 454 | }; 455 | EA022F805800BF651B2CBFA1 /* [CP] Embed Pods Frameworks */ = { 456 | isa = PBXShellScriptBuildPhase; 457 | buildActionMask = 2147483647; 458 | files = ( 459 | ); 460 | inputPaths = ( 461 | "${PODS_ROOT}/Target Support Files/Pods-RxCocoa-Texture_Example/Pods-RxCocoa-Texture_Example-frameworks.sh", 462 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", 463 | "${BUILT_PRODUCTS_DIR}/PINCache/PINCache.framework", 464 | "${BUILT_PRODUCTS_DIR}/PINOperation/PINOperation.framework", 465 | "${BUILT_PRODUCTS_DIR}/PINRemoteImage/PINRemoteImage.framework", 466 | "${BUILT_PRODUCTS_DIR}/RxAlamofire/RxAlamofire.framework", 467 | "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", 468 | "${BUILT_PRODUCTS_DIR}/RxCocoa-Texture/RxCocoa_Texture.framework", 469 | "${BUILT_PRODUCTS_DIR}/RxRelay/RxRelay.framework", 470 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", 471 | "${BUILT_PRODUCTS_DIR}/Texture/AsyncDisplayKit.framework", 472 | ); 473 | name = "[CP] Embed Pods Frameworks"; 474 | outputPaths = ( 475 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", 476 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PINCache.framework", 477 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PINOperation.framework", 478 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PINRemoteImage.framework", 479 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxAlamofire.framework", 480 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", 481 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa_Texture.framework", 482 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxRelay.framework", 483 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", 484 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AsyncDisplayKit.framework", 485 | ); 486 | runOnlyForDeploymentPostprocessing = 0; 487 | shellPath = /bin/sh; 488 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RxCocoa-Texture_Example/Pods-RxCocoa-Texture_Example-frameworks.sh\"\n"; 489 | showEnvVarsInLog = 0; 490 | }; 491 | /* End PBXShellScriptBuildPhase section */ 492 | 493 | /* Begin PBXSourcesBuildPhase section */ 494 | 607FACCC1AFB9204008FA782 /* Sources */ = { 495 | isa = PBXSourcesBuildPhase; 496 | buildActionMask = 2147483647; 497 | files = ( 498 | 9B0E1630210DAD9D0013EC86 /* Network.swift in Sources */, 499 | 9B6C23932124EB4200129E2F /* RepositoryViewModel2.swift in Sources */, 500 | 9B0E162A210DAD890013EC86 /* Repository.swift in Sources */, 501 | 9B0E1626210DAD7B0013EC86 /* Decoder+extension.swift in Sources */, 502 | 9B0E161D210DAD550013EC86 /* RepositoryViewController.swift in Sources */, 503 | 9B0E1639210DADB70013EC86 /* RepositoryListCellNode.swift in Sources */, 504 | 9B0E163F210DADB70013EC86 /* ASBinderTestNode.swift in Sources */, 505 | 9B0E162B210DAD890013EC86 /* User.swift in Sources */, 506 | 9BE3DFE3211E780600816FD9 /* ImageDownloader.swift in Sources */, 507 | 9B0E163E210DADB70013EC86 /* ImageTestNode.swift in Sources */, 508 | 9B0E163B210DADB70013EC86 /* NetworkImageTestNode.swift in Sources */, 509 | 9B0E163D210DADB70013EC86 /* EditableTextTestNode.swift in Sources */, 510 | 9B0E163A210DADB70013EC86 /* TextTestNode.swift in Sources */, 511 | 9B0E162F210DAD9D0013EC86 /* RepoService.swift in Sources */, 512 | 9B0E1642210DADCF0013EC86 /* RepositoryViewModel.swift in Sources */, 513 | 9B0E1622210DAD6E0013EC86 /* RepoProvider.swift in Sources */, 514 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, 515 | 9B0E163C210DADB70013EC86 /* ButtonTestNode.swift in Sources */, 516 | ); 517 | runOnlyForDeploymentPostprocessing = 0; 518 | }; 519 | 607FACE11AFB9204008FA782 /* Sources */ = { 520 | isa = PBXSourcesBuildPhase; 521 | buildActionMask = 2147483647; 522 | files = ( 523 | BA5756B9222CC72D00D8CBD9 /* ASDisplayNode+RxSpec.swift in Sources */, 524 | 9B0E1607210DA3E80013EC86 /* ASEditableTextNode+RxSpec.swift in Sources */, 525 | 9BB3ECFE22784059007CE252 /* BinderAndDriver+RxSpec.swift in Sources */, 526 | 9B0E1606210DA3E50013EC86 /* ASControlNode+RxSpec.swift in Sources */, 527 | 9B0E1603210DA3CE0013EC86 /* ASImageNode+RxSpec.swift in Sources */, 528 | 9BB3ED02227842C5007CE252 /* ASTextNode2+RxSpec.swift in Sources */, 529 | 9B0E1609210DA3F00013EC86 /* ASTextNode+RxSpec.swift in Sources */, 530 | 9B0E1605210DA3E30013EC86 /* ASButtonNode+RxSpec.swift in Sources */, 531 | 9B0E1608210DA3ED0013EC86 /* ASNetworkImageNode+RxSpec.swift in Sources */, 532 | ); 533 | runOnlyForDeploymentPostprocessing = 0; 534 | }; 535 | /* End PBXSourcesBuildPhase section */ 536 | 537 | /* Begin PBXTargetDependency section */ 538 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { 539 | isa = PBXTargetDependency; 540 | target = 607FACCF1AFB9204008FA782 /* RxCocoa-Texture_Example */; 541 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; 542 | }; 543 | /* End PBXTargetDependency section */ 544 | 545 | /* Begin PBXVariantGroup section */ 546 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { 547 | isa = PBXVariantGroup; 548 | children = ( 549 | 607FACDF1AFB9204008FA782 /* Base */, 550 | ); 551 | name = LaunchScreen.xib; 552 | sourceTree = ""; 553 | }; 554 | /* End PBXVariantGroup section */ 555 | 556 | /* Begin XCBuildConfiguration section */ 557 | 607FACED1AFB9204008FA782 /* Debug */ = { 558 | isa = XCBuildConfiguration; 559 | buildSettings = { 560 | ALWAYS_SEARCH_USER_PATHS = NO; 561 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 562 | CLANG_CXX_LIBRARY = "libc++"; 563 | CLANG_ENABLE_MODULES = YES; 564 | CLANG_ENABLE_OBJC_ARC = YES; 565 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 566 | CLANG_WARN_BOOL_CONVERSION = YES; 567 | CLANG_WARN_COMMA = YES; 568 | CLANG_WARN_CONSTANT_CONVERSION = YES; 569 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 570 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 571 | CLANG_WARN_EMPTY_BODY = YES; 572 | CLANG_WARN_ENUM_CONVERSION = YES; 573 | CLANG_WARN_INFINITE_RECURSION = YES; 574 | CLANG_WARN_INT_CONVERSION = YES; 575 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 576 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 577 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 578 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 579 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 580 | CLANG_WARN_STRICT_PROTOTYPES = YES; 581 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 582 | CLANG_WARN_UNREACHABLE_CODE = YES; 583 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 584 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 585 | COPY_PHASE_STRIP = NO; 586 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 587 | ENABLE_STRICT_OBJC_MSGSEND = YES; 588 | ENABLE_TESTABILITY = YES; 589 | GCC_C_LANGUAGE_STANDARD = gnu99; 590 | GCC_DYNAMIC_NO_PIC = NO; 591 | GCC_NO_COMMON_BLOCKS = YES; 592 | GCC_OPTIMIZATION_LEVEL = 0; 593 | GCC_PREPROCESSOR_DEFINITIONS = ( 594 | "DEBUG=1", 595 | "$(inherited)", 596 | ); 597 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 598 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 599 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 600 | GCC_WARN_UNDECLARED_SELECTOR = YES; 601 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 602 | GCC_WARN_UNUSED_FUNCTION = YES; 603 | GCC_WARN_UNUSED_VARIABLE = YES; 604 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 605 | MTL_ENABLE_DEBUG_INFO = YES; 606 | ONLY_ACTIVE_ARCH = YES; 607 | SDKROOT = iphoneos; 608 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 609 | SWIFT_VERSION = 4.0; 610 | }; 611 | name = Debug; 612 | }; 613 | 607FACEE1AFB9204008FA782 /* Release */ = { 614 | isa = XCBuildConfiguration; 615 | buildSettings = { 616 | ALWAYS_SEARCH_USER_PATHS = NO; 617 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 618 | CLANG_CXX_LIBRARY = "libc++"; 619 | CLANG_ENABLE_MODULES = YES; 620 | CLANG_ENABLE_OBJC_ARC = YES; 621 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 622 | CLANG_WARN_BOOL_CONVERSION = YES; 623 | CLANG_WARN_COMMA = YES; 624 | CLANG_WARN_CONSTANT_CONVERSION = YES; 625 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 626 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 627 | CLANG_WARN_EMPTY_BODY = YES; 628 | CLANG_WARN_ENUM_CONVERSION = YES; 629 | CLANG_WARN_INFINITE_RECURSION = YES; 630 | CLANG_WARN_INT_CONVERSION = YES; 631 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 632 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 633 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 634 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 635 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 636 | CLANG_WARN_STRICT_PROTOTYPES = YES; 637 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 638 | CLANG_WARN_UNREACHABLE_CODE = YES; 639 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 640 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 641 | COPY_PHASE_STRIP = NO; 642 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 643 | ENABLE_NS_ASSERTIONS = NO; 644 | ENABLE_STRICT_OBJC_MSGSEND = YES; 645 | GCC_C_LANGUAGE_STANDARD = gnu99; 646 | GCC_NO_COMMON_BLOCKS = YES; 647 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 648 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 649 | GCC_WARN_UNDECLARED_SELECTOR = YES; 650 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 651 | GCC_WARN_UNUSED_FUNCTION = YES; 652 | GCC_WARN_UNUSED_VARIABLE = YES; 653 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 654 | MTL_ENABLE_DEBUG_INFO = NO; 655 | SDKROOT = iphoneos; 656 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 657 | SWIFT_VERSION = 4.0; 658 | VALIDATE_PRODUCT = YES; 659 | }; 660 | name = Release; 661 | }; 662 | 607FACF01AFB9204008FA782 /* Debug */ = { 663 | isa = XCBuildConfiguration; 664 | baseConfigurationReference = F4C2FBFBDA0CE9B43659AD60 /* Pods-RxCocoa-Texture_Example.debug.xcconfig */; 665 | buildSettings = { 666 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 667 | CODE_SIGN_IDENTITY = "iPhone Developer"; 668 | CODE_SIGN_STYLE = Automatic; 669 | DEVELOPMENT_TEAM = JTEXWEH4CZ; 670 | INFOPLIST_FILE = "RxCocoa-Texture/Info.plist"; 671 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 672 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 673 | MODULE_NAME = ExampleApp; 674 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 675 | PRODUCT_NAME = "$(TARGET_NAME)"; 676 | PROVISIONING_PROFILE_SPECIFIER = ""; 677 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 678 | SWIFT_VERSION = 5.0; 679 | }; 680 | name = Debug; 681 | }; 682 | 607FACF11AFB9204008FA782 /* Release */ = { 683 | isa = XCBuildConfiguration; 684 | baseConfigurationReference = A700DE1AE94F129E06542938 /* Pods-RxCocoa-Texture_Example.release.xcconfig */; 685 | buildSettings = { 686 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 687 | CODE_SIGN_IDENTITY = "iPhone Developer"; 688 | CODE_SIGN_STYLE = Automatic; 689 | DEVELOPMENT_TEAM = JTEXWEH4CZ; 690 | INFOPLIST_FILE = "RxCocoa-Texture/Info.plist"; 691 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 692 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 693 | MODULE_NAME = ExampleApp; 694 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 695 | PRODUCT_NAME = "$(TARGET_NAME)"; 696 | PROVISIONING_PROFILE_SPECIFIER = ""; 697 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 698 | SWIFT_VERSION = 5.0; 699 | }; 700 | name = Release; 701 | }; 702 | 607FACF31AFB9204008FA782 /* Debug */ = { 703 | isa = XCBuildConfiguration; 704 | baseConfigurationReference = 9E1C79DA561D40B89763472E /* Pods-RxCocoa-Texture_Tests.debug.xcconfig */; 705 | buildSettings = { 706 | DEVELOPMENT_TEAM = JTEXWEH4CZ; 707 | FRAMEWORK_SEARCH_PATHS = ( 708 | "$(SDKROOT)/Developer/Library/Frameworks", 709 | "$(inherited)", 710 | ); 711 | GCC_PREPROCESSOR_DEFINITIONS = ( 712 | "DEBUG=1", 713 | "$(inherited)", 714 | ); 715 | INFOPLIST_FILE = Tests/Info.plist; 716 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 717 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 718 | PRODUCT_NAME = "$(TARGET_NAME)"; 719 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 720 | SWIFT_VERSION = 5.0; 721 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxCocoa-Texture_Example.app/RxCocoa-Texture_Example"; 722 | }; 723 | name = Debug; 724 | }; 725 | 607FACF41AFB9204008FA782 /* Release */ = { 726 | isa = XCBuildConfiguration; 727 | baseConfigurationReference = CB878FDCA4BF09167F15A871 /* Pods-RxCocoa-Texture_Tests.release.xcconfig */; 728 | buildSettings = { 729 | DEVELOPMENT_TEAM = JTEXWEH4CZ; 730 | FRAMEWORK_SEARCH_PATHS = ( 731 | "$(SDKROOT)/Developer/Library/Frameworks", 732 | "$(inherited)", 733 | ); 734 | INFOPLIST_FILE = Tests/Info.plist; 735 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 736 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 737 | PRODUCT_NAME = "$(TARGET_NAME)"; 738 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 739 | SWIFT_VERSION = 5.0; 740 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxCocoa-Texture_Example.app/RxCocoa-Texture_Example"; 741 | }; 742 | name = Release; 743 | }; 744 | /* End XCBuildConfiguration section */ 745 | 746 | /* Begin XCConfigurationList section */ 747 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "RxCocoa-Texture" */ = { 748 | isa = XCConfigurationList; 749 | buildConfigurations = ( 750 | 607FACED1AFB9204008FA782 /* Debug */, 751 | 607FACEE1AFB9204008FA782 /* Release */, 752 | ); 753 | defaultConfigurationIsVisible = 0; 754 | defaultConfigurationName = Release; 755 | }; 756 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "RxCocoa-Texture_Example" */ = { 757 | isa = XCConfigurationList; 758 | buildConfigurations = ( 759 | 607FACF01AFB9204008FA782 /* Debug */, 760 | 607FACF11AFB9204008FA782 /* Release */, 761 | ); 762 | defaultConfigurationIsVisible = 0; 763 | defaultConfigurationName = Release; 764 | }; 765 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "RxCocoa-Texture_Tests" */ = { 766 | isa = XCConfigurationList; 767 | buildConfigurations = ( 768 | 607FACF31AFB9204008FA782 /* Debug */, 769 | 607FACF41AFB9204008FA782 /* Release */, 770 | ); 771 | defaultConfigurationIsVisible = 0; 772 | defaultConfigurationName = Release; 773 | }; 774 | /* End XCConfigurationList section */ 775 | }; 776 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 777 | } 778 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture.xcodeproj/xcshareddata/xcschemes/RxCocoa-Texture-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/ASBinderTestNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASBinderTestNode.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import AsyncDisplayKit 10 | import RxCocoa_Texture 11 | import RxSwift 12 | import RxCocoa 13 | 14 | class ASBinderTestNode: ASDisplayNode { 15 | 16 | let textNode = ASTextNode() 17 | let imageNode = ASNetworkImageNode() 18 | 19 | let textNode2 = ASTextNode() 20 | let imageNode2 = ASNetworkImageNode() 21 | 22 | let disposeBag = DisposeBag() 23 | let url = URL(string: "https://koreaboo-cdn.storage.googleapis.com/2017/08/sana-1-1.jpg") 24 | 25 | override init() { 26 | super.init() 27 | self.automaticallyManagesSubnodes = true 28 | 29 | Observable.just(url) 30 | .bind(to: imageNode.rx.url, setNeedsLayout: self) 31 | .disposed(by: disposeBag) 32 | 33 | Observable.just(Optional("optional text")) 34 | .bind(to: textNode.rx.text(nil), setNeedsLayout: self) 35 | .disposed(by: disposeBag) 36 | 37 | Driver.just(url) 38 | .drive(imageNode2.rx.url, setNeedsLayout: self) 39 | .disposed(by: disposeBag) 40 | 41 | Driver.just(Optional("optional test")) 42 | .drive(textNode2.rx.text(nil), setNeedsLayout: self) 43 | .disposed(by: disposeBag) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | @UIApplicationMain 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | window = UIWindow(frame: UIScreen.main.bounds) // create UIwindow 18 | let viewController = RepositoryViewController() 19 | let navigationController = 20 | UINavigationController(rootViewController: viewController) 21 | if let window = window { 22 | window.rootViewController = navigationController 23 | window.makeKeyAndVisible() 24 | } 25 | 26 | return true 27 | } 28 | 29 | func applicationWillResignActive(_ application: UIApplication) { 30 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 31 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 32 | } 33 | 34 | func applicationDidEnterBackground(_ application: UIApplication) { 35 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 36 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 37 | } 38 | 39 | func applicationWillEnterForeground(_ application: UIApplication) { 40 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 41 | } 42 | 43 | func applicationDidBecomeActive(_ application: UIApplication) { 44 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 45 | } 46 | 47 | func applicationWillTerminate(_ application: UIApplication) { 48 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/ButtonTestNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonTestNode.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | 9 | import Foundation 10 | import AsyncDisplayKit 11 | import RxSwift 12 | import RxCocoa 13 | import RxCocoa_Texture 14 | 15 | class ButtonTestNode: ASDisplayNode { 16 | 17 | /** ASButtonNode is ASControlNode subclass, 18 | so, you just use ASControlNode+RxExtension functions 19 | ref: ASControlNode+RxExtension 20 | **/ 21 | lazy var buttonNode = ASButtonNode() 22 | let disposeBag = DisposeBag() 23 | let url = URL(string: "https://koreaboo-cdn.storage.googleapis.com/2017/08/sana-1-1.jpg") 24 | let touchRelay = PublishRelay() 25 | 26 | let defaultAttr = [NSAttributedString.Key.foregroundColor: UIColor.black] 27 | let disableAttr = [NSAttributedString.Key.foregroundColor: UIColor.gray] 28 | 29 | override init() { 30 | super.init() 31 | 32 | // tap event (ref: RxCocoa UIButton+Rx) 33 | buttonNode.rx.tap 34 | .subscribe() 35 | .disposed(by: disposeBag) 36 | 37 | // binding relay about tap event 38 | buttonNode.rx.tap(to: touchRelay) 39 | .disposed(by: disposeBag) 40 | 41 | // custom touch event 42 | buttonNode.rx.controlEvent(.touchUpInside) 43 | .subscribe() 44 | .disposed(by: disposeBag) 45 | 46 | Observable.just("you can bind text with attribute") 47 | .bind(to: buttonNode.rx.text(defaultAttr)) 48 | .disposed(by: disposeBag) 49 | 50 | Observable.just("you can bind text with attribute and targeted control state") 51 | .bind(to: buttonNode.rx.text(disableAttr, target: .disabled)) 52 | .disposed(by: disposeBag) 53 | 54 | Observable.just(NSAttributedString(string: "attributedText too")) 55 | .bind(to: buttonNode.rx.attributedText) 56 | .disposed(by: disposeBag) 57 | 58 | Observable.just(NSAttributedString(string: "attributedText too")) 59 | .bind(to: buttonNode.rx.attributedText(.disabled)) 60 | .disposed(by: disposeBag) 61 | 62 | Observable.just("you can apply text to various control state") 63 | .bind(to: buttonNode.rx 64 | .text(applyList: [.normal(defaultAttr), 65 | .disabled(disableAttr)])) 66 | .disposed(by: disposeBag) 67 | 68 | // set image 69 | Observable.just(#imageLiteral(resourceName: "texture_icon")) 70 | .bind(to: buttonNode.rx.image) 71 | .disposed(by: disposeBag) 72 | 73 | // set backgroundImage 74 | Observable.just(#imageLiteral(resourceName: "texture_icon")) 75 | .bind(to: buttonNode.rx.backgroundImage) 76 | .disposed(by: disposeBag) 77 | 78 | // set targeted control state image 79 | Observable.just(#imageLiteral(resourceName: "texture_icon")) 80 | .bind(to: buttonNode.rx 81 | .image(applyList: [.normal(nil), 82 | .disabled(#imageLiteral(resourceName: "Texture"))])) 83 | .disposed(by: disposeBag) 84 | 85 | // set targeted control state background-image 86 | Observable.just(#imageLiteral(resourceName: "texture_icon")) 87 | .bind(to: buttonNode.rx 88 | .backgroundImage(applyList: [.normal(nil), 89 | .disabled(#imageLiteral(resourceName: "texture_icon"))])) 90 | .disposed(by: disposeBag) 91 | 92 | 93 | Observable.just(false) 94 | .bind(to: buttonNode.rx.isHidden) 95 | .disposed(by: disposeBag) 96 | 97 | Observable.just(false) 98 | .bind(to: buttonNode.rx.isEnabled) 99 | .disposed(by: disposeBag) 100 | 101 | Observable.just(false) 102 | .bind(to: buttonNode.rx.isHighlighted) 103 | .disposed(by: disposeBag) 104 | 105 | Observable.just(false) 106 | .bind(to: buttonNode.rx.isSelected) 107 | .disposed(by: disposeBag) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/Decoder+extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Decoder+extension.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | extension PrimitiveSequence where Element == Data { 12 | // .generateArrayModel(type: MODEL_CLASS_NAME.self).subscribe ... TODO 13 | func generateArrayModel() -> Single<[T]> { 14 | return self.asObservable() 15 | .flatMap({ data -> Observable<[T]> in 16 | let array = try? JSONDecoder().decode([T].self, from: data) 17 | return Observable.just(array ?? []) 18 | }) 19 | .asSingle() 20 | } 21 | 22 | func generateObjectModel() -> Single { 23 | return self.asObservable() 24 | .flatMap({ data -> Observable in 25 | let object = try? JSONDecoder().decode(T.self, from: data) 26 | return Observable.just(object ?? nil) 27 | }) 28 | .asSingle() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/EditableTextTestNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestTestNode.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import AsyncDisplayKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxCocoa_Texture 13 | 14 | class EditableTextTestNode: ASDisplayNode { 15 | 16 | lazy var textNode = ASEditableTextNode() 17 | let disposeBag = DisposeBag() 18 | static let attribute = [NSAttributedString.Key.foregroundColor: UIColor.gray] 19 | 20 | override init() { 21 | super.init() 22 | 23 | Observable.just("text") 24 | .bind(to: textNode.rx.text(TextTestNode.attribute)) 25 | .disposed(by: disposeBag) 26 | 27 | Observable.just("text") 28 | .map { NSAttributedString(string: $0, attributes: TextTestNode.attribute) } 29 | .bind(to: textNode.rx.attributedText) 30 | .disposed(by: disposeBag) 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/ImageDownloader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // imageDownloader.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxCocoa 11 | 12 | struct ImageDownloader { 13 | 14 | // fake image download 15 | static func download() -> Observable { 16 | return Observable.create({ operation in 17 | let url = URL(string: "https://avatars1.githubusercontent.com/u/19504988?s=460&v=4") 18 | DispatchQueue.global().async { 19 | operation.onNext(url) 20 | operation.onCompleted() 21 | } 22 | return Disposables.create() 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/ImageTestNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageTestNode.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import AsyncDisplayKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxCocoa_Texture 13 | 14 | class ImageTestNode: ASDisplayNode { 15 | 16 | /** ASImageNode is ASControlNode subclass, 17 | so, you just use ASControlNode+RxExtension functions 18 | ref: ASControlNode+RxExtension 19 | **/ 20 | 21 | lazy var imageNode = ASImageNode() 22 | let disposeBag = DisposeBag() 23 | 24 | override init() { 25 | super.init() 26 | 27 | Observable.just(#imageLiteral(resourceName: "Texture")) 28 | .bind(to: imageNode.rx.image) 29 | .disposed(by: disposeBag) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/Images.xcassets/texture_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "texture_icon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/Images.xcassets/texture_icon.imageset/texture_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxSwiftCommunity/RxCocoa-Texture/ced5b7b183587282f073dafbb41699dc464378ae/Example/RxCocoa-Texture/Images.xcassets/texture_icon.imageset/texture_icon.png -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/Network.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Network.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import RxAlamofire 10 | import Alamofire 11 | import RxSwift 12 | import RxCocoa 13 | 14 | class Network { 15 | 16 | static let shared = Network() 17 | 18 | func get(url: String, params: [String: Any]?) -> Single { 19 | return Observable.create({ operation in 20 | do { 21 | let convertedURL = try url.asURL() 22 | 23 | let nextHandler: (HTTPURLResponse, Any) -> Void = { res, data in 24 | do { 25 | let rawData = try JSONSerialization.data(withJSONObject: data, options: []) 26 | operation.onNext(rawData) 27 | operation.onCompleted() 28 | } catch { 29 | let error = NSError(domain: "failed JSONSerialization", 30 | code: 0, 31 | userInfo: nil) 32 | operation.onError(error) 33 | } 34 | } 35 | 36 | let errorHandler: (Error) -> Void = { error in 37 | operation.onError(error) 38 | } 39 | 40 | 41 | _ = RxAlamofire.requestJSON(.get, 42 | convertedURL, 43 | parameters: params, 44 | encoding: URLEncoding.default, 45 | headers: nil) 46 | .subscribe(onNext: nextHandler, 47 | onError: errorHandler) 48 | 49 | } catch { 50 | let error = NSError(domain: "failed convert url", 51 | code: 0, 52 | userInfo: nil) 53 | operation.onError(error) 54 | } 55 | 56 | return Disposables.create() 57 | }).asSingle() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/NetworkImageTestNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkImageTestNode.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import AsyncDisplayKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxCocoa_Texture 13 | 14 | class NetworkImageTestNode: ASDisplayNode { 15 | 16 | /** ASNEtworkImageNode is ASControlNode subclass, 17 | so, you just use ASControlNode+RxExtension functions 18 | ref: ASControlNode+RxExtension 19 | **/ 20 | 21 | lazy var imageNode = ASNetworkImageNode() 22 | let disposeBag = DisposeBag() 23 | let url = URL(string: "https://koreaboo-cdn.storage.googleapis.com/2017/08/sana-1-1.jpg") 24 | 25 | override init() { 26 | super.init() 27 | 28 | Observable.just(url) 29 | .bind(to: imageNode.rx.url) 30 | .disposed(by: disposeBag) 31 | 32 | Observable.just(url) 33 | .bind(to: imageNode.rx.url(resetToDefault: false)) 34 | .disposed(by: disposeBag) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/RepoProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RepoProvider.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxCocoa 11 | 12 | struct RepoProvider { 13 | private static let repoRelay = BehaviorRelay<[Int: (repo: Repository, count: Int, updatedAt: Date)]>(value: [:]) 14 | private static let repoObservable = repoRelay 15 | .asObservable() 16 | .subscribeOn(SerialDispatchQueueScheduler(queue: queue, internalSerialQueueName: UUID().uuidString)) 17 | .share(replay: 1, scope: .whileConnected) 18 | private static let queue = DispatchQueue(label: "RepoProvider.RxMVVMTexture.com", qos: .utility) 19 | 20 | static func addAndUpdate(_ repo: Repository) { 21 | queue.async { 22 | var repoValue = self.repoRelay.value 23 | if let record = repoValue[repo.id] { 24 | record.repo.merge(repo) 25 | repoValue[repo.id] = (repo: record.repo, count: record.count + 1, updatedAt: Date()) 26 | } else { 27 | repoValue[repo.id] = (repo: repo, count: 1, updatedAt: Date()) 28 | } 29 | self.repoRelay.accept(repoValue) 30 | } 31 | } 32 | 33 | 34 | static func update(_ repo: Repository) { 35 | queue.async { 36 | var repoValue = self.repoRelay.value 37 | if let record = repoValue[repo.id] { 38 | record.repo.merge(repo) 39 | repoValue[repo.id] = (repo: record.repo, count: record.count, updatedAt: Date()) 40 | } 41 | self.repoRelay.accept(repoValue) 42 | } 43 | } 44 | 45 | static func retain(id: Int) { 46 | queue.async { 47 | var repoValue = self.repoRelay.value 48 | var record = repoValue[id] 49 | guard record != nil else { return } 50 | 51 | record?.count += 1 52 | repoValue[id] = record 53 | self.repoRelay.accept(repoValue) 54 | } 55 | } 56 | 57 | static func release(id: Int) { 58 | queue.async { 59 | var repoValue = self.repoRelay.value 60 | var record = repoValue[id] 61 | guard record != nil else { return } 62 | 63 | record?.count -= 1 64 | if record?.count ?? 0 < 1 { 65 | record = nil 66 | } 67 | repoValue[id] = record 68 | self.repoRelay.accept(repoValue) 69 | } 70 | } 71 | 72 | static func repo(id: Int) -> Repository? { 73 | var repo: Repository? 74 | queue.sync { 75 | repo = self.repoRelay.value[id]?.repo 76 | } 77 | return repo 78 | } 79 | 80 | static func observable(id: Int) -> Observable { 81 | return repoObservable 82 | .map { $0[id] } 83 | .distinctUntilChanged { $0?.updatedAt == $1?.updatedAt } 84 | .map { $0?.repo } 85 | .share(replay: 1, scope: .whileConnected) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/RepoService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RepoService.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxCocoa 11 | 12 | class RepoService { 13 | enum Route { 14 | case basePath 15 | 16 | var path: String { 17 | let base = "https://api.github.com/repositories" 18 | 19 | switch self { 20 | case .basePath: return base 21 | } 22 | } 23 | 24 | enum Params { 25 | case since(Int?) 26 | 27 | var key: String { 28 | switch self { 29 | case .since: return "since" 30 | } 31 | } 32 | 33 | var value: Any? { 34 | switch self { 35 | case .since(let value): return value 36 | } 37 | } 38 | } 39 | 40 | static func parameters(_ params: [Params]?) -> [String: Any]? { 41 | guard let `params` = params else { return nil } 42 | var result: [String: Any] = [:] 43 | 44 | for param in params { 45 | result[param.key] = param.value 46 | } 47 | 48 | return result.isEmpty ? nil: result 49 | } 50 | } 51 | } 52 | 53 | extension RepoService { 54 | static func loadRepository(params: [RepoService.Route.Params]?) -> Single<[Repository]> { 55 | return Network.shared.get(url: Route.basePath.path, 56 | params: Route.parameters(params)) 57 | .generateArrayModel() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/Repository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Repository.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import RxCocoa_Texture 10 | 11 | class Repository: Decodable { 12 | var id: Int = -1 13 | var user: User? 14 | var repositoryName: String? 15 | var desc: String? 16 | var isPrivate: Bool = false 17 | var isForked: Bool = false 18 | 19 | enum CodingKeys: String, CodingKey { 20 | case id = "id" 21 | case user = "owner" 22 | case repositoryName = "full_name" 23 | case desc = "description" 24 | case isPrivate = "private" 25 | case isForked = "fork" 26 | } 27 | 28 | func merge(_ repo: Repository?) { 29 | guard let repo = repo else { return } 30 | user?.merge(repo.user) 31 | repositoryName = repo.repositoryName 32 | desc = repo.desc 33 | isPrivate = repo.isPrivate 34 | isForked = repo.isForked 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/RepositoryListCellNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RepositoryListCellNode.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import AsyncDisplayKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxCocoa_Texture 13 | 14 | class RepositoryListCellNode: ASCellNode { 15 | typealias Node = RepositoryListCellNode 16 | 17 | struct Attribute { 18 | static let placeHolderColor: UIColor = UIColor.gray.withAlphaComponent(0.2) 19 | } 20 | 21 | // nodes 22 | lazy var userProfileNode = { () -> ASNetworkImageNode in 23 | let node = ASNetworkImageNode() 24 | node.style.preferredSize = CGSize(width: 50.0, height: 50.0) 25 | node.cornerRadius = 25.0 26 | node.clipsToBounds = true 27 | node.placeholderColor = Attribute.placeHolderColor 28 | node.borderColor = UIColor.gray.withAlphaComponent(0.5).cgColor 29 | node.borderWidth = 0.5 30 | return node 31 | }() 32 | 33 | lazy var usernameNode = { () -> ASTextNode in 34 | let node = ASTextNode() 35 | node.maximumNumberOfLines = 1 36 | node.placeholderColor = Attribute.placeHolderColor 37 | return node 38 | }() 39 | 40 | lazy var descriptionNode = { () -> ASTextNode in 41 | let node = ASTextNode() 42 | node.placeholderColor = Attribute.placeHolderColor 43 | node.maximumNumberOfLines = 2 44 | node.truncationAttributedText = NSAttributedString(string: " ...More", 45 | attributes: Node.moreSeeAttributes) 46 | node.delegate = self 47 | node.isUserInteractionEnabled = true 48 | return node 49 | }() 50 | 51 | lazy var statusNode = { () -> ASTextNode in 52 | let node = ASTextNode() 53 | node.placeholderColor = Attribute.placeHolderColor 54 | return node 55 | }() 56 | 57 | let disposeBag = DisposeBag() 58 | 59 | var id: Int = -1 60 | 61 | init(viewModel: RepositoryViewModel2) { 62 | self.id = viewModel.id 63 | super.init() 64 | self.selectionStyle = .none 65 | self.backgroundColor = .white 66 | self.automaticallyManagesSubnodes = true 67 | 68 | viewModel.profileURL 69 | .bind(to: userProfileNode.rx.url) 70 | .disposed(by: disposeBag) 71 | 72 | viewModel.username 73 | .bind(to: usernameNode.rx.text(Node.usernameAttributes), 74 | setNeedsLayout: self) 75 | .disposed(by: disposeBag) 76 | 77 | viewModel.desc 78 | .bind(to: descriptionNode.rx.text(Node.descAttributes), 79 | setNeedsLayout: self) 80 | .disposed(by: disposeBag) 81 | 82 | viewModel.status 83 | .bind(to: statusNode.rx.text(Node.statusAttributes), 84 | setNeedsLayout: self) 85 | .disposed(by: disposeBag) 86 | 87 | userProfileNode.rx.tap.asObservable().flatMap({ ImageDownloader.download() }) 88 | .bind(to: viewModel.updateProfileImage) 89 | .disposed(by: disposeBag) 90 | } 91 | } 92 | 93 | extension RepositoryListCellNode: ASTextNodeDelegate { 94 | 95 | func textNodeTappedTruncationToken(_ textNode: ASTextNode) { 96 | textNode.maximumNumberOfLines = 0 97 | self.recursivelyEnsureDisplaySynchronously(true) 98 | self.setNeedsLayout() 99 | } 100 | } 101 | 102 | // MARK: - LayoutSpec 103 | extension RepositoryListCellNode { 104 | 105 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { 106 | let contentLayout = contentLayoutSpec() 107 | contentLayout.style.flexShrink = 1.0 108 | contentLayout.style.flexGrow = 1.0 109 | 110 | userProfileNode.style.flexShrink = 1.0 111 | userProfileNode.style.flexGrow = 0.0 112 | 113 | let stackLayout = ASStackLayoutSpec(direction: .horizontal, 114 | spacing: 10.0, 115 | justifyContent: .start, 116 | alignItems: .center, 117 | children: [userProfileNode, 118 | contentLayout]) 119 | return ASInsetLayoutSpec(insets: UIEdgeInsets(top: 10.0, 120 | left: 10.0, 121 | bottom: 10.0, 122 | right: 10.0), 123 | child: stackLayout) 124 | } 125 | 126 | private func contentLayoutSpec() -> ASLayoutSpec { 127 | let elements = [self.usernameNode, 128 | self.descriptionNode, 129 | self.statusNode].filter { $0.attributedText?.length ?? 0 > 0 } 130 | return ASStackLayoutSpec(direction: .vertical, 131 | spacing: 5.0, 132 | justifyContent: .start, 133 | alignItems: .stretch, 134 | children: elements) 135 | } 136 | } 137 | 138 | extension RepositoryListCellNode { 139 | 140 | static var usernameAttributes: [NSAttributedString.Key: Any] { 141 | return [NSAttributedString.Key.foregroundColor: UIColor.black, 142 | NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20.0)] 143 | } 144 | 145 | static var descAttributes: [NSAttributedString.Key: Any] { 146 | return [NSAttributedString.Key.foregroundColor: UIColor.darkGray, 147 | NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15.0)] 148 | } 149 | 150 | static var statusAttributes: [NSAttributedString.Key: Any] { 151 | return [NSAttributedString.Key.foregroundColor: UIColor.gray, 152 | NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12.0)] 153 | } 154 | 155 | static var moreSeeAttributes: [NSAttributedString.Key: Any] { 156 | return [NSAttributedString.Key.foregroundColor: UIColor.darkGray, 157 | NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15.0, weight: .medium)] 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/RepositoryViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RepositoryViewController.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import AsyncDisplayKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | class RepositoryViewController: ASViewController { 14 | 15 | private var items: [RepositoryViewModel2] = [] 16 | private var context = ASBatchContext() 17 | 18 | let disposeBag = DisposeBag() 19 | 20 | init() { 21 | let tableNode = ASTableNode(style: .plain) 22 | tableNode.backgroundColor = .white 23 | tableNode.automaticallyManagesSubnodes = true 24 | super.init(node: tableNode) 25 | 26 | self.title = "Repository" 27 | 28 | // main thread 29 | self.node.onDidLoad({ node in 30 | guard let `node` = node as? ASTableNode else { return } 31 | node.view.separatorStyle = .singleLine 32 | }) 33 | 34 | self.node.leadingScreensForBatching = 2.0 35 | self.node.dataSource = self 36 | self.node.delegate = self 37 | self.node.allowsSelectionDuringEditing = true 38 | } 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | self.loadMoreRepo(since: nil) 43 | } 44 | 45 | required init?(coder aDecoder: NSCoder) { 46 | fatalError("init(coder:) has not been implemented") 47 | } 48 | 49 | func loadMoreRepo(since: Int?) { 50 | _ = RepoService.loadRepository(params: [.since(since)]) 51 | .delay(.milliseconds(500), scheduler: MainScheduler.asyncInstance) 52 | .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .default)) 53 | .map { $0.map { RepositoryViewModel2(repository: $0) } } 54 | .observeOn(MainScheduler.instance) 55 | .subscribe(onSuccess: { [weak self] items in 56 | guard let `self` = self else { return } 57 | 58 | if since == nil { 59 | self.items = items 60 | self.node.reloadData() 61 | self.context.completeBatchFetching(true) 62 | } else { 63 | // appending is good at table performance 64 | let updateIndexPaths = items.enumerated() 65 | .map { offset, _ -> IndexPath in 66 | return IndexPath(row: self.items.count - 1 + offset, section: 0) 67 | } 68 | 69 | self.items.append(contentsOf: items) 70 | self.node.insertRows(at: updateIndexPaths, 71 | with: .fade) 72 | self.context.completeBatchFetching(true) 73 | } 74 | }, onError: { [weak self] error in 75 | guard let `self` = self else { return } 76 | self.context.completeBatchFetching(true) 77 | }) 78 | } 79 | } 80 | 81 | extension RepositoryViewController: ASTableDataSource { 82 | func numberOfSections(in tableNode: ASTableNode) -> Int { 83 | return 1 84 | } 85 | 86 | func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int { 87 | return self.items.count 88 | } 89 | 90 | /* 91 | Node Block Thread Safety Warning 92 | It is very important that node blocks be thread-safe. 93 | One aspect of that is ensuring that the data model is accessed outside of the node block. 94 | Therefore, it is unlikely that you should need to use the index inside of the block. 95 | */ 96 | func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { 97 | return { 98 | guard self.items.count > indexPath.row else { return ASCellNode() } 99 | return RepositoryListCellNode(viewModel: self.items[indexPath.row]) 100 | } 101 | } 102 | 103 | // func tableNode(_ tableNode: ASTableNode, nodeForRowAt indexPath: IndexPath) -> ASCellNode { 104 | // guard self.items.count > indexPath.row else { return ASCellNode() } 105 | // return RepositoryListCellNode(viewModel: self.items[indexPath.row]) 106 | // } 107 | 108 | } 109 | 110 | extension RepositoryViewController: ASTableDelegate { 111 | // block ASBatchContext active state 112 | func shouldBatchFetch(for tableNode: ASTableNode) -> Bool { 113 | return !self.context.isFetching() 114 | } 115 | 116 | // load more 117 | func tableNode(_ tableNode: ASTableNode, willBeginBatchFetchWith context: ASBatchContext) { 118 | self.context = context 119 | self.loadMoreRepo(since: self.items.last?.id) 120 | } 121 | 122 | // editable cell 123 | func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 124 | return true 125 | } 126 | 127 | func tableView(_ tableView: UITableView, 128 | commit editingStyle: UITableViewCell.EditingStyle, 129 | forRowAt indexPath: IndexPath) { 130 | 131 | if editingStyle == .delete { 132 | self.node.performBatchUpdates({ 133 | self.items.remove(at: indexPath.row) 134 | self.node.deleteRows(at: [indexPath], with: .fade) 135 | }, completion: nil) 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/RepositoryViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RepositoryViewModel.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxCocoa 11 | 12 | class RepositoryViewModel { 13 | 14 | // @INPUT 15 | let updateRepository = PublishRelay() 16 | let updateUsername = PublishRelay() 17 | let updateDescription = PublishRelay() 18 | let updateProfileImage = PublishRelay() 19 | 20 | // @OUTPUT 21 | var username: Observable 22 | var profileURL: Observable 23 | var desc: Observable 24 | var status: Observable 25 | 26 | let id: Int 27 | let disposeBag = DisposeBag() 28 | 29 | deinit { 30 | RepoProvider.release(id: id) 31 | } 32 | 33 | init(repository: Repository) { 34 | self.id = repository.id 35 | 36 | RepoProvider.addAndUpdate(repository) 37 | 38 | let repoObserver = RepoProvider.observable(id: id) 39 | .asObservable() 40 | .share(replay: 1, scope: .whileConnected) 41 | 42 | self.username = repoObserver 43 | .map { $0?.user?.username } 44 | 45 | self.profileURL = repoObserver 46 | .map { $0?.user?.profileURL } 47 | 48 | self.desc = repoObserver 49 | .map { $0?.desc } 50 | 51 | self.status = repoObserver 52 | .map { item -> String? in 53 | var statusArray: [String] = [] 54 | if let isForked = item?.isForked, isForked { 55 | statusArray.append("Forked") 56 | } 57 | 58 | if let isPrivate = item?.isPrivate, isPrivate { 59 | statusArray.append("Private") 60 | } 61 | 62 | return statusArray.isEmpty ? nil: statusArray.joined(separator: " · ") 63 | } 64 | 65 | self.updateRepository.subscribe(onNext: { newRepo in 66 | RepoProvider.update(newRepo) 67 | }).disposed(by: disposeBag) 68 | 69 | updateUsername.withLatestFrom(repoObserver) { ($0, $1) } 70 | .subscribe(onNext: { text, repo in 71 | guard let repo = repo else { return } 72 | repo.user?.username = text ?? "" 73 | RepoProvider.update(repo) 74 | }).disposed(by: disposeBag) 75 | 76 | updateDescription.withLatestFrom(repoObserver) { ($0, $1) } 77 | .subscribe(onNext: { text, repo in 78 | guard let repo = repo else { return } 79 | repo.desc = text 80 | RepoProvider.update(repo) 81 | }).disposed(by: disposeBag) 82 | 83 | updateProfileImage.withLatestFrom(repoObserver) { ($0, $1) } 84 | .subscribe(onNext: { url, repo in 85 | guard let repo = repo else { return } 86 | repo.user?.profileURL = url 87 | RepoProvider.update(repo) 88 | }).disposed(by: disposeBag) 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/RepositoryViewModel2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RepositoryViewModel2.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxCocoa 11 | import RxCocoa_Texture 12 | 13 | class RepositoryViewModel2 { 14 | 15 | // @INPUT 16 | let updateRepository = PublishRelay() 17 | let updateUsername = PublishRelay() 18 | let updateDescription = PublishRelay() 19 | let updateProfileImage = PublishRelay() 20 | 21 | // @OUTPUT 22 | var username = BehaviorRelay(value: nil) 23 | var profileURL = BehaviorRelay(value: nil) 24 | var desc = BehaviorRelay(value: nil) 25 | var status = BehaviorRelay(value: nil) 26 | 27 | let id: Int 28 | let disposeBag = DisposeBag() 29 | 30 | deinit { 31 | RepoProvider.release(id: id) 32 | } 33 | 34 | init(repository: Repository) { 35 | self.id = repository.id 36 | 37 | RepoProvider.addAndUpdate(repository) 38 | 39 | let repoObserver = RepoProvider.observable(id: id) 40 | .asObservable() 41 | .share(replay: 1, scope: .whileConnected) 42 | 43 | repoObserver 44 | .map { $0?.user?.username } 45 | .bind(to: username) 46 | .disposed(by: disposeBag) 47 | 48 | repoObserver 49 | .map { $0?.user?.profileURL } 50 | .bind(to: profileURL) 51 | .disposed(by: disposeBag) 52 | 53 | repoObserver 54 | .map { $0?.desc } 55 | .bind(to: desc) 56 | .disposed(by: disposeBag) 57 | 58 | repoObserver 59 | .map { item -> String? in 60 | var statusArray: [String] = [] 61 | if let isForked = item?.isForked, isForked { 62 | statusArray.append("Forked") 63 | } 64 | 65 | if let isPrivate = item?.isPrivate, isPrivate { 66 | statusArray.append("Private") 67 | } 68 | 69 | return statusArray.isEmpty ? nil: statusArray.joined(separator: " · ") 70 | }.bind(to: status) 71 | .disposed(by: disposeBag) 72 | 73 | self.updateRepository.subscribe(onNext: { newRepo in 74 | RepoProvider.update(newRepo) 75 | }).disposed(by: disposeBag) 76 | 77 | updateUsername.withLatestFrom(repoObserver) { ($0, $1) } 78 | .subscribe(onNext: { text, repo in 79 | guard let repo = repo else { return } 80 | repo.user?.username = text ?? "" 81 | RepoProvider.update(repo) 82 | }).disposed(by: disposeBag) 83 | 84 | updateDescription.withLatestFrom(repoObserver) { ($0, $1) } 85 | .subscribe(onNext: { text, repo in 86 | guard let repo = repo else { return } 87 | repo.desc = text 88 | RepoProvider.update(repo) 89 | }).disposed(by: disposeBag) 90 | 91 | updateProfileImage.withLatestFrom(repoObserver) { ($0, $1) } 92 | .subscribe(onNext: { url, repo in 93 | guard let repo = repo else { return } 94 | repo.user?.profileURL = url 95 | RepoProvider.update(repo) 96 | }).disposed(by: disposeBag) 97 | 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/TextTestNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestTestNode.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import AsyncDisplayKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxCocoa_Texture 13 | 14 | class TextTestNode: ASDisplayNode { 15 | 16 | /** ASTextNode is ASControlNode subclass, 17 | so, you just use ASControlNode+RxExtension functions 18 | ref: ASControlNode+RxExtension 19 | **/ 20 | 21 | lazy var textNode = ASTextNode() 22 | let disposeBag = DisposeBag() 23 | static let attribute = [NSAttributedString.Key.foregroundColor: UIColor.gray] 24 | 25 | override init() { 26 | super.init() 27 | 28 | Observable.just("text") 29 | .bind(to: textNode.rx.text(TextTestNode.attribute)) 30 | .disposed(by: disposeBag) 31 | 32 | Observable.just("text") 33 | .map { NSAttributedString(string: $0, attributes: TextTestNode.attribute) } 34 | .bind(to: textNode.rx.attributedText) 35 | .disposed(by: disposeBag) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Example/RxCocoa-Texture/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import RxCocoa_Texture 10 | 11 | class User: Decodable { 12 | var username: String = "" 13 | var profileURL: URL? 14 | 15 | enum CodingKeys: String, CodingKey { 16 | case username = "login" 17 | case profileURL = "avatar_url" 18 | } 19 | 20 | func merge(_ user: User?) { 21 | guard let user = user else { return } 22 | self.username = user.username 23 | self.profileURL = user.profileURL 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Example/Tests/ASButtonNode+RxSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASButtonNode+RxExtensionSpec.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Quick 9 | import Nimble 10 | import RxTest 11 | import RxSwift 12 | import RxCocoa 13 | import AsyncDisplayKit 14 | import UIKit 15 | @testable import RxCocoa_Texture 16 | 17 | class ASButtonNode_RxExtensionSpecSpec: QuickSpec { 18 | 19 | override func spec() { 20 | 21 | context("ASButtonNode Reactive Extension Unit Test") { 22 | 23 | let buttonNode = ASButtonNode() 24 | let testAttributedString = NSAttributedString.init(string: "apple") 25 | let testAttributedString2 = NSAttributedString.init(string: "banana") 26 | let testText = "cream" 27 | let testText2 = "dream" 28 | let disposedBag = DisposeBag() 29 | 30 | it("should be success bind attributedText") { 31 | 32 | Observable.just(testAttributedString) 33 | .bind(to: buttonNode.rx.attributedText) 34 | .disposed(by: disposedBag) 35 | 36 | expect(buttonNode.attributedTitle(for: .normal)) 37 | .to(equal(testAttributedString)) 38 | expect(buttonNode.attributedTitle(for: .highlighted)) 39 | .to(equal(testAttributedString)) 40 | expect(buttonNode.attributedTitle(for: .disabled)) 41 | .to(equal(testAttributedString)) 42 | expect(buttonNode.attributedTitle(for: .selected)) 43 | .to(equal(testAttributedString)) 44 | 45 | Observable.just(testAttributedString2) 46 | .bind(to: buttonNode.rx.attributedText(.highlighted)) 47 | .disposed(by: disposedBag) 48 | 49 | expect(buttonNode.attributedTitle(for: .highlighted)) 50 | .toNot(equal(testAttributedString)) 51 | expect(buttonNode.attributedTitle(for: .highlighted)) 52 | .to(equal(testAttributedString2)) 53 | 54 | expect(buttonNode.attributedTitle(for: .normal)) 55 | .to(equal(testAttributedString)) 56 | expect(buttonNode.attributedTitle(for: .disabled)) 57 | .to(equal(testAttributedString)) 58 | expect(buttonNode.attributedTitle(for: .selected)) 59 | .to(equal(testAttributedString)) 60 | } 61 | 62 | it("should be success bind text") { 63 | 64 | Observable.just(testText) 65 | .bind(to: buttonNode.rx.text(nil)) 66 | .disposed(by: disposedBag) 67 | 68 | expect(buttonNode.attributedTitle(for: .normal)?.string == testText) 69 | .to(beTrue()) 70 | expect(buttonNode.attributedTitle(for: .highlighted)?.string == testText) 71 | .to(beTrue()) 72 | expect(buttonNode.attributedTitle(for: .disabled)?.string == testText) 73 | .to(beTrue()) 74 | expect(buttonNode.attributedTitle(for: .selected)?.string == testText) 75 | .to(beTrue()) 76 | 77 | Observable.just(testText2) 78 | .bind(to: buttonNode.rx.text(nil, target: .disabled)) 79 | .disposed(by: disposedBag) 80 | 81 | expect(buttonNode.attributedTitle(for: .normal)?.string == testText) 82 | .to(beTrue()) 83 | expect(buttonNode.attributedTitle(for: .highlighted)?.string == testText) 84 | .to(beTrue()) 85 | expect(buttonNode.attributedTitle(for: .selected)?.string == testText) 86 | .to(beTrue()) 87 | 88 | expect(buttonNode.attributedTitle(for: .disabled)?.string == testText) 89 | .to(beFalse()) 90 | expect(buttonNode.attributedTitle(for: .disabled)?.string == testText2) 91 | .to(beTrue()) 92 | 93 | Observable.just(testText2) 94 | .bind(to: buttonNode.rx.text(applyList: [.selected(nil), 95 | .highlighted(nil)])) 96 | .disposed(by: disposedBag) 97 | 98 | 99 | expect(buttonNode.attributedTitle(for: .normal)?.string == testText) 100 | .to(beTrue()) 101 | 102 | 103 | expect(buttonNode.attributedTitle(for: .selected)?.string == testText2) 104 | .to(beTrue()) 105 | expect(buttonNode.attributedTitle(for: .disabled)?.string == testText2) 106 | .to(beTrue()) 107 | expect(buttonNode.attributedTitle(for: .highlighted)?.string == testText2) 108 | .to(beTrue()) 109 | } 110 | } 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /Example/Tests/ASControlNode+RxSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASControlNode+RxExtensionSpec.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Quick 9 | import Nimble 10 | import RxTest 11 | import RxSwift 12 | import RxCocoa 13 | import AsyncDisplayKit 14 | @testable import RxCocoa_Texture 15 | 16 | class ASControlNode_RxExtensionSpecSpec: QuickSpec { 17 | 18 | // refer: http://texturegroup.org/docs/node-overview.html 19 | override func spec() { 20 | 21 | context("ASControlNode Reactive Extension Test") { 22 | 23 | let controlNode = ASControlNode() 24 | let disposedBag = DisposeBag() 25 | 26 | it("should be emit expected event") { 27 | let scheduler = TestScheduler.init(initialClock: 0) 28 | 29 | let emitObserver = scheduler 30 | .createHotObservable([.next(100, ASControlNodeEvent.touchUpInside), 31 | .next(200, ASControlNodeEvent.touchDown), 32 | .next(300, ASControlNodeEvent.touchUpOutside), 33 | .next(400, ASControlNodeEvent.touchDragInside), 34 | .next(500, ASControlNodeEvent.touchCancel)]) 35 | // etc 36 | 37 | emitObserver.subscribe(onNext: { event in 38 | controlNode.sendActions(forControlEvents: event, with: nil) 39 | }).disposed(by: disposedBag) 40 | 41 | let outputEvent = scheduler 42 | .record(controlNode.rx.controlEvent(.touchUpInside) 43 | .map { _ -> ASControlNodeEvent in return .touchUpInside }) 44 | let outputEvent2 = scheduler 45 | .record(controlNode.rx.controlEvent(.touchDown) 46 | .map { _ -> ASControlNodeEvent in return .touchDown }) 47 | let outputEvent3 = scheduler 48 | .record(controlNode.rx.controlEvent(.touchUpOutside) 49 | .map { _ -> ASControlNodeEvent in return .touchUpOutside}) 50 | let outputEvent4 = scheduler 51 | .record(controlNode.rx.controlEvent(.touchDragInside) 52 | .map { _ -> ASControlNodeEvent in return .touchDragInside }) 53 | let outputEvent5 = scheduler 54 | .record(controlNode.rx.controlEvent(.touchCancel) 55 | .map { _ -> ASControlNodeEvent in return .touchCancel }) 56 | 57 | scheduler.start() 58 | 59 | let expectEvent: [Recorded>] = [ 60 | .next(100, .touchUpInside) 61 | ] 62 | 63 | let expectEvent2: [Recorded>] = [ 64 | .next(200, .touchDown) 65 | ] 66 | 67 | let expectEvent3: [Recorded>] = [ 68 | .next(300, .touchUpOutside) 69 | ] 70 | 71 | let expectEvent4: [Recorded>] = [ 72 | .next(400, .touchDragInside) 73 | ] 74 | 75 | let expectEvent5: [Recorded>] = [ 76 | .next(500, .touchCancel) 77 | ] 78 | 79 | XCTAssertEqual(outputEvent.events, expectEvent) 80 | XCTAssertEqual(outputEvent2.events, expectEvent2) 81 | XCTAssertEqual(outputEvent3.events, expectEvent3) 82 | XCTAssertEqual(outputEvent4.events, expectEvent4) 83 | XCTAssertEqual(outputEvent5.events, expectEvent5) 84 | } 85 | 86 | it("should be emit tap event") { 87 | 88 | let scheduler = TestScheduler.init(initialClock: 0) 89 | 90 | let emitObserver = scheduler 91 | .createHotObservable([.next(100, ()), 92 | .next(200, ()), 93 | .next(300, ())]) 94 | 95 | emitObserver.subscribe(onNext: { _ in 96 | controlNode.sendActions(forControlEvents: .touchUpInside, with: nil) 97 | }).disposed(by: disposedBag) 98 | 99 | let outputEvent = scheduler 100 | .record(controlNode.rx.tap.map { _ in return true }) 101 | 102 | scheduler.start() 103 | 104 | let expectEvent: [Recorded>] = [ 105 | .next(100, true), 106 | .next(200, true), 107 | .next(300, true) 108 | ] 109 | 110 | XCTAssertEqual(outputEvent.events, expectEvent) 111 | } 112 | 113 | it("should be emit relay tap event") { 114 | 115 | let scheduler = TestScheduler.init(initialClock: 0) 116 | let emitter = PublishRelay() 117 | let accepter: Observable 118 | 119 | accepter = emitter.map { _ in return true }.asObservable() 120 | 121 | let emitObserver = scheduler 122 | .createHotObservable([.next(100, ()), 123 | .next(200, ()), 124 | .next(300, ())]) 125 | 126 | emitObserver.subscribe(onNext: { _ in 127 | controlNode.sendActions(forControlEvents: .touchUpInside, with: nil) 128 | }).disposed(by: disposedBag) 129 | 130 | controlNode.rx.tap(to: emitter).disposed(by: disposedBag) 131 | 132 | let outputEvent = scheduler.record(accepter) 133 | 134 | scheduler.start() 135 | 136 | let expectEvent: [Recorded>] = [ 137 | .next(100, true), 138 | .next(200, true), 139 | .next(300, true) 140 | ] 141 | 142 | XCTAssertEqual(outputEvent.events, expectEvent) 143 | } 144 | 145 | it("should be hidden/show") { 146 | 147 | Observable.just(true) 148 | .bind(to: controlNode.rx.isHidden) 149 | .disposed(by: disposedBag) 150 | 151 | expect(controlNode.isHidden).to(beTrue()) 152 | 153 | Observable.just(false) 154 | .bind(to: controlNode.rx.isHidden) 155 | .disposed(by: disposedBag) 156 | 157 | expect(controlNode.isHidden).to(beFalse()) 158 | } 159 | 160 | it("should be enable/disable") { 161 | 162 | Observable.just(true) 163 | .bind(to: controlNode.rx.isEnabled) 164 | .disposed(by: disposedBag) 165 | 166 | expect(controlNode.isEnabled).to(beTrue()) 167 | 168 | Observable.just(false) 169 | .bind(to: controlNode.rx.isEnabled) 170 | .disposed(by: disposedBag) 171 | 172 | expect(controlNode.isEnabled).to(beFalse()) 173 | 174 | } 175 | 176 | it("should be highlight/unhighlight") { 177 | 178 | Observable.just(true) 179 | .bind(to: controlNode.rx.isHighlighted) 180 | .disposed(by: disposedBag) 181 | 182 | expect(controlNode.isHighlighted).to(beTrue()) 183 | 184 | Observable.just(false) 185 | .bind(to: controlNode.rx.isHighlighted) 186 | .disposed(by: disposedBag) 187 | 188 | expect(controlNode.isHighlighted).to(beFalse()) 189 | } 190 | 191 | it("should observe highlight/unhighlight") { 192 | final class UITouchStub: UITouch { 193 | private let _tapCount: Int 194 | override var tapCount: Int { 195 | return self._tapCount 196 | } 197 | 198 | override init() { 199 | self._tapCount = 1 // becaused of ASControlNode.touchesBegan(_:with:) 200 | super.init() 201 | } 202 | } 203 | 204 | // given 205 | var isHighlighted: Bool = false 206 | controlNode.isEnabled = true 207 | controlNode.rx.isHighlighted 208 | .subscribe(onNext: { isHighlighted = $0 }) 209 | .disposed(by: disposedBag) 210 | 211 | // when & then - Touch down/up/cancel 212 | controlNode.touchesBegan([UITouchStub()], with: nil) 213 | expect(isHighlighted).to(beTrue()) 214 | 215 | controlNode.touchesBegan([UITouchStub()], with: nil) 216 | controlNode.touchesEnded([UITouchStub()], with: nil) 217 | expect(isHighlighted).to(beFalse()) 218 | 219 | controlNode.touchesBegan([UITouchStub()], with: nil) 220 | controlNode.touchesCancelled([UITouchStub()], with: nil) 221 | expect(isHighlighted).to(beFalse()) 222 | } 223 | 224 | it("should be selected/non-selected") { 225 | 226 | Observable.just(true) 227 | .bind(to: controlNode.rx.isSelected) 228 | .disposed(by: disposedBag) 229 | 230 | expect(controlNode.isSelected).to(beTrue()) 231 | 232 | Observable.just(false) 233 | .bind(to: controlNode.rx.isSelected) 234 | .disposed(by: disposedBag) 235 | 236 | expect(controlNode.isSelected).to(beFalse()) 237 | } 238 | } 239 | } 240 | } 241 | 242 | extension TestScheduler { 243 | /// Creates a `TestableObserver` instance which immediately subscribes 244 | /// to the `source` and disposes the subscription at virtual time 100000. 245 | func record(_ source: O) -> TestableObserver { 246 | let observer = self.createObserver(O.Element.self) 247 | let disposable = source.asObservable().bind(to: observer) 248 | self.scheduleAt(100000) { 249 | disposable.dispose() 250 | } 251 | return observer 252 | } 253 | } 254 | 255 | -------------------------------------------------------------------------------- /Example/Tests/ASDisplayNode+RxSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASDisplayNode+RxSpec.swift 3 | // 4 | // Created by Kanghoon 5 | // Copyright © 2019 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Quick 9 | import Nimble 10 | import RxTest 11 | import RxSwift 12 | import RxCocoa 13 | import AsyncDisplayKit 14 | @testable import RxCocoa_Texture 15 | 16 | class ASDisplayNode_RxExtensionSpec: QuickSpec { 17 | 18 | override func spec() { 19 | describe("ASDisplayNode") { 20 | let node1 = ASDisplayNode() 21 | let node2 = ASDisplayNode() 22 | let node3 = ASDisplayNode() 23 | let node4 = ASDisplayNode() 24 | 25 | beforeEach { 26 | node1.rx.width.onNext(ASDimension(unit: .points, value: 17.0)) 27 | node1.rx.minWidth.onNext(ASDimension(unit: .points, value: 15.0)) 28 | node1.rx.maxWidth.onNext(ASDimension(unit: .points, value: 20.0)) 29 | 30 | node2.rx.height.onNext(ASDimension(unit: .points, value: 17.0)) 31 | node2.rx.minHeight.onNext(ASDimension(unit: .points, value: 15.0)) 32 | node2.rx.maxHeight.onNext(ASDimension(unit: .points, value: 20.0)) 33 | 34 | node3.rx.preferredSize.onNext(CGSize(width: 40.0, height: 40.0)) 35 | 36 | node4.rx.backgroundColor.onNext(.red) 37 | node4.rx.alpha.onNext(0.3) 38 | node4.rx.isUserInteractionEnabled.onNext(false) 39 | } 40 | 41 | it("should has correct width") { 42 | expect(node1.style.width.value).to(equal(17.0)) 43 | } 44 | 45 | it("should has correct minWidth") { 46 | expect(node1.style.minWidth.value).to(equal(15.0)) 47 | } 48 | 49 | it("should has correct maxWidth") { 50 | expect(node1.style.maxWidth.value).to(equal(20.0)) 51 | } 52 | 53 | it("should has correct height") { 54 | expect(node2.style.height.value).to(equal(17.0)) 55 | } 56 | 57 | it("should has correct minHeight") { 58 | expect(node2.style.minHeight.value).to(equal(15.0)) 59 | } 60 | 61 | it("should has correct maxHeight") { 62 | expect(node2.style.maxHeight.value).to(equal(20.0)) 63 | } 64 | 65 | it("should has correct preferredSize") { 66 | expect(node3.style.preferredSize).to(equal(CGSize(width: 40.0, height: 40.0))) 67 | } 68 | 69 | it("should has correct backgroundColor") { 70 | expect(node4.backgroundColor).to(equal(.red)) 71 | } 72 | 73 | it("should has correct alpha") { 74 | expect(node4.alpha).to(equal(0.3)) 75 | } 76 | 77 | it("should has correct isUserInteractionEnabled") { 78 | expect(node4.isUserInteractionEnabled).to(equal(false)) 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Example/Tests/ASEditableTextNode+RxSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASEditableTextNode+RxExtensionSpec.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Quick 9 | import Nimble 10 | import RxTest 11 | import RxBlocking 12 | import RxSwift 13 | import RxCocoa 14 | import AsyncDisplayKit 15 | @testable import RxCocoa_Texture 16 | 17 | class ASEditableTextNode_RxExtensionSpec: QuickSpec { 18 | 19 | override func spec() { 20 | 21 | context("ASEditableTextNode Reactive Extension Unit Test") { 22 | 23 | let textNode1 = ASEditableTextNode() 24 | let textNode2 = ASEditableTextNode() 25 | let textNode3 = ASEditableTextNode() 26 | let textNode4 = ASEditableTextNode() 27 | let textNode5 = ASEditableTextNode() 28 | 29 | let disposeBag = DisposeBag() 30 | 31 | beforeEach { 32 | Observable.just("apple") 33 | .bind(to: textNode1.rx.text([:])) 34 | .disposed(by: disposeBag) 35 | 36 | Observable.just(nil) 37 | .bind(to: textNode2.rx.text([:])) 38 | .disposed(by: disposeBag) 39 | 40 | Observable.just("banana") 41 | .map({ NSAttributedString(string: $0) }) 42 | .bind(to: textNode3.rx.attributedText) 43 | .disposed(by: disposeBag) 44 | 45 | Observable.just(nil) 46 | .bind(to: textNode4.rx.attributedText) 47 | .disposed(by: disposeBag) 48 | 49 | textNode5.attributedText = NSAttributedString(string: "car") 50 | textNode5.delegate?.editableTextNodeDidUpdateText?(textNode5) 51 | } 52 | 53 | it("should be emit expected event") { 54 | 55 | expect(textNode1.attributedText?.string).to(equal("apple")) 56 | expect(textNode2.attributedText?.string).to(beNil()) 57 | expect(textNode3.attributedText?.string).to(equal("banana")) 58 | expect(textNode4.attributedText?.string).to(beNil()) 59 | expect(try? textNode5.rx.attributedText.filter { $0 != nil }.toBlocking().first()??.string) 60 | .to(equal("car")) 61 | } 62 | } 63 | } 64 | } 65 | 66 | 67 | -------------------------------------------------------------------------------- /Example/Tests/ASImageNode+RxSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASImageNode+RxExtensionSpec.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Quick 9 | import Nimble 10 | import RxTest 11 | import RxSwift 12 | import RxCocoa 13 | import AsyncDisplayKit 14 | @testable import RxCocoa_Texture 15 | 16 | class ASImageNode_RxExtensionSpecSpec: QuickSpec { 17 | 18 | override func spec() { 19 | 20 | context("ASImageNode Reactive Extension Unit Test") { 21 | 22 | let imageNode1 = ASImageNode() 23 | let imageNode2 = ASImageNode() 24 | let disposeBag = DisposeBag() 25 | 26 | beforeEach { 27 | Observable.just(#imageLiteral(resourceName: "texture_icon")) 28 | .bind(to: imageNode1.rx.image) 29 | .disposed(by: disposeBag) 30 | 31 | Observable.just(nil) 32 | .bind(to: imageNode2.rx.image) 33 | .disposed(by: disposeBag) 34 | } 35 | 36 | it("should be emit expected event") { 37 | expect(imageNode1.image).to(equal(#imageLiteral(resourceName: "texture_icon"))) 38 | expect(imageNode2.image).to(beNil()) 39 | } 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Example/Tests/ASNetworkImageNode+RxSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASNetworkImageNode+RxExtensionSpec.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Quick 9 | import Nimble 10 | import RxTest 11 | import RxSwift 12 | import RxCocoa 13 | import AsyncDisplayKit 14 | @testable import RxCocoa_Texture 15 | 16 | class ASNetworkImageNode_RxExtensionSpecSpec: QuickSpec { 17 | 18 | override func spec() { 19 | 20 | context("ASNetworkImageNode Reactive Extension Unit Test") { 21 | 22 | var url: URL! 23 | let imageNode1 = ASNetworkImageNode() 24 | let imageNode2 = ASNetworkImageNode() 25 | 26 | let disposeBag = DisposeBag() 27 | 28 | beforeEach { 29 | url = URL(string: "https://koreaboo-cdn.storage.googleapis.com/2017/08/sana-1-1.jpg") 30 | Observable.just(url) 31 | .bind(to: imageNode1.rx.url) 32 | .disposed(by: disposeBag) 33 | 34 | Observable.just(nil) 35 | .bind(to: imageNode2.rx.url) 36 | .disposed(by: disposeBag) 37 | } 38 | 39 | it("should be emit expected event") { 40 | expect(imageNode1.url).to(equal(url)) 41 | expect(imageNode2.url).to(beNil()) 42 | } 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/Tests/ASTextNode+RxSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASTextNode+RxExtensionSpec.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Quick 9 | import Nimble 10 | import RxTest 11 | import RxSwift 12 | import RxCocoa 13 | import AsyncDisplayKit 14 | @testable import RxCocoa_Texture 15 | 16 | class ASTextNode_RxExtensionSpecSpec: QuickSpec { 17 | 18 | override func spec() { 19 | 20 | context("ASTextNode Reactive Extension Unit Test") { 21 | 22 | let textNode1 = ASTextNode() 23 | let textNode2 = ASTextNode() 24 | let textNode3 = ASTextNode() 25 | let textNode4 = ASTextNode() 26 | 27 | let disposeBag = DisposeBag() 28 | 29 | beforeEach { 30 | Observable.just("apple") 31 | .bind(to: textNode1.rx.text([:])) 32 | .disposed(by: disposeBag) 33 | 34 | Observable.just(nil) 35 | .bind(to: textNode2.rx.text([:])) 36 | .disposed(by: disposeBag) 37 | 38 | Observable.just("banana") 39 | .map({ NSAttributedString(string: $0) }) 40 | .bind(to: textNode3.rx.attributedText) 41 | .disposed(by: disposeBag) 42 | 43 | Observable.just(nil) 44 | .bind(to: textNode4.rx.attributedText) 45 | .disposed(by: disposeBag) 46 | } 47 | 48 | it("should be emit expected event") { 49 | 50 | expect(textNode1.attributedText?.string).to(equal("apple")) 51 | expect(textNode2.attributedText?.string).to(equal("")) 52 | expect(textNode3.attributedText?.string).to(equal("banana")) 53 | expect(textNode4.attributedText?.string).to(equal("")) 54 | } 55 | } 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /Example/Tests/ASTextNode2+RxSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASTextNode2+RxExtensionSpec.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Quick 9 | import Nimble 10 | import RxTest 11 | import RxSwift 12 | import RxCocoa 13 | import AsyncDisplayKit 14 | @testable import RxCocoa_Texture 15 | 16 | #if AS_ENABLE_TEXTNODE 17 | 18 | class ASTextNode2_RxExtensionSpecSpec: QuickSpec { 19 | 20 | override func spec() { 21 | 22 | context("ASTextNode Reactive Extension Unit Test") { 23 | 24 | let textNode1 = ASTextNode2() 25 | let textNode2 = ASTextNode2() 26 | let textNode3 = ASTextNode2() 27 | let textNode4 = ASTextNode2() 28 | 29 | let disposeBag = DisposeBag() 30 | 31 | beforeEach { 32 | Observable.just("apple") 33 | .bind(to: textNode1.rx.text([:])) 34 | .disposed(by: disposeBag) 35 | 36 | Observable.just(nil) 37 | .bind(to: textNode2.rx.text([:])) 38 | .disposed(by: disposeBag) 39 | 40 | Observable.just("banana") 41 | .map({ NSAttributedString(string: $0) }) 42 | .bind(to: textNode3.rx.attributedText) 43 | .disposed(by: disposeBag) 44 | 45 | Observable.just(nil) 46 | .bind(to: textNode4.rx.attributedText) 47 | .disposed(by: disposeBag) 48 | } 49 | 50 | it("should be emit expected event") { 51 | 52 | expect(textNode1.attributedText?.string).to(equal("apple")) 53 | expect(textNode2.attributedText?.string).to(equal("")) 54 | expect(textNode3.attributedText?.string).to(equal("banana")) 55 | expect(textNode4.attributedText?.string).to(equal("")) 56 | } 57 | } 58 | } 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /Example/Tests/BinderAndDriver+RxSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BinderAndDriver+RxSpec.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import Quick 9 | import Nimble 10 | import RxTest 11 | import RxSwift 12 | import RxCocoa 13 | import AsyncDisplayKit 14 | @testable import RxCocoa_Texture 15 | 16 | class BinderAndDriver_RxExtensionSpec: QuickSpec { 17 | 18 | override func spec() { 19 | 20 | var disposeBag = DisposeBag() 21 | 22 | describe("Bind Test") { 23 | let textNode = ASTextNode() 24 | let textNode2 = ASTextNode() 25 | let textNode3 = ASTextNode() 26 | 27 | beforeEach { 28 | disposeBag = DisposeBag() 29 | 30 | Observable.just("test") 31 | .bind(to: textNode.rx.text(nil), setNeedsLayout: nil) 32 | .disposed(by: disposeBag) 33 | 34 | Observable.just("test2") 35 | .bind(to: textNode2.rx.text(nil), setNeedsLayout: nil) 36 | .disposed(by: disposeBag) 37 | 38 | Observable.just("test3") 39 | .bind(to: textNode3.rx.text(nil), setNeedsLayout: nil) 40 | .disposed(by: disposeBag) 41 | } 42 | 43 | it("should be bind estimated value") { 44 | 45 | expect(textNode.attributedText!.string).to(equal("test")) 46 | expect(textNode2.attributedText!.string).to(equal("test2")) 47 | expect(textNode3.attributedText!.string).to(equal("test3")) 48 | } 49 | } 50 | 51 | describe("Drive Test") { 52 | let textNode = ASTextNode() 53 | let textNode2 = ASTextNode() 54 | let textNode3 = ASTextNode() 55 | 56 | beforeEach { 57 | disposeBag = DisposeBag() 58 | 59 | Driver.just("test") 60 | .drive(textNode.rx.text(nil), setNeedsLayout: nil) 61 | .disposed(by: disposeBag) 62 | 63 | Driver.just("test2") 64 | .drive(textNode2.rx.text(nil), setNeedsLayout: nil) 65 | .disposed(by: disposeBag) 66 | 67 | Driver.just("test3") 68 | .drive(textNode3.rx.text(nil), setNeedsLayout: nil) 69 | .disposed(by: disposeBag) 70 | } 71 | 72 | it("should be drive estimated value") { 73 | 74 | expect(textNode.attributedText!.string).to(equal("test")) 75 | expect(textNode2.attributedText!.string).to(equal("test2")) 76 | expect(textNode3.attributedText!.string).to(equal("test3")) 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 RxSwiftCommunity. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![alt text](https://github.com/RxSwiftCommunity/RxCocoa-Texture/blob/master/resources/logo.png) 2 | 3 | [![CI Status](https://api.travis-ci.org/RxSwiftCommunity/RxCocoa-Texture.svg?branch=master)](https://travis-ci.org/GeekTree0101/RxCocoa-Texture) 4 | [![Version](https://img.shields.io/cocoapods/v/RxCocoa-Texture.svg?style=flat)](https://cocoapods.org/pods/RxCocoa-Texture) 5 | [![License](https://img.shields.io/cocoapods/l/RxCocoa-Texture.svg?style=flat)](https://cocoapods.org/pods/RxCocoa-Texture) 6 | [![Platform](https://img.shields.io/cocoapods/p/RxCocoa-Texture.svg?style=flat)](https://cocoapods.org/pods/RxCocoa-Texture) 7 | 8 | ## Notice 9 | - Now support swift 5.x https://github.com/RxSwiftCommunity/RxCocoa-Texture/blob/swift-5.x 10 | ```ruby 11 | pod 'RxCocoa-Texture', '3.x.x' 12 | ``` 13 | 14 | > ### Your Contributions always welcome welcome!. 15 | 16 | ## Concept 17 | RxCocoa provides extensions to the Cocoa and Cocoa Touch frameworks to take advantage of RxSwift. 18 | Texture provides various basic UI components such as ASTableNode, ASControlNode, ASButtonNode and so on. 19 | ref: [Node Subclasses](http://texturegroup.org/docs/node-overview.html) 20 | 21 | So, This Library provides extensions to the Texture frameworks to take advantage of RxSwift like a RxCocoa 22 | 23 | ref: [Texture + RxSwift Interactive Wrapper](https://medium.com/@h2s1880/texture-rxswift-interactive-wrapper-d3c9843ed8d7) 24 | 25 | ## Example 26 | 27 | #### Extension 28 | 29 | - [ASButtonNode RxExtension Example](https://github.com/GeekTree0101/RxCocoa-Texture/tree/master/Example/RxCocoa-Texture/ButtonTestNode.swift) 30 | 31 | - [ASImageNode RxExtension Example](https://github.com/GeekTree0101/RxCocoa-Texture/tree/master/Example/RxCocoa-Texture/ImageTestNode.swift) 32 | 33 | - [ASNetworkImageNode RxExtension Example](https://github.com/GeekTree0101/RxCocoa-Texture/tree/master/Example/RxCocoa-Texture/NetworkImageTestNode.swift) 34 | 35 | 36 | - [ASTextNode RxExtension Example](https://github.com/GeekTree0101/RxCocoa-Texture/tree/master/Example/RxCocoa-Texture/TextTestNode.swift) 37 | 38 | - [ASEditableTextNode RxExtension Example](https://github.com/GeekTree0101/RxCocoa-Texture/tree/master/Example/RxCocoa-Texture/EditableTextTestNode.swift) 39 | 40 | - [ASScrollNode](https://github.com/ReactiveX/RxSwift/blob/master/RxCocoa/iOS/UIScrollView%2BRx.swift) 41 | 42 | #### ASBinder 43 | : Subscribed Observer operates asynchronously. 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
Expectation FlowExpectation UI
55 | 56 | But, Node dosen't know that event value applied on UI before draw. 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |
Unexpectation FlowUnexpectation UI
68 | 69 | In this case, Node should use setNeedsLayout. but, [bind:_] doesn't call setNeedsLayout automatically. 70 | 71 | Normally, you can use like this code 72 | 73 | ```swift 74 | // Profile NetworkImage Node is default 75 | // username, description is Optional 76 | 77 | // *** self is usernameNode supernode 78 | viewModel.username 79 | .subscribe(onNext: { [weak self] text in 80 | self?.usernameNode.rx.text(Node.usernameAttributes).onNext(text) 81 | self?.setNeedsLayout() // Here 82 | }) 83 | .disposed(by: disposeBag) 84 | ``` 85 | 86 | If you use ASBinder then you don't need call setNeedsLayout. ASBinder will operates it automatically. 87 | 88 | ```swift 89 | // Profile NetworkImage Node is default 90 | // username, description is Optional 91 | 92 | // *** self is usernameNode supernode 93 | viewModel.username 94 | .bind(to: usernameNode.rx.text(Node.usernameAttributes), 95 | setNeedsLayout: self) 96 | .disposed(by: disposeBag) 97 | 98 | // *** self is descriptionNode supernode 99 | viewModel.desc 100 | .bind(to: descriptionNode.rx.text(Node.descAttributes), 101 | setNeedsLayout: self) 102 | .disposed(by: disposeBag) 103 | ``` 104 | 105 | If you don't need setNeedsLayout then just write code like this. 106 | 107 | 108 | ```swift 109 | // setNeedsLayout default is nil! 110 | viewModel.username 111 | .bind(to: usernameNode.rx.text(Node.usernameAttributes)) 112 | .disposed(by: disposeBag) 113 | 114 | viewModel.desc 115 | .bind(to: descriptionNode.rx.text(Node.descAttributes)) 116 | .disposed(by: disposeBag) 117 | ``` 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 |
ASBinder FlowOutput UI
129 | 130 | - [ASBinder](https://github.com/GeekTree0101/RxCocoa-Texture/blob/master/Example/RxCocoa-Texture/ASBinderTestNode.swift) 131 | 132 | 133 | ## Installation 134 | 135 | RxCocoa-Texture is available through [CocoaPods](https://cocoapods.org). To install 136 | it, simply add the following line to your Podfile: 137 | 138 | #### swift 4.x 139 | 140 | ```ruby 141 | pod 'RxCocoa-Texture' 142 | ``` 143 | 144 | ## Caution 145 | This library has been migrated to Texture 2.7. 146 | When Rx subscribe logic moves from initialization to didLoad method. I no longer faced this problem. 147 | When using RxSwift / RxCocoa, it is safe to subscribe from the didLoad method. 148 | https://github.com/TextureGroup/Texture/issues/977 149 | 150 | ## Author 151 | 152 | Geektree0101, h2s1880@gmail.com 153 | 154 | ## License 155 | This library belongs to RxSwiftCommunity. 156 | RxCocoa-Texture is available under the MIT license. See the LICENSE file for more info 157 | -------------------------------------------------------------------------------- /RxCocoa-Texture.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'RxCocoa-Texture' 3 | s.version = '3.1.0' 4 | s.summary = 'RxCocoa Extension Library for Texture' 5 | 6 | s.description = 'This library is built on Texture with RxCocoa, RxCocoa is a framework that helps make Cocoa APIs used in iOS and OS X easier to use with reactive techniques.' 7 | 8 | s.homepage = 'https://github.com/RxSwiftCommunity/RxCocoa-Texture' 9 | s.license = { :type => 'MIT', :file => 'LICENSE' } 10 | s.author = { 'Geektree0101' => 'h2s1880@gmail.com' } 11 | s.source = { :git => 'https://github.com/RxSwiftCommunity/RxCocoa-Texture.git', :tag => s.version.to_s } 12 | 13 | s.ios.deployment_target = '10.0' 14 | s.swift_version = '5.0' 15 | s.source_files = 'RxCocoa-Texture/Classes/**/*' 16 | 17 | s.dependency 'RxSwift', '~> 6' 18 | s.dependency 'RxCocoa', '~> 6' 19 | s.dependency 'Texture', '~> 3' 20 | end 21 | -------------------------------------------------------------------------------- /RxCocoa-Texture/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxSwiftCommunity/RxCocoa-Texture/ced5b7b183587282f073dafbb41699dc464378ae/RxCocoa-Texture/Assets/.gitkeep -------------------------------------------------------------------------------- /RxCocoa-Texture/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxSwiftCommunity/RxCocoa-Texture/ced5b7b183587282f073dafbb41699dc464378ae/RxCocoa-Texture/Classes/.gitkeep -------------------------------------------------------------------------------- /RxCocoa-Texture/Classes/ASBinder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASBinder.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import AsyncDisplayKit 9 | import RxSwift 10 | import RxCocoa 11 | 12 | public struct ASBinder: ASObserverType { 13 | 14 | public typealias E = Value 15 | private let _binding: (Event, ASDisplayNode?) -> () 16 | private var _directlyBinding: (Value?) -> () 17 | 18 | public init(_ target: Target, 19 | scheduler: ImmediateSchedulerType = MainScheduler(), 20 | binding: @escaping (Target, Value) -> ()) { 21 | 22 | weak var weakTarget = target 23 | 24 | _directlyBinding = { value in 25 | if let target = weakTarget, 26 | let `value` = value { 27 | binding(target, value) 28 | } 29 | } 30 | 31 | _binding = { event, node in 32 | switch event { 33 | case .next(let element): 34 | _ = scheduler.schedule(element) { element in 35 | if let target = weakTarget { 36 | ASPerformBlockOnMainThread { 37 | binding(target, element) 38 | } 39 | } 40 | node?.rx_setNeedsLayout() 41 | return Disposables.create() 42 | } 43 | case .error(let error): 44 | #if DEBUG 45 | fatalError(error.localizedDescription) 46 | #else 47 | print(error) 48 | #endif 49 | case .completed: 50 | break 51 | } 52 | } 53 | } 54 | 55 | public func on(_ event: Event, node: ASDisplayNode?) { 56 | 57 | _binding(event, node) 58 | } 59 | 60 | public func on(_ event: Event) { 61 | 62 | _binding(event, nil) 63 | } 64 | 65 | public func directlyBinding(_ element: Value?) { 66 | 67 | _directlyBinding(element) 68 | } 69 | } 70 | 71 | public protocol ASObserverType: ObserverType { 72 | 73 | func on(_ event: Event, node: ASDisplayNode?) 74 | func directlyBinding(_ element: Element?) 75 | } 76 | 77 | extension ObservableType { 78 | 79 | public func bind( 80 | to relays: PublishRelay..., 81 | setNeedsLayout node: ASDisplayNode?) -> Disposable { 82 | 83 | return bind(to: relays, setNeedsLayout: node) 84 | } 85 | 86 | public func bind( 87 | to relays: PublishRelay..., 88 | setNeedsLayout node: ASDisplayNode?) -> Disposable { 89 | 90 | return self.map { $0 as Element? }.bind(to: relays, setNeedsLayout: node) 91 | } 92 | 93 | private func bind( 94 | to relays: [PublishRelay], 95 | setNeedsLayout node: ASDisplayNode?) -> Disposable { 96 | 97 | weak var weakNode = node 98 | 99 | return subscribe { e in 100 | switch e { 101 | case let .next(element): 102 | relays.forEach { 103 | $0.accept(element) 104 | } 105 | weakNode?.rx_setNeedsLayout() 106 | case let .error(error): 107 | let log = "Binding error to behavior relay: \(error)" 108 | #if DEBUG 109 | fatalError(log) 110 | #else 111 | print(log) 112 | #endif 113 | case .completed: 114 | break 115 | } 116 | } 117 | } 118 | 119 | public func bind( 120 | to relays: BehaviorRelay..., 121 | setNeedsLayout node: ASDisplayNode?) -> Disposable { 122 | 123 | return self.bind(to: relays, setNeedsLayout: node) 124 | } 125 | 126 | public func bind( 127 | to relays: BehaviorRelay..., 128 | setNeedsLayout node: ASDisplayNode?) -> Disposable { 129 | 130 | return self.map { $0 as Element? }.bind(to: relays, setNeedsLayout: node) 131 | } 132 | 133 | private func bind( 134 | to relays: [BehaviorRelay], 135 | setNeedsLayout node: ASDisplayNode?) -> Disposable { 136 | 137 | weak var weakNode = node 138 | 139 | return subscribe { e in 140 | switch e { 141 | case let .next(element): 142 | relays.forEach { 143 | $0.accept(element) 144 | } 145 | weakNode?.rx_setNeedsLayout() 146 | case let .error(error): 147 | let log = "Binding error to behavior relay: \(error)" 148 | #if DEBUG 149 | fatalError(log) 150 | #else 151 | print(log) 152 | #endif 153 | case .completed: 154 | break 155 | } 156 | } 157 | } 158 | } 159 | 160 | extension ObservableType { 161 | 162 | public func bind( 163 | to observers: Observer..., 164 | setNeedsLayout node: ASDisplayNode? = nil) -> Disposable where Observer.Element == Element { 165 | 166 | return self.bind( 167 | to: observers, 168 | setNeedsLayout: node 169 | ) 170 | } 171 | 172 | public func bind( 173 | to observers: Observer..., 174 | setNeedsLayout node: ASDisplayNode? = nil) -> Disposable where Observer.Element == Element? { 175 | 176 | return self.map { $0 as Element? } 177 | .bind( 178 | to: observers, 179 | setNeedsLayout: node 180 | ) 181 | } 182 | 183 | private func bind( 184 | to observers: [Observer], 185 | setNeedsLayout node: ASDisplayNode? = nil) -> Disposable where Observer.Element == Element { 186 | 187 | weak var weakNode = node 188 | 189 | return self.subscribe { event in 190 | observers.forEach { 191 | $0.on(event, node: weakNode) 192 | } 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /RxCocoa-Texture/Classes/ASButtonNode+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASnode+Rx.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import AsyncDisplayKit 9 | import RxSwift 10 | import RxCocoa 11 | import UIKit 12 | 13 | /** refer 14 | public static var normal: UIControlState { get } 15 | 16 | public static var highlighted: UIControlState { get } // used when UIControl isHighlighted is set 17 | 18 | public static var disabled: UIControlState { get } 19 | 20 | public static var selected: UIControlState { get } // flag usable by app (see below) 21 | **/ 22 | 23 | extension Reactive where Base: ASButtonNode { 24 | 25 | // apply attributedText on all control state 26 | public var attributedText: ASBinder { 27 | 28 | return ASBinder(self.base) { node, attributedText in 29 | self.setAllAttributedTitle(node, attributedText) 30 | } 31 | } 32 | 33 | // apply attributedText on targeted control state 34 | public func attributedText(_ controlState: UIControl.State) -> ASBinder { 35 | 36 | return ASBinder(self.base) { node, attributedText in 37 | node.setAttributedTitle(attributedText, for: controlState) 38 | } 39 | } 40 | 41 | // apply text with attribute on all control state 42 | public func text(_ attribute: [NSAttributedString.Key: Any]?) -> ASBinder { 43 | 44 | return ASBinder(self.base) { node, text in 45 | guard let text = text else { 46 | self.setAllAttributedTitle(node, nil) 47 | return 48 | } 49 | let attrText = NSAttributedString(string: text, 50 | attributes: attribute) 51 | self.setAllAttributedTitle(node, attrText) 52 | } 53 | } 54 | 55 | // apply text with attribute on targeted control state 56 | public func text(_ attribute: [NSAttributedString.Key: Any]?, 57 | target: UIControl.State) -> ASBinder { 58 | 59 | return ASBinder(self.base) { node, text in 60 | guard let text = text else { 61 | node.setAttributedTitle(nil, for: target) 62 | return 63 | } 64 | let attrText = NSAttributedString(string: text, 65 | attributes: attribute) 66 | node.setAttributedTitle(attrText, for: target) 67 | } 68 | } 69 | 70 | // apply text with attributes 71 | public func text(applyList: [ASControlStateType]) -> ASBinder { 72 | 73 | return ASBinder(self.base) { node, text in 74 | guard let text = text else { 75 | for apply in applyList { 76 | node.setAttributedTitle(nil, 77 | for: apply.state) 78 | } 79 | return 80 | } 81 | 82 | for apply in applyList { 83 | let attrText = NSAttributedString(string: text, 84 | attributes: apply.attributes) 85 | node.setAttributedTitle(attrText, 86 | for: apply.state) 87 | } 88 | } 89 | } 90 | 91 | public var image: ASBinder { 92 | 93 | return ASBinder(self.base) { node, image in 94 | self.setAllImage(node, image: image) 95 | } 96 | } 97 | 98 | public var backgroundImage: ASBinder { 99 | 100 | return ASBinder(self.base) { node, image in 101 | self.setAllBackgroundImage(node, image: image) 102 | } 103 | } 104 | 105 | public func image(applyList: [ASControlStateType]) -> ASBinder { 106 | 107 | return ASBinder(self.base) { node, image in 108 | 109 | guard let image = image else { 110 | for apply in applyList { 111 | node.setImage(nil, for: apply.state) 112 | } 113 | return 114 | } 115 | 116 | for apply in applyList { 117 | node.setImage(apply.image ?? image, 118 | for: apply.state) 119 | } 120 | } 121 | } 122 | 123 | public func backgroundImage(applyList: [ASControlStateType]) -> ASBinder { 124 | return ASBinder(self.base) { node, image in 125 | 126 | guard let image = image else { 127 | for apply in applyList { 128 | node.setBackgroundImage(nil, for: apply.state) 129 | } 130 | return 131 | } 132 | 133 | for apply in applyList { 134 | node.setBackgroundImage(apply.image ?? image, 135 | for: apply.state) 136 | } 137 | } 138 | } 139 | 140 | public enum ASControlStateType { 141 | 142 | case normal(Any?) 143 | case highlighted(Any?) 144 | case disabled(Any?) 145 | case selected(Any?) 146 | 147 | var state: UIControl.State { 148 | 149 | switch self { 150 | case .normal: 151 | return .normal 152 | case .highlighted: 153 | return .highlighted 154 | case .disabled: 155 | return .disabled 156 | case .selected: 157 | return .selected 158 | } 159 | } 160 | 161 | var url: URL? { 162 | 163 | switch self { 164 | case .normal(let attr): 165 | return attr as? URL 166 | case .highlighted(let attr): 167 | return attr as? URL 168 | case .disabled(let attr): 169 | return attr as? URL 170 | case .selected(let attr): 171 | return attr as? URL 172 | } 173 | } 174 | 175 | var image: UIImage? { 176 | 177 | switch self { 178 | case .normal(let attr): 179 | return attr as? UIImage 180 | case .highlighted(let attr): 181 | return attr as? UIImage 182 | case .disabled(let attr): 183 | return attr as? UIImage 184 | case .selected(let attr): 185 | return attr as? UIImage 186 | } 187 | } 188 | 189 | var attributes: [NSAttributedString.Key: Any]? { 190 | 191 | switch self { 192 | case .normal(let attr): 193 | return attr as? [NSAttributedString.Key: Any] 194 | case .highlighted(let attr): 195 | return attr as? [NSAttributedString.Key: Any] 196 | case .disabled(let attr): 197 | return attr as? [NSAttributedString.Key: Any] 198 | case .selected(let attr): 199 | return attr as? [NSAttributedString.Key: Any] 200 | } 201 | } 202 | } 203 | 204 | private func setAllAttributedTitle(_ node: ASButtonNode, 205 | _ attrText: NSAttributedString?) { 206 | 207 | node.setAttributedTitle(attrText, for: .normal) 208 | node.setAttributedTitle(attrText, for: .selected) 209 | node.setAttributedTitle(attrText, for: .highlighted) 210 | node.setAttributedTitle(attrText, for: .disabled) 211 | } 212 | 213 | private func setAllImage(_ node: ASButtonNode, image: UIImage?) { 214 | 215 | node.setImage(image, for: .normal) 216 | node.setImage(image, for: .selected) 217 | node.setImage(image, for: .highlighted) 218 | node.setImage(image, for: .disabled) 219 | } 220 | 221 | private func setAllBackgroundImage(_ node: ASButtonNode, image: UIImage?) { 222 | 223 | node.setBackgroundImage(image, for: .normal) 224 | node.setBackgroundImage(image, for: .selected) 225 | node.setBackgroundImage(image, for: .highlighted) 226 | node.setBackgroundImage(image, for: .disabled) 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /RxCocoa-Texture/Classes/ASControlNode+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASControlNode+Rx.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | 9 | import AsyncDisplayKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | extension Reactive where Base: ASControlNode { 14 | 15 | /// Reactive wrapper for target action pattern. 16 | /// 17 | /// - parameter controlEvents: Filter for observed ASControlNodeEvent types. 18 | public func controlEvent(_ controlEvents: ASControlNodeEvent) -> ControlEvent { 19 | 20 | let source: Observable = Observable 21 | .create { [weak control = self.base] observer in 22 | MainScheduler.ensureExecutingOnScheduler() 23 | 24 | guard let control = control else { 25 | observer.on(.completed) 26 | return Disposables.create() 27 | } 28 | 29 | let controlTarget = ASControlTarget(control, controlEvents) { control in 30 | observer.on(.next(control)) 31 | } 32 | 33 | return Disposables.create(with: controlTarget.dispose) 34 | } 35 | .takeUntil(deallocated) 36 | 37 | return ControlEvent(events: source) 38 | } 39 | 40 | /// Creates a `ControlProperty` that is triggered by target/action pattern value updates. 41 | /// 42 | /// - parameter controlEvents: ASControlNodeEvents that trigger value update sequence elements. 43 | /// - parameter getter: Property value getter. 44 | /// - parameter setter: Property value setter. 45 | public func controlProperty( 46 | editingEvents: ASControlNodeEvent, 47 | getter: @escaping (Base) -> T, 48 | setter: @escaping (Base, T) -> () 49 | ) -> ControlProperty { 50 | let source: Observable = Observable.create { [weak weakControl = base] observer in 51 | guard let control = weakControl else { 52 | observer.on(.completed) 53 | return Disposables.create() 54 | } 55 | 56 | observer.on(.next(getter(control))) 57 | 58 | let controlTarget = ASControlTarget(control, editingEvents) { _ in 59 | if let control = weakControl { 60 | observer.on(.next(getter(control))) 61 | } 62 | } 63 | 64 | return Disposables.create(with: controlTarget.dispose) 65 | } 66 | .takeUntil(deallocated) 67 | 68 | let bindingObserver = ASBinder(base, binding: setter) 69 | 70 | return ControlProperty(values: source, valueSink: bindingObserver) 71 | } 72 | 73 | public var tap: ControlEvent { 74 | 75 | let source = self.controlEvent(.touchUpInside).map { _ in return } 76 | return ControlEvent(events: source) 77 | } 78 | 79 | public func tap(to relay: PublishRelay<()>) -> Disposable { 80 | 81 | return self.controlEvent(.touchUpInside) 82 | .map { _ in return } 83 | .asSignal(onErrorJustReturn: ()) 84 | .emit(to: relay) 85 | } 86 | 87 | public var isHidden: ASBinder { 88 | 89 | return ASBinder(self.base) { node, isHidden in 90 | node.isHidden = isHidden 91 | } 92 | } 93 | 94 | public var isEnabled: ASBinder { 95 | 96 | return ASBinder(self.base) { node, isEnabled in 97 | node.isEnabled = isEnabled 98 | } 99 | } 100 | 101 | public var isHighlighted: ControlProperty { 102 | 103 | return self.controlProperty( 104 | editingEvents: [.touchDown, .touchUpInside, .touchCancel], 105 | getter: { control in 106 | control.isHighlighted 107 | }, 108 | setter: { control, isHighlighted in 109 | control.isHighlighted = isHighlighted 110 | } 111 | ) 112 | } 113 | 114 | public var isSelected: ASBinder { 115 | 116 | return ASBinder(self.base) { node, isSelected in 117 | node.isSelected = isSelected 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /RxCocoa-Texture/Classes/ASControlTarget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASControlTarget.swift 3 | // RxCocoa-Texture 4 | // 5 | // Created by KanghoonOh on 20/05/2019. 6 | // 7 | 8 | import AsyncDisplayKit 9 | import RxSwift 10 | import RxCocoa 11 | 12 | // This should be only used from `MainScheduler` 13 | final class ASControlTarget: _RXKVOObserver, Disposable { 14 | 15 | typealias Callback = (Control) -> Void 16 | 17 | let selector = #selector(eventHandler(_:)) 18 | 19 | weak var controlNode: Control? 20 | let controlEvents: ASControlNodeEvent 21 | var callback: Callback? 22 | 23 | init(_ controlNode: Control, 24 | _ controlEvents: ASControlNodeEvent, 25 | callback: @escaping Callback) { 26 | 27 | self.controlNode = controlNode 28 | self.controlEvents = controlEvents 29 | self.callback = callback 30 | 31 | super.init() 32 | 33 | controlNode.addTarget(self, 34 | action: selector, 35 | forControlEvents: controlEvents) 36 | 37 | let method = self.method(for: selector) 38 | if method == nil { 39 | fatalError("Can't find method") 40 | } 41 | } 42 | 43 | @objc func eventHandler(_ sender: UIGestureRecognizer) { 44 | 45 | if let callback = self.callback, let controlNode = self.controlNode { 46 | callback(controlNode) 47 | } 48 | } 49 | 50 | override func dispose() { 51 | 52 | super.dispose() 53 | self.controlNode?.removeTarget(self, 54 | action: selector, 55 | forControlEvents: .allEvents) 56 | self.callback = nil 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /RxCocoa-Texture/Classes/ASDisplayNode+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASDisplayNode+Rx.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import AsyncDisplayKit 9 | import RxSwift 10 | import RxCocoa 11 | import UIKit 12 | 13 | extension Reactive where Base: ASDisplayNode { 14 | 15 | public var alpha: ASBinder { 16 | return ASBinder(self.base) { node, alpha in 17 | node.alpha = alpha 18 | } 19 | } 20 | 21 | public var backgroundColor: ASBinder { 22 | return ASBinder(self.base) { node, backgroundColor in 23 | node.backgroundColor = backgroundColor 24 | } 25 | } 26 | 27 | public var didLoad: Observable { 28 | 29 | return self.methodInvoked(#selector(Base.didLoad)) 30 | .map { _ in return } 31 | .asObservable() 32 | } 33 | 34 | public var isHidden: ASBinder { 35 | 36 | return ASBinder(self.base) { node, isHidden in 37 | node.isHidden = isHidden 38 | } 39 | } 40 | 41 | public var isUserInteractionEnabled: ASBinder { 42 | return ASBinder(self.base) { node, isUserInteractionEnabled in 43 | node.isUserInteractionEnabled = isUserInteractionEnabled 44 | } 45 | } 46 | 47 | public var setNeedsLayout: Binder { 48 | 49 | return Binder(self.base) { node, _ in 50 | node.rx_setNeedsLayout() 51 | } 52 | } 53 | } 54 | 55 | extension Reactive where Base: ASDisplayNode { 56 | 57 | public var didEnterPreloadState: ControlEvent { 58 | 59 | let source = self.methodInvoked(#selector(Base.didEnterPreloadState)).map { _ in return } 60 | return ControlEvent(events: source) 61 | } 62 | 63 | public var didEnterDisplayState: ControlEvent { 64 | 65 | let source = self.methodInvoked(#selector(Base.didEnterDisplayState)).map { _ in return } 66 | return ControlEvent(events: source) 67 | } 68 | 69 | public var didEnterVisibleState: ControlEvent { 70 | 71 | let source = self.methodInvoked(#selector(Base.didEnterVisibleState)).map { _ in return } 72 | return ControlEvent(events: source) 73 | } 74 | 75 | public var didExitVisibleState: ControlEvent { 76 | 77 | let source = self.methodInvoked(#selector(Base.didExitVisibleState)).map { _ in return } 78 | return ControlEvent(events: source) 79 | } 80 | 81 | public var didExitDisplayState: ControlEvent { 82 | 83 | let source = self.methodInvoked(#selector(Base.didExitDisplayState)).map { _ in return } 84 | return ControlEvent(events: source) 85 | } 86 | 87 | public var didExitPreloadState: ControlEvent { 88 | 89 | let source = self.methodInvoked(#selector(Base.didEnterPreloadState)).map { _ in return } 90 | return ControlEvent(events: source) 91 | } 92 | } 93 | 94 | extension Reactive where Base: ASDisplayNode { 95 | 96 | public var width: ASBinder { 97 | return ASBinder(self.base) { node, width in 98 | node.style.width = width 99 | } 100 | } 101 | 102 | public var minWidth: ASBinder { 103 | return ASBinder(self.base) { node, minWidth in 104 | node.style.minWidth = minWidth 105 | } 106 | } 107 | 108 | public var maxWidth: ASBinder { 109 | return ASBinder(self.base) { node, maxWidth in 110 | node.style.maxWidth = maxWidth 111 | } 112 | } 113 | 114 | public var height: ASBinder { 115 | return ASBinder(self.base) { node, height in 116 | node.style.height = height 117 | } 118 | } 119 | 120 | public var minHeight: ASBinder { 121 | return ASBinder(self.base) { node, minHeight in 122 | node.style.minHeight = minHeight 123 | } 124 | } 125 | 126 | public var maxHeight: ASBinder { 127 | return ASBinder(self.base) { node, maxHeight in 128 | node.style.maxHeight = maxHeight 129 | } 130 | } 131 | 132 | public var preferredSize: ASBinder { 133 | return ASBinder(self.base) { node, preferredSize in 134 | node.style.preferredSize = preferredSize 135 | } 136 | } 137 | 138 | public var minSize: ASBinder { 139 | return ASBinder(self.base) { node, minSize in 140 | node.style.minSize = minSize 141 | } 142 | } 143 | 144 | public var maxSize: ASBinder { 145 | return ASBinder(self.base) { node, maxSize in 146 | node.style.maxSize = maxSize 147 | } 148 | } 149 | } 150 | 151 | extension ASDisplayNode { 152 | 153 | /** 154 | setNeedsLayout with avoid block layout measure passing before node loaded 155 | 156 | - important: Block layout measure passing from rx.subscribe 157 | 158 | - returns: Void 159 | */ 160 | public func rx_setNeedsLayout() { 161 | 162 | if self.isNodeLoaded { 163 | self.setNeedsLayout() 164 | } else { 165 | self.layoutIfNeeded() 166 | self.invalidateCalculatedLayout() 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /RxCocoa-Texture/Classes/ASEditableTextNode+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASEditableTextNode+Rx.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import AsyncDisplayKit 9 | import RxSwift 10 | import RxCocoa 11 | 12 | extension Reactive where Base: ASEditableTextNode { 13 | 14 | public var delegate: DelegateProxy { 15 | return RxASEditableTextNodeDelegateProxy.proxy(for: base) 16 | } 17 | 18 | public var attributedText: ControlProperty { 19 | 20 | let source: Observable = Observable.deferred { [weak editableTextNode = self.base] in 21 | let attrText = editableTextNode?.attributedText 22 | 23 | let textChanged: Observable = editableTextNode?.rx.delegate.methodInvoked(#selector(ASEditableTextNodeDelegate.editableTextNodeDidUpdateText(_:))) 24 | .observeOn(MainScheduler.asyncInstance) 25 | .map { _ in 26 | return editableTextNode?.attributedText 27 | } 28 | ?? .empty() 29 | 30 | return textChanged.startWith(attrText) 31 | } 32 | 33 | let bindingObserver = ASBinder(self.base) { node, attributedText in 34 | node.attributedText = attributedText 35 | } 36 | 37 | return ControlProperty(values: source, valueSink: bindingObserver) 38 | } 39 | 40 | public func text(_ attributes: [NSAttributedString.Key: Any]?) -> ASBinder { 41 | 42 | return ASBinder(self.base) { node, text in 43 | guard let text = text else { 44 | node.attributedText = nil 45 | return 46 | } 47 | 48 | node.attributedText = NSAttributedString(string: text, 49 | attributes: attributes) 50 | } 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /RxCocoa-Texture/Classes/ASImageNode+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASImageNode+Rx.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import AsyncDisplayKit 9 | import RxSwift 10 | import RxCocoa 11 | 12 | extension Reactive where Base: ASImageNode { 13 | 14 | public var image: ASBinder { 15 | 16 | return ASBinder(self.base) { node, image in 17 | node.image = image 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RxCocoa-Texture/Classes/ASNetworkImageNode+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASNetworkImageNode+Rx.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import AsyncDisplayKit 9 | import RxSwift 10 | import RxCocoa 11 | 12 | extension Reactive where Base: ASNetworkImageNode { 13 | 14 | public var url: ASBinder { 15 | 16 | return ASBinder(self.base) { node, url in 17 | node.setURL(url, resetToDefault: true) 18 | } 19 | } 20 | 21 | public func url(resetToDefault: Bool) -> ASBinder { 22 | 23 | return ASBinder(self.base) { node, url in 24 | node.setURL(url, resetToDefault: resetToDefault) 25 | } 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /RxCocoa-Texture/Classes/ASTextNode+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASTextNode+Rx.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import AsyncDisplayKit 9 | import RxSwift 10 | import RxCocoa 11 | 12 | extension Reactive where Base: ASTextNode { 13 | 14 | public var attributedText: ASBinder { 15 | 16 | return ASBinder(self.base) { node, attributedText in 17 | node.attributedText = attributedText 18 | } 19 | } 20 | 21 | public func text(_ attributes: [NSAttributedString.Key: Any]?) -> ASBinder { 22 | 23 | return ASBinder(self.base) { node, text in 24 | guard let text = text else { 25 | node.attributedText = nil 26 | return 27 | } 28 | 29 | node.attributedText = NSAttributedString(string: text, 30 | attributes: attributes) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RxCocoa-Texture/Classes/ASTextNode2+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASTextNode2+Rx.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import AsyncDisplayKit 9 | import RxSwift 10 | import RxCocoa 11 | 12 | #if (!AS_ENABLE_TEXTNODE) 13 | 14 | extension Reactive where Base: ASTextNode2 { 15 | 16 | public var attributedText: ASBinder { 17 | 18 | return ASBinder(self.base) { node, attributedText in 19 | node.attributedText = attributedText 20 | } 21 | } 22 | 23 | public func text(_ attributes: [NSAttributedString.Key: Any]?) -> ASBinder { 24 | 25 | return ASBinder(self.base) { node, text in 26 | guard let text = text else { 27 | node.attributedText = nil 28 | return 29 | } 30 | 31 | node.attributedText = NSAttributedString(string: text, 32 | attributes: attributes) 33 | } 34 | } 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /RxCocoa-Texture/Classes/Driver+ASSubscription.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Driver+ASSubscription.swift 3 | // 4 | // Created by Geektree0101. 5 | // Copyright © 2018 RxSwiftCommunity. All rights reserved. 6 | // 7 | 8 | import AsyncDisplayKit 9 | import RxSwift 10 | import RxCocoa 11 | 12 | private let errorMessage = "`drive*` family of methods can be only called from `MainThread`.\n" + 13 | "This is required to ensure that the last replayed `Driver` element is delivered on `MainThread`.\n" 14 | 15 | extension SharedSequenceConvertibleType where SharingStrategy == DriverSharingStrategy { 16 | 17 | public func drive(_ observer: O, 18 | directlyBind: Bool = false, 19 | setNeedsLayout node: ASDisplayNode? = nil) -> Disposable where O.Element == Element { 20 | MainScheduler.ensureExecutingOnScheduler(errorMessage: errorMessage) 21 | return self.asSharedSequence() 22 | .asObservable() 23 | .bind(to: observer, 24 | setNeedsLayout: node) 25 | } 26 | 27 | public func drive(_ observer: O, 28 | directlyBind: Bool = false, 29 | setNeedsLayout node: ASDisplayNode? = nil) -> Disposable where O.Element == Element? { 30 | MainScheduler.ensureExecutingOnScheduler(errorMessage: errorMessage) 31 | return self.asSharedSequence() 32 | .asObservable() 33 | .map { $0 as Element? } 34 | .bind(to: observer, 35 | setNeedsLayout: node) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /RxCocoa-Texture/Classes/RxASEditableTextNodeDelegateProxy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RxASEditableTextNodeDelegateProxy.swift 3 | // RxCocoa-Texture 4 | // 5 | // Created by KanghoonOh on 20/05/2019. 6 | // 7 | 8 | import AsyncDisplayKit 9 | import RxSwift 10 | import RxCocoa 11 | 12 | extension ASEditableTextNode: HasDelegate { 13 | public typealias Delegate = ASEditableTextNodeDelegate 14 | } 15 | 16 | open class RxASEditableTextNodeDelegateProxy 17 | : DelegateProxy 18 | , DelegateProxyType 19 | , ASEditableTextNodeDelegate { 20 | 21 | public weak private(set) var editableTextNode: ASEditableTextNode? 22 | 23 | public init(editableTextNode: ASEditableTextNode) { 24 | self.editableTextNode = editableTextNode 25 | super.init(parentObject: editableTextNode, 26 | delegateProxy: RxASEditableTextNodeDelegateProxy.self) 27 | } 28 | 29 | public static func registerKnownImplementations() { 30 | self.register { RxASEditableTextNodeDelegateProxy(editableTextNode: $0) } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /resources/ASMVI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxSwiftCommunity/RxCocoa-Texture/ced5b7b183587282f073dafbb41699dc464378ae/resources/ASMVI.png -------------------------------------------------------------------------------- /resources/asbinder_workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxSwiftCommunity/RxCocoa-Texture/ced5b7b183587282f073dafbb41699dc464378ae/resources/asbinder_workflow.png -------------------------------------------------------------------------------- /resources/badcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxSwiftCommunity/RxCocoa-Texture/ced5b7b183587282f073dafbb41699dc464378ae/resources/badcase.png -------------------------------------------------------------------------------- /resources/badcase2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxSwiftCommunity/RxCocoa-Texture/ced5b7b183587282f073dafbb41699dc464378ae/resources/badcase2.png -------------------------------------------------------------------------------- /resources/expect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxSwiftCommunity/RxCocoa-Texture/ced5b7b183587282f073dafbb41699dc464378ae/resources/expect.png -------------------------------------------------------------------------------- /resources/expect2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxSwiftCommunity/RxCocoa-Texture/ced5b7b183587282f073dafbb41699dc464378ae/resources/expect2.png -------------------------------------------------------------------------------- /resources/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxSwiftCommunity/RxCocoa-Texture/ced5b7b183587282f073dafbb41699dc464378ae/resources/flow.png -------------------------------------------------------------------------------- /resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxSwiftCommunity/RxCocoa-Texture/ced5b7b183587282f073dafbb41699dc464378ae/resources/logo.png --------------------------------------------------------------------------------