├── Resource
└── logo.png
├── Tests
├── RxDataSources-TextureTests
│ └── DummyTests.swift
└── LinuxMain.swift
├── Example
├── GithubRepos
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Repo.swift
│ ├── AppDelegate.swift
│ ├── GithubService.swift
│ ├── MainSection.swift
│ ├── Info.plist
│ ├── RepoViewModel.swift
│ ├── RepoCellNode.swift
│ ├── CategoryViewController.swift
│ ├── RepoTableViewController.swift
│ ├── RepoCollectionViewController.swift
│ └── Base.lproj
│ │ └── LaunchScreen.storyboard
├── Podfile
├── GithubRepos.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── project.pbxproj
└── GithubRepos.xcworkspace
│ └── contents.xcworkspacedata
├── .gitignore
├── Podfile
├── Makefile
├── Sources
└── RxDataSources-Texture
│ ├── NodeTransition.swift
│ ├── ASTableNode
│ ├── RxASTableDataSourceType.swift
│ ├── RxASTableSectionedReloadDataSource.swift
│ ├── RxASTableDataSourceProxy.swift
│ ├── RxASTableDelegateProxy.swift
│ ├── RxASTableSectionedAnimatedDataSource.swift
│ ├── ASTableSectionedDataSource.swift
│ └── ASTableNode+Rx.swift
│ ├── ASCollectionNode
│ ├── RxASCollectionDataSourceType.swift
│ ├── RxASCollectionSectionedReloadDataSource.swift
│ ├── RxASCollectionDataSourceProxy.swift
│ ├── RxASCollectionDelegateProxy.swift
│ ├── RxASCollectionSectionedAnimatedDataSource.swift
│ ├── ASCollectionSectionedDataSource.swift
│ └── ASCollectionNode+Rx.swift
│ ├── AnimationConfiguration.swift
│ ├── Array+Extensions.swift
│ ├── ASDelegateProxyType.swift
│ ├── RxASErrorHandler.swift
│ └── UI+SectionNodeType.swift
├── Package.resolved
├── Package.swift
├── .github
└── workflows
│ └── ci.yml
├── LICENSE
├── RxDataSources-Texture.podspec
├── Podfile.lock
└── README.md
/Resource/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OhKanghoon/RxDataSources-Texture/HEAD/Resource/logo.png
--------------------------------------------------------------------------------
/Tests/RxDataSources-TextureTests/DummyTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | final class DummyTests: XCTestCase {
4 | }
5 |
--------------------------------------------------------------------------------
/Example/GithubRepos/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import RxTextureTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += RxTextureTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /.swiftpm
4 | /Packages
5 | /*.xcodeproj
6 | /*.xcworkspace
7 | **/xcuserdata
8 | **/xcshareddata
9 | Pods/
10 | Carthage/
11 |
12 | Example/**/Podfile.lock
13 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '9.0'
2 | inhibit_all_warnings!
3 |
4 | target 'RxDataSources-Texture' do
5 | use_frameworks!
6 | pod 'Texture', '~> 3.0'
7 |
8 | target 'RxDataSources-TextureTests' do
9 | end
10 |
11 | end
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | project: clean
2 | swift package generate-xcodeproj --enable-code-coverage
3 | ruby -e "require 'xcodeproj'; Xcodeproj::Project.open('RxDataSources-Texture.xcodeproj').save" || true
4 | pod install
5 |
6 | clean:
7 | rm -rf Pods
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '9.0'
2 | use_frameworks!
3 | inhibit_all_warnings!
4 |
5 | target 'GithubRepos' do
6 | pod 'RxDataSources-Texture', :path => '../'
7 | pod 'RxOptional'
8 | pod 'Alamofire'
9 | pod 'RxAlamofire'
10 | end
--------------------------------------------------------------------------------
/Example/GithubRepos.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/GithubRepos.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/NodeTransition.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NodeTransition.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | /// Transition between two view states
9 | public enum NodeTransition {
10 | /// animated transition
11 | case animated
12 | /// refresh view without animations
13 | case reload
14 | }
15 |
--------------------------------------------------------------------------------
/Example/GithubRepos/Repo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Repo.swift
3 | // GithubRepos
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Repo: Codable {
12 |
13 | let id: Int
14 | let name: String
15 | let fullName: String
16 | let owner: User
17 |
18 | enum CodingKeys: String, CodingKey {
19 | case id = "id"
20 | case name = "name"
21 | case fullName = "full_name"
22 | case owner = "owner"
23 | }
24 | }
25 |
26 | struct User: Codable {
27 | let avatarURL: String
28 |
29 | enum CodingKeys: String, CodingKey {
30 | case avatarURL = "avatar_url"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "RxDataSources",
6 | "repositoryURL": "https://github.com/RxSwiftCommunity/RxDataSources.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "241c62e7b578b2346c8b60efdf31b4eb2eab2966",
10 | "version": "5.0.0"
11 | }
12 | },
13 | {
14 | "package": "RxSwift",
15 | "repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "c8742ed97fc2f0c015a5ea5eddefb064cd7532d2",
19 | "version": "6.0.0"
20 | }
21 | }
22 | ]
23 | },
24 | "version": 1
25 | }
26 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "RxDataSources-Texture",
7 | platforms: [
8 | .iOS(.v9)
9 | ],
10 | products: [
11 | .library(name: "RxDataSources-Texture", targets: ["RxDataSources-Texture"]),
12 | ],
13 | dependencies: [
14 | .package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "6.0.0")),
15 | .package(url: "https://github.com/RxSwiftCommunity/RxDataSources.git", .upToNextMajor(from: "5.0.0")),
16 | ],
17 | targets: [
18 | .target(name: "RxDataSources-Texture", dependencies: ["Differentiator", "RxSwift", "RxCocoa"]),
19 | .testTarget(name: "RxDataSources-TextureTests", dependencies: ["RxDataSources-Texture"]),
20 | ]
21 | )
22 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASTableNode/RxASTableDataSourceType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxASTableDataSourceType.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 |
10 | import Foundation
11 | import AsyncDisplayKit
12 | import RxSwift
13 |
14 | /// Marks data source as `ASTableNode` reactive data source enabling it to be used with one of the `bindTo` methods.
15 | public protocol RxASTableDataSourceType /*: ASTableDataSource*/ {
16 |
17 | /// Type of elements that can be bound to table node.
18 | associatedtype Element
19 |
20 | /// New observable sequence event observed.
21 | ///
22 | /// - parameter tableView: Bound table node.
23 | /// - parameter observedEvent: Event
24 | func tableNode(_ tableNode: ASTableNode, observedEvent: Event) -> Void
25 | }
26 |
27 | #endif
28 |
--------------------------------------------------------------------------------
/Example/GithubRepos/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // GithubRepos
4 | //
5 | // Created by ohkanghoon on 2020/03/31.
6 | // Copyright © 2020 kanghoon. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 | var window: UIWindow?
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
16 | window = UIWindow(frame: UIScreen.main.bounds)
17 | window?.backgroundColor = .white
18 | window?.makeKeyAndVisible()
19 |
20 | let viewController = CategoryViewController()
21 |
22 | let navigationController = UINavigationController(rootViewController: viewController)
23 | window?.rootViewController = navigationController
24 | return true
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASCollectionNode/RxASCollectionDataSourceType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxASCollectionDataSourceType.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 |
10 | import Foundation
11 | import AsyncDisplayKit
12 | import RxSwift
13 |
14 | /// Marks data source as `ASCollectionNode` reactive data source enabling it to be used with one of the `bindTo` methods.
15 | public protocol RxASCollectionDataSourceType /*: ASCollectionDataSource*/ {
16 |
17 | /// Type of elements that can be bound to table node.
18 | associatedtype Element
19 |
20 | /// New observable sequence event observed.
21 | ///
22 | /// - parameter collectionNode: Bound collection node.
23 | /// - parameter observedEvent: Event
24 | func collectionNode(_ collectionNode: ASCollectionNode, observedEvent: Event) -> Void
25 | }
26 |
27 | #endif
28 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASTableNode/RxASTableSectionedReloadDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxASTableSectionedReloadDataSource.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 | import Foundation
10 | import AsyncDisplayKit
11 | #if !RX_NO_MODULE
12 | import RxSwift
13 | import RxCocoa
14 | #endif
15 | import Differentiator
16 |
17 | open class RxASTableSectionedReloadDataSource
18 | : ASTableSectionedDataSource
19 | , RxASTableDataSourceType {
20 | public typealias Element = [S]
21 |
22 | open func tableNode(_ tableNode: ASTableNode, observedEvent: Event) {
23 | Binder(self) { dataSource, element in
24 | #if DEBUG
25 | dataSource._dataSourceBound = true
26 | #endif
27 | dataSource.setSections(element)
28 | tableNode.reloadData()
29 | }.on(observedEvent)
30 | }
31 | }
32 | #endif
33 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | jobs:
10 | test:
11 | runs-on: macOS-latest
12 | env:
13 | WORKSPACE: RxDataSources-Texture.xcworkspace
14 | SCHEME: RxDataSources-Texture-Package
15 | SDK: iphonesimulator
16 | DESTINATION: platform=iOS Simulator,name=iPhone 11 Pro,OS=latest
17 |
18 | steps:
19 | - uses: actions/checkout@v1
20 |
21 | - name: Generate Xcode Project
22 | run: make project
23 |
24 | - name: Build and Test
25 | run: |
26 | set -o pipefail && xcodebuild clean build test \
27 | -workspace "$WORKSPACE" \
28 | -scheme "$SCHEME" \
29 | -sdk "$SDK" \
30 | -destination "$DESTINATION" \
31 | -configuration Debug \
32 | -enableCodeCoverage YES \
33 | CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty -c
34 |
--------------------------------------------------------------------------------
/Example/GithubRepos/GithubService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GithubService.swift
3 | // GithubRepos
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | import RxAlamofire
12 |
13 | protocol GithubServiceProtocol {
14 | func repositories(since: Int?) -> Single<[Repo]>
15 | }
16 |
17 | final class GithubService: GithubServiceProtocol {
18 |
19 | init() {}
20 |
21 | func repositories(since: Int?) -> Single<[Repo]> {
22 | return RxAlamofire.request(
23 | .get,
24 | "https://api.github.com/repositories",
25 | parameters: since.map { ["since": $0] }
26 | )
27 | .validate(statusCode: 200..<300)
28 | .responseData()
29 | .debug()
30 | .map { (_, data) -> [Repo] in
31 | let decoder = JSONDecoder()
32 | return try decoder.decode([Repo].self, from: data)
33 | }
34 | .asSingle()
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASCollectionNode/RxASCollectionSectionedReloadDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxASCollectionSectionedReloadDataSource.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 21/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 | import Foundation
10 | import AsyncDisplayKit
11 | #if !RX_NO_MODULE
12 | import RxSwift
13 | import RxCocoa
14 | #endif
15 | import Differentiator
16 |
17 | open class RxASCollectionSectionedReloadDataSource
18 | : ASCollectionSectionedDataSource
19 | , RxASCollectionDataSourceType {
20 |
21 | public typealias Element = [S]
22 |
23 | open func collectionNode(_ collectionNode: ASCollectionNode, observedEvent: Event<[S]>) {
24 | Binder(self) { dataSource, element in
25 | #if DEBUG
26 | dataSource._dataSourceBound = true
27 | #endif
28 | dataSource.setSections(element)
29 | collectionNode.reloadData()
30 | collectionNode.collectionViewLayout.invalidateLayout()
31 | }.on(observedEvent)
32 | }
33 | }
34 | #endif
35 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/AnimationConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnimationConfiguration.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 | import Foundation
10 | import UIKit
11 |
12 | /**
13 | Exposes custom animation styles for insertion, deletion and reloading behavior.
14 | */
15 | public struct AnimationConfiguration {
16 | public let animated: Bool
17 | public let insertAnimation: UITableViewRowAnimation
18 | public let reloadAnimation: UITableViewRowAnimation
19 | public let deleteAnimation: UITableViewRowAnimation
20 |
21 | public init(animated: Bool = true,
22 | insertAnimation: UITableViewRowAnimation = .automatic,
23 | reloadAnimation: UITableViewRowAnimation = .automatic,
24 | deleteAnimation: UITableViewRowAnimation = .automatic) {
25 | self.animated = animated
26 | self.insertAnimation = insertAnimation
27 | self.reloadAnimation = reloadAnimation
28 | self.deleteAnimation = deleteAnimation
29 | }
30 | }
31 | #endif
32 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/Array+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+Extensions.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 | import Foundation
10 | import Differentiator
11 |
12 | extension Array where Element: SectionModelType {
13 | mutating func moveFromSourceIndexPath(_ sourceIndexPath: IndexPath, destinationIndexPath: IndexPath) {
14 | let sourceSection = self[sourceIndexPath.section]
15 | var sourceItems = sourceSection.items
16 |
17 | let sourceItem = sourceItems.remove(at: sourceIndexPath.item)
18 |
19 | let sourceSectionNew = Element(original: sourceSection, items: sourceItems)
20 | self[sourceIndexPath.section] = sourceSectionNew
21 |
22 | let destinationSection = self[destinationIndexPath.section]
23 | var destinationItems = destinationSection.items
24 | destinationItems.insert(sourceItem, at: destinationIndexPath.item)
25 |
26 | self[destinationIndexPath.section] = Element(original: destinationSection, items: destinationItems)
27 | }
28 | }
29 | #endif
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2019 OhKanghoon
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Example/GithubRepos/MainSection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainSection.swift
3 | // GithubRepos
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import RxDataSources_Texture
10 |
11 | enum MainSection {
12 | case repo(repos: [MainSectionItem])
13 | }
14 |
15 | extension MainSection: AnimatableSectionModelType {
16 |
17 | typealias Identity = String
18 |
19 | var identity: String {
20 | switch self {
21 | case .repo: return "repo"
22 | }
23 | }
24 |
25 | var items: [MainSectionItem] {
26 | switch self {
27 | case .repo(let items): return items
28 | }
29 | }
30 |
31 | init(original: MainSection, items: [MainSectionItem]) {
32 | switch original {
33 | case .repo: self = .repo(repos: items)
34 | }
35 | }
36 | }
37 |
38 | enum MainSectionItem {
39 | case repo(Repo)
40 | }
41 |
42 | extension MainSectionItem: IdentifiableType {
43 | typealias Identity = Int
44 |
45 | var identity: Int {
46 | switch self {
47 | case .repo(let repo):
48 | return repo.id
49 | }
50 | }
51 | }
52 |
53 | extension MainSectionItem: Equatable {
54 | static func == (lhs: MainSectionItem, rhs: MainSectionItem) -> Bool {
55 | return lhs.identity == rhs.identity
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/RxDataSources-Texture.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint RxTextureDataSources.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'RxDataSources-Texture'
11 | s.version = '1.4.0'
12 | s.summary = 'RxDataSources With Texture'
13 | s.description = <<-DESC
14 | This is a collection of reactive data sources for ASTableNode and ASCollectionNode
15 | DESC
16 | s.homepage = 'https://github.com/OhKanghoon/RxDataSources-Texture'
17 | s.license = { :type => 'MIT', :file => 'LICENSE' }
18 | s.author = { 'OhKanghoon' => 'ggaa96@naver.com' }
19 | s.source = { :git => 'https://github.com/OhKanghoon/RxDataSources-Texture.git', :tag => s.version.to_s }
20 |
21 | s.ios.deployment_target = '9.0'
22 | s.requires_arc = true
23 | s.swift_version = '5.0'
24 |
25 | s.source_files = 'Sources/**/*.{swift,h,m}'
26 |
27 | s.dependency 'RxSwift', '~> 6.0'
28 | s.dependency 'RxCocoa', '~> 6.0'
29 | s.dependency 'Differentiator', '~> 5.0'
30 | s.dependency 'Texture', '~> 3.0'
31 | end
32 |
--------------------------------------------------------------------------------
/Example/GithubRepos/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Example/GithubRepos/RepoViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RepoViewModel.swift
3 | // GithubRepos
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AsyncDisplayKit
11 | import RxSwift
12 | import RxRelay
13 |
14 | final class RepoViewModel {
15 |
16 | let refreshRelay = PublishRelay()
17 | let loadMoreRelay = PublishRelay()
18 |
19 | let repos = BehaviorRelay<[Repo]?>(value: nil)
20 | let since = BehaviorRelay(value: nil)
21 | let sections: Observable<[MainSection]>
22 |
23 | let disposeBag = DisposeBag()
24 |
25 | init(githubService: GithubServiceProtocol) {
26 | self.sections = self.repos
27 | .filterNil()
28 | .map { [MainSection.repo(repos: $0.map { MainSectionItem.repo($0) })] }
29 | .asObservable()
30 |
31 | self.repos.map { $0?.last?.id }
32 | .filterNil()
33 | .bind(to: since)
34 | .disposed(by: disposeBag)
35 |
36 | self.refreshRelay
37 | .flatMap { githubService.repositories(since: nil).catchErrorJustReturn([]) }
38 | .bind(to: repos)
39 | .disposed(by: disposeBag)
40 |
41 | self.loadMoreRelay
42 | .withLatestFrom(since)
43 | .filterNil()
44 | .distinctUntilChanged()
45 | .flatMap { githubService.repositories(since: $0).catchErrorJustReturn([]) }
46 | .withLatestFrom(repos) { (new, old) -> [Repo] in
47 | var sequence = old ?? []
48 | sequence.append(contentsOf: new)
49 | return sequence
50 | }
51 | .bind(to: repos)
52 | .disposed(by: disposeBag)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - PINCache (3.0.3):
3 | - PINCache/Arc-exception-safe (= 3.0.3)
4 | - PINCache/Core (= 3.0.3)
5 | - PINCache/Arc-exception-safe (3.0.3):
6 | - PINCache/Core
7 | - PINCache/Core (3.0.3):
8 | - PINOperation (~> 1.2.1)
9 | - PINOperation (1.2.1)
10 | - PINRemoteImage/Core (3.0.3):
11 | - PINOperation
12 | - PINRemoteImage/iOS (3.0.3):
13 | - PINRemoteImage/Core
14 | - PINRemoteImage/PINCache (3.0.3):
15 | - PINCache (~> 3.0.3)
16 | - PINRemoteImage/Core
17 | - Texture (3.0.0):
18 | - Texture/AssetsLibrary (= 3.0.0)
19 | - Texture/Core (= 3.0.0)
20 | - Texture/MapKit (= 3.0.0)
21 | - Texture/Photos (= 3.0.0)
22 | - Texture/PINRemoteImage (= 3.0.0)
23 | - Texture/Video (= 3.0.0)
24 | - Texture/AssetsLibrary (3.0.0):
25 | - Texture/Core
26 | - Texture/Core (3.0.0)
27 | - Texture/MapKit (3.0.0):
28 | - Texture/Core
29 | - Texture/Photos (3.0.0):
30 | - Texture/Core
31 | - Texture/PINRemoteImage (3.0.0):
32 | - PINRemoteImage/iOS (~> 3.0.0)
33 | - PINRemoteImage/PINCache
34 | - Texture/Core
35 | - Texture/Video (3.0.0):
36 | - Texture/Core
37 |
38 | DEPENDENCIES:
39 | - Texture (~> 3.0)
40 |
41 | SPEC REPOS:
42 | trunk:
43 | - PINCache
44 | - PINOperation
45 | - PINRemoteImage
46 | - Texture
47 |
48 | SPEC CHECKSUMS:
49 | PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086
50 | PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20
51 | PINRemoteImage: f1295b29f8c5e640e25335a1b2bd9d805171bd01
52 | Texture: 2f109e937850d94d1d07232041c9c7313ccddb81
53 |
54 | PODFILE CHECKSUM: 84c07afdd654394e2a5117c18b492819e1cc99d0
55 |
56 | COCOAPODS: 1.10.0
57 |
--------------------------------------------------------------------------------
/Example/GithubRepos/Assets.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" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Example/GithubRepos/RepoCellNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RepoCellNode.swift
3 | // GithubRepos
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AsyncDisplayKit
11 |
12 | final class RepoCellNode: ASCellNode {
13 |
14 | enum Const {
15 | static let imageSize: CGSize = .init(width: 60, height: 60)
16 | static let spacing: CGFloat = 8
17 | static let placeholderColor: UIColor = .init(
18 | red: 233.0 / 255.0,
19 | green: 237.0 / 255.0,
20 | blue: 240.0 / 255.0,
21 | alpha: 1.0
22 | )
23 | }
24 |
25 | enum ViewType {
26 | case table
27 | case collection
28 | }
29 |
30 | let imageNode: ASNetworkImageNode = {
31 | let node = ASNetworkImageNode()
32 | node.style.preferredSize = Const.imageSize
33 | node.placeholderFadeDuration = 0.3
34 | node.placeholderColor = Const.placeholderColor
35 | return node
36 | }()
37 |
38 | let titleNode: ASTextNode = {
39 | let node = ASTextNode()
40 | node.maximumNumberOfLines = 2
41 | node.style.flexShrink = 1.0
42 | return node
43 | }()
44 |
45 | let type: ViewType
46 |
47 | init(_ type: ViewType, repo: Repo) {
48 | self.type = type
49 | super.init()
50 | self.automaticallyManagesSubnodes = true
51 | self.backgroundColor = .white
52 | self.selectionStyle = .none
53 |
54 | imageNode.url = URL(string: repo.owner.avatarURL)
55 | titleNode.attributedText = NSAttributedString(
56 | string: repo.fullName,
57 | attributes: [.font: UIFont.systemFont(ofSize: 13),
58 | .foregroundColor: UIColor.black]
59 | )
60 | }
61 |
62 | override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
63 | let contentStackSpec = ASStackLayoutSpec(
64 | direction: .horizontal,
65 | spacing: Const.spacing,
66 | justifyContent: .start,
67 | alignItems: .center,
68 | children: [
69 | imageNode,
70 | titleNode
71 | ]
72 | )
73 | switch type {
74 | case .collection:
75 | contentStackSpec.style.preferredLayoutSize.width = ASDimensionMake(constrainedSize.max.width / 2 - 1)
76 | default: break
77 | }
78 |
79 | return ASInsetLayoutSpec(insets: .zero, child: contentStackSpec)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Example/GithubRepos/CategoryViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CategoryViewController.swift
3 | // GithubRepos
4 | //
5 | // Created by Kanghoon on 22/02/2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AsyncDisplayKit
11 | import RxSwift
12 | import RxCocoa
13 | import RxOptional
14 |
15 | final class CategoryViewController: ASDKViewController {
16 |
17 | enum Item: Int, CaseIterable {
18 | case table
19 | case collection
20 |
21 | var title: String {
22 | switch self {
23 | case .table: return "ASTableNode Example"
24 | case .collection: return "ASCollectionNode Example"
25 | }
26 | }
27 | }
28 |
29 | // MARK: - Initialization
30 |
31 | override init() {
32 | super.init(node: ASTableNode())
33 | node.delegate = self
34 | node.dataSource = self
35 | title = "Categories"
36 | }
37 |
38 | required init?(coder aDecoder: NSCoder) {
39 | fatalError("init(coder:) has not been implemented")
40 | }
41 |
42 |
43 | // MARK: - View Life Cycle
44 |
45 | override func viewDidLoad() {
46 | super.viewDidLoad()
47 | self.node.view.alwaysBounceVertical = true
48 | }
49 | }
50 |
51 | extension CategoryViewController: ASTableDataSource {
52 | func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int {
53 | return Item.allCases.count
54 | }
55 |
56 | func tableNode(_ tableNode: ASTableNode, nodeForRowAt indexPath: IndexPath) -> ASCellNode {
57 | guard let item = Item(rawValue: indexPath.row) else { return ASCellNode() }
58 | let cell = ASTextCellNode()
59 | cell.selectionStyle = .none
60 | cell.text = item.title
61 | return cell
62 | }
63 | }
64 |
65 |
66 | extension CategoryViewController: ASTableDelegate {
67 | func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) {
68 | guard let item = Item(rawValue: indexPath.row) else { return }
69 |
70 | let vc: UIViewController
71 | let viewModel = RepoViewModel(githubService: GithubService())
72 |
73 | switch item {
74 | case .table:
75 | vc = RepoTableViewController(viewModel: viewModel)
76 | case .collection:
77 | vc = RepoCollectionViewController(viewModel: viewModel)
78 | }
79 | self.navigationController?.pushViewController(vc, animated: true)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 
4 | [](https://github.com/OhKanghoon/RxDataSources-Texture/actions)
5 | [](https://cocoapods.org/pods/RxDataSources-Texture)
6 | [](https://cocoapods.org/pods/RxDataSources-Texture)
7 | [](https://cocoapods.org/pods/RxDataSources-Texture)
8 |
9 | ## Usage
10 |
11 | 1. Turn your data into an Observable sequence
12 | 2. Bind the data to the tableNode / collectionNode using :
13 | - rx.items(dataSource:protocol)
14 |
15 | ```swift
16 | let dataSource = RxASTableSectionedReloadDataSource>(
17 | configureCellBlock: { (_, _, _, num) in
18 | return {
19 | let cell = ASTextCellNode()
20 | cell.text = "\(num)"
21 | return cell
22 | }
23 | })
24 |
25 | Observable.just([SectionModel(model: "title", items: [1, 2, 3])])
26 | .bind(to: tableNode.rx.items(dataSource: dataSource))
27 | .disposed(by: disposeBag)
28 | ```
29 |
30 | ## Example
31 |
32 | To run the example project, clone the repo, and run `pod install` from the Example directory first.
33 | - [RxDataSources Example](https://github.com/OhKanghoon/RxDataSources-Texture/tree/master/Example)
34 |
35 | ## Requirements
36 |
37 | - Swift 5.2
38 | - [RxSwift](https://github.com/ReactiveX/RxSwift) (~> 6.0)
39 | - [RxCocoa](https://github.com/ReactiveX/RxSwift/tree/master/RxCocoa) (~> 6.0)
40 | - Differentiator (~> 5.0)
41 | - [Texture](https://github.com/TextureGroup/Texture) (~> 3.0)
42 |
43 | ## Installation
44 |
45 | RxDataSources-Texture is available through [CocoaPods](https://cocoapods.org). To install
46 | it, simply add the following line to your Podfile:
47 |
48 | ```ruby
49 | pod 'RxDataSources-Texture'
50 | ```
51 |
52 | ## Development
53 |
54 | ```console
55 | $ make project
56 | $ open RxDataSources-Texture.xcworkspace
57 | ```
58 |
59 | ## Author
60 |
61 | OhKanghoon, ggaa96@naver.com
62 |
63 | ## License
64 |
65 | RxDataSources-Texture is available under the MIT license. See the LICENSE file for more info.
66 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASDelegateProxyType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ASDelegateProxyType.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 | import UIKit
10 | import AsyncDisplayKit
11 | import RxSwift
12 | import RxCocoa
13 |
14 | extension ObservableType {
15 | func subscribeProxyDataSource(ofObject object: DelegateProxy.ParentObject, dataSource: DelegateProxy.Delegate, retainDataSource: Bool, binding: @escaping (DelegateProxy, Event) -> Void)
16 | -> Disposable
17 | where DelegateProxy.ParentObject: ASDisplayNode
18 | , DelegateProxy.Delegate: AnyObject {
19 | let proxy = DelegateProxy.proxy(for: object)
20 |
21 | // disposable needs to be disposed on the main thread
22 | let unregisterDelegate = ScheduledDisposable(
23 | scheduler: MainScheduler.instance,
24 | disposable: DelegateProxy.installForwardDelegate(dataSource, retainDelegate: retainDataSource, onProxyForObject: object)
25 | )
26 | // this is needed to flush any delayed old state (https://github.com/RxSwiftCommunity/RxDataSources/pull/75)
27 | object.layoutIfNeeded()
28 |
29 | let subscription = self.asObservable()
30 | .observe(on: MainScheduler())
31 | .catch { error in
32 | bindingError(error)
33 | return Observable.empty()
34 | }
35 | // source can never end, otherwise it would release the subscriber, and deallocate the data source
36 | .concat(Observable.never())
37 | .take(until: object.rx.deallocated)
38 | .subscribe { [weak object] (event: Event) in
39 |
40 | if let object = object {
41 | assert(proxy === DelegateProxy.currentDelegate(for: object), "Proxy changed from the time it was first set.\nOriginal: \(proxy)\nExisting: \(String(describing: DelegateProxy.currentDelegate(for: object)))")
42 | }
43 |
44 | binding(proxy, event)
45 |
46 | switch event {
47 | case .error(let error):
48 | bindingError(error)
49 | unregisterDelegate.dispose()
50 | case .completed:
51 | unregisterDelegate.dispose()
52 | default:
53 | break
54 | }
55 | }
56 |
57 | return Disposables.create { [weak object] in
58 | subscription.dispose()
59 | object?.layoutIfNeeded()
60 | unregisterDelegate.dispose()
61 | }
62 | }
63 | }
64 |
65 | #endif
66 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASTableNode/RxASTableDataSourceProxy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxASTableDataSourceProxy.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 |
10 | import UIKit
11 | import AsyncDisplayKit
12 | import RxSwift
13 | import RxCocoa
14 |
15 | extension ASTableNode: HasDataSource {
16 | public typealias DataSource = ASTableDataSource
17 | }
18 |
19 | fileprivate let tableDataSourceNotSet = ASTableDataSourceNotSet()
20 |
21 | final class ASTableDataSourceNotSet
22 | : NSObject
23 | , ASTableDataSource {
24 |
25 | func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int {
26 | return 0
27 | }
28 |
29 | func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock {
30 | rxAbstractMethod(message: dataSourceNotSet)
31 | }
32 | }
33 |
34 | /// For more information take a look at `DelegateProxyType`.
35 | final class RxASTableDataSourceProxy
36 | : DelegateProxy
37 | , DelegateProxyType
38 | , ASTableDataSource {
39 |
40 | /// Typed parent object.
41 | public weak private(set) var tableNode: ASTableNode?
42 |
43 | /// - parameter tableNode: Parent object for delegate proxy.
44 | public init(tableNode: ASTableNode) {
45 | self.tableNode = tableNode
46 | super.init(parentObject: tableNode, delegateProxy: RxASTableDataSourceProxy.self)
47 | }
48 |
49 | // Register known implementations
50 | public static func registerKnownImplementations() {
51 | self.register { RxASTableDataSourceProxy(tableNode: $0) }
52 | }
53 |
54 | fileprivate weak var _requiredMethodsDataSource: ASTableDataSource? = tableDataSourceNotSet
55 |
56 | // MARK: DataSource
57 |
58 | /// Required datasource method implementation.
59 | public func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int {
60 | return (_requiredMethodsDataSource
61 | ?? tableDataSourceNotSet).tableNode!(tableNode, numberOfRowsInSection: section)
62 | }
63 |
64 | /// Required datasource method implementation.
65 | public func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock {
66 | return (_requiredMethodsDataSource ?? tableDataSourceNotSet).tableNode!(tableNode, nodeBlockForRowAt: indexPath)
67 | }
68 |
69 | public override func setForwardToDelegate(_ forwardToDelegate: ASTableDataSource?, retainDelegate: Bool) {
70 | _requiredMethodsDataSource = forwardToDelegate ?? tableDataSourceNotSet
71 | super.setForwardToDelegate(forwardToDelegate, retainDelegate: retainDelegate)
72 | }
73 | }
74 | #endif
75 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASTableNode/RxASTableDelegateProxy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxASTableDelegateProxy.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 |
10 | import Foundation
11 | import AsyncDisplayKit
12 | import RxSwift
13 | import RxCocoa
14 |
15 | extension ASTableNode: HasDelegate {
16 | public typealias Delegate = ASTableDelegate
17 | }
18 |
19 | /// For more information take a look at `DelegateProxyType`.
20 | open class RxASTableDelegateProxy
21 | : DelegateProxy
22 | , DelegateProxyType
23 | , ASTableDelegate {
24 |
25 | /// Typed parent object.
26 | public weak private(set) var tableNode: ASTableNode?
27 |
28 | /// - parameter tableNode: Parent object for delegate proxy.
29 | public init(tableNode: ASTableNode) {
30 | self.tableNode = tableNode
31 | super.init(parentObject: tableNode, delegateProxy: RxASTableDelegateProxy.self)
32 | }
33 |
34 | // Register known implementations
35 | public static func registerKnownImplementations() {
36 | self.register { RxASTableDelegateProxy(tableNode: $0) }
37 | }
38 |
39 | fileprivate var _contentOffsetBehaviorSubject: BehaviorSubject?
40 | fileprivate var _contentOffsetPublishSubject: PublishSubject<()>?
41 |
42 | /// Optimized version used for observing content offset changes.
43 | internal var contentOffsetBehaviorSubject: BehaviorSubject {
44 | if let subject = _contentOffsetBehaviorSubject {
45 | return subject
46 | }
47 |
48 | let subject = BehaviorSubject(value: self.tableNode?.contentOffset ?? CGPoint.zero)
49 | _contentOffsetBehaviorSubject = subject
50 |
51 | return subject
52 | }
53 |
54 | /// Optimized version used for observing content offset changes.
55 | internal var contentOffsetPublishSubject: PublishSubject<()> {
56 | if let subject = _contentOffsetPublishSubject {
57 | return subject
58 | }
59 |
60 | let subject = PublishSubject<()>()
61 | _contentOffsetPublishSubject = subject
62 |
63 | return subject
64 | }
65 |
66 | // MARK: delegate methods
67 |
68 | /// For more information take a look at `DelegateProxyType`.
69 | public func scrollViewDidScroll(_ scrollView: UIScrollView) {
70 | if let subject = _contentOffsetBehaviorSubject {
71 | subject.on(.next(scrollView.contentOffset))
72 | }
73 | if let subject = _contentOffsetPublishSubject {
74 | subject.on(.next(()))
75 | }
76 | self._forwardToDelegate?.scrollViewDidScroll?(scrollView)
77 | }
78 |
79 | deinit {
80 | if let subject = _contentOffsetBehaviorSubject {
81 | subject.on(.completed)
82 | }
83 |
84 | if let subject = _contentOffsetPublishSubject {
85 | subject.on(.completed)
86 | }
87 | }
88 | }
89 |
90 | #endif
91 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASCollectionNode/RxASCollectionDataSourceProxy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxASCollectionDataSourceProxy.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 |
10 | import UIKit
11 | import AsyncDisplayKit
12 | import RxSwift
13 | import RxCocoa
14 |
15 | extension ASCollectionNode: HasDataSource {
16 | public typealias DataSource = ASCollectionDataSource
17 | }
18 |
19 | fileprivate let collectionDataSourceNotSet = ASCollectionDataSourceNotSet()
20 |
21 | final class ASCollectionDataSourceNotSet
22 | : NSObject
23 | , ASCollectionDataSource {
24 |
25 | func collectionNode(_ collectionNode: ASCollectionNode, numberOfItemsInSection section: Int) -> Int {
26 | return 0
27 | }
28 |
29 | func collectionNode(_ collectionNode: ASCollectionNode, nodeBlockForItemAt indexPath: IndexPath) -> ASCellNodeBlock {
30 | rxAbstractMethod(message: dataSourceNotSet)
31 | }
32 | }
33 |
34 | /// For more information take a look at `DelegateProxyType`.
35 | final class RxASCollectionDataSourceProxy
36 | : DelegateProxy
37 | , DelegateProxyType
38 | , ASCollectionDataSource {
39 |
40 | /// Typed parent object.
41 | public weak private(set) var collectionNode: ASCollectionNode?
42 |
43 | /// - parameter tableNode: Parent object for delegate proxy.
44 | public init(collectionNode: ASCollectionNode) {
45 | self.collectionNode = collectionNode
46 | super.init(parentObject: collectionNode, delegateProxy: RxASCollectionDataSourceProxy.self)
47 | }
48 |
49 | // Register known implementations
50 | public static func registerKnownImplementations() {
51 | self.register { RxASCollectionDataSourceProxy(collectionNode: $0) }
52 | }
53 |
54 | fileprivate weak var _requiredMethodsDataSource: ASCollectionDataSource? = collectionDataSourceNotSet
55 |
56 | // MARK: DataSource
57 |
58 | /// Required datasource method implementation.
59 | public func collectionNode(_ collectionNode: ASCollectionNode, numberOfItemsInSection section: Int) -> Int {
60 | return (_requiredMethodsDataSource
61 | ?? collectionDataSourceNotSet).collectionNode!(collectionNode, numberOfItemsInSection: section)
62 | }
63 |
64 | /// Required datasource method implementation.
65 |
66 | public func collectionNode(_ collectionNode: ASCollectionNode, nodeBlockForItemAt indexPath: IndexPath) -> ASCellNodeBlock {
67 | return (_requiredMethodsDataSource ?? collectionDataSourceNotSet).collectionNode!(collectionNode, nodeBlockForItemAt: indexPath)
68 | }
69 |
70 | public override func setForwardToDelegate(_ forwardToDelegate: ASCollectionDataSource?, retainDelegate: Bool) {
71 | _requiredMethodsDataSource = forwardToDelegate ?? collectionDataSourceNotSet
72 | super.setForwardToDelegate(forwardToDelegate, retainDelegate: retainDelegate)
73 | }
74 | }
75 | #endif
76 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASCollectionNode/RxASCollectionDelegateProxy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxASCollectionDelegateProxy.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 |
10 | import Foundation
11 | import AsyncDisplayKit
12 | import RxSwift
13 | import RxCocoa
14 |
15 | extension ASCollectionNode: HasDelegate {
16 | public typealias Delegate = ASCollectionDelegate
17 | }
18 |
19 | /// For more information take a look at `DelegateProxyType`.
20 | open class RxASCollectionDelegateProxy
21 | : DelegateProxy
22 | , DelegateProxyType
23 | , ASCollectionDelegate
24 | , ASCollectionDelegateFlowLayout
25 | , UICollectionViewDelegateFlowLayout {
26 |
27 | /// Typed parent object.
28 | public weak private(set) var collectionNode: ASCollectionNode?
29 |
30 | /// - parameter tableNode: Parent object for delegate proxy.
31 | public init(collectionNode: ASCollectionNode) {
32 | self.collectionNode = collectionNode
33 | super.init(parentObject: collectionNode, delegateProxy: RxASCollectionDelegateProxy.self)
34 | }
35 |
36 | // Register known implementations
37 | public static func registerKnownImplementations() {
38 | self.register { RxASCollectionDelegateProxy(collectionNode: $0) }
39 | }
40 |
41 | fileprivate var _contentOffsetBehaviorSubject: BehaviorSubject?
42 | fileprivate var _contentOffsetPublishSubject: PublishSubject<()>?
43 |
44 | /// Optimized version used for observing content offset changes.
45 | internal var contentOffsetBehaviorSubject: BehaviorSubject {
46 | if let subject = _contentOffsetBehaviorSubject {
47 | return subject
48 | }
49 |
50 | let subject = BehaviorSubject(value: self.collectionNode?.contentOffset ?? CGPoint.zero)
51 | _contentOffsetBehaviorSubject = subject
52 |
53 | return subject
54 | }
55 |
56 | /// Optimized version used for observing content offset changes.
57 | internal var contentOffsetPublishSubject: PublishSubject<()> {
58 | if let subject = _contentOffsetPublishSubject {
59 | return subject
60 | }
61 |
62 | let subject = PublishSubject<()>()
63 | _contentOffsetPublishSubject = subject
64 |
65 | return subject
66 | }
67 |
68 | // MARK: delegate methods
69 |
70 | /// For more information take a look at `DelegateProxyType`.
71 | public func scrollViewDidScroll(_ scrollView: UIScrollView) {
72 | if let subject = _contentOffsetBehaviorSubject {
73 | subject.on(.next(scrollView.contentOffset))
74 | }
75 | if let subject = _contentOffsetPublishSubject {
76 | subject.on(.next(()))
77 | }
78 | self._forwardToDelegate?.scrollViewDidScroll?(scrollView)
79 | }
80 |
81 | deinit {
82 | if let subject = _contentOffsetBehaviorSubject {
83 | subject.on(.completed)
84 | }
85 |
86 | if let subject = _contentOffsetPublishSubject {
87 | subject.on(.completed)
88 | }
89 | }
90 | }
91 |
92 | #endif
93 |
--------------------------------------------------------------------------------
/Example/GithubRepos/RepoTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RepoTableViewController.swift
3 | // GithubRepos
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AsyncDisplayKit
11 | import RxDataSources_Texture
12 | import RxSwift
13 | import RxCocoa
14 | import RxOptional
15 |
16 | class RepoTableViewController: ASDKViewController {
17 |
18 | // MARK: - Properties
19 |
20 | private let animatedDataSource = RxASTableSectionedAnimatedDataSource(
21 | configureCellBlock: { _, _, _, sectionItem in
22 | switch sectionItem {
23 | case .repo(let repoItem):
24 | return { RepoCellNode(.table, repo: repoItem) }
25 | }
26 | })
27 |
28 | private let dataSource = RxASTableSectionedReloadDataSource(
29 | configureCellBlock: { _, _, _, sectionItem in
30 | switch sectionItem {
31 | case .repo(let repoItem):
32 | return { RepoCellNode(.table, repo: repoItem) }
33 | }
34 | })
35 |
36 | let tableNode = ASTableNode()
37 |
38 | var batchContext: ASBatchContext?
39 | let viewModel: RepoViewModel
40 | let disposeBag = DisposeBag()
41 |
42 |
43 | // MARK: - Initialization
44 |
45 | init(viewModel: RepoViewModel) {
46 | self.viewModel = viewModel
47 | super.init(node: ASDisplayNode())
48 | self.node.automaticallyManagesSubnodes = true
49 | self.node.automaticallyRelayoutOnSafeAreaChanges = true
50 | self.node.layoutSpecBlock = { [weak self] (_, sizeRange) -> ASLayoutSpec in
51 | return self?.layoutSpecThatFits(sizeRange) ?? ASLayoutSpec()
52 | }
53 |
54 | self.title = "Table DataSources"
55 | self.bindViewModel()
56 | viewModel.refreshRelay.accept(())
57 | }
58 |
59 | required init?(coder aDecoder: NSCoder) {
60 | fatalError("init(coder:) has not been implemented")
61 | }
62 |
63 |
64 | // MARK: - View Life Cycle
65 |
66 | override func viewDidLoad() {
67 | super.viewDidLoad()
68 | self.tableNode.view.separatorStyle = .none
69 | self.tableNode.view.alwaysBounceVertical = true
70 | }
71 |
72 |
73 | // MARK: - Binding
74 |
75 | private func bindViewModel() {
76 | self.tableNode.rx
77 | .setDelegate(self)
78 | .disposed(by: disposeBag)
79 |
80 | self.viewModel.sections
81 | .do(onNext: { [weak self] _ in
82 | self?.batchContext?.completeBatchFetching(true)
83 | })
84 | .bind(to: tableNode.rx.items(dataSource: animatedDataSource))
85 | .disposed(by: disposeBag)
86 |
87 | self.tableNode.rx.willBeginBatchFetch
88 | .asObservable()
89 | .do(onNext: { [weak self] context in
90 | self?.batchContext = context
91 | }).map { _ in return }
92 | .bind(to: viewModel.loadMoreRelay)
93 | .disposed(by: disposeBag)
94 | }
95 |
96 |
97 | // MARK: - LayoutSpec
98 |
99 | func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
100 | return ASInsetLayoutSpec(insets: self.node.safeAreaInsets, child: self.tableNode)
101 | }
102 | }
103 |
104 | extension RepoTableViewController: ASTableDelegate {
105 | func shouldBatchFetch(for tableNode: ASTableNode) -> Bool {
106 | return self.viewModel.since.value != nil
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/RxASErrorHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxASErrorHandler.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | import Foundation
9 | import RxCocoa
10 |
11 | @_exported import Differentiator
12 |
13 | enum RxDataSourceTextureError: Error {
14 | case outOfBounds(indexPath: IndexPath)
15 | }
16 |
17 | // MARK: Error binding policies
18 |
19 | func bindingError(_ error: Swift.Error) {
20 | let error = "Binding error: \(error)"
21 | #if DEBUG
22 | rxFatalError(error)
23 | #else
24 | print(error)
25 | #endif
26 | }
27 |
28 | /// Swift does not implement abstract methods. This method is used as a runtime check to ensure that methods which intended to be abstract (i.e., they should be implemented in subclasses) are not called directly on the superclass.
29 | func rxAbstractMethod(message: String = "Abstract method", file: StaticString = #file, line: UInt = #line) -> Swift.Never {
30 | rxFatalError(message, file: file, line: line)
31 | }
32 |
33 | func rxFatalError(_ lastMessage: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) -> Swift.Never {
34 | // The temptation to comment this line is great, but please don't, it's for your own good. The choice is yours.
35 | fatalError(lastMessage(), file: file, line: line)
36 | }
37 |
38 | func rxFatalErrorInDebug(_ lastMessage: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) {
39 | #if DEBUG
40 | fatalError(lastMessage(), file: file, line: line)
41 | #else
42 | print("\(file):\(line): \(lastMessage())")
43 | #endif
44 | }
45 |
46 | func rxDebugFatalError(_ error: Error) {
47 | rxDebugFatalError("\(error)")
48 | }
49 |
50 | func rxDebugFatalError(_ message: String) {
51 | #if DEBUG
52 | fatalError(message)
53 | #else
54 | print(message)
55 | #endif
56 | }
57 |
58 | // MARK: casts or fatal error
59 |
60 | // workaround for Swift compiler bug, cheers compiler team :)
61 | func castOptionalOrFatalError(_ value: Any?) -> T? {
62 | if value == nil {
63 | return nil
64 | }
65 | let v: T = castOrFatalError(value)
66 | return v
67 | }
68 |
69 | func castOrThrow(_ resultType: T.Type, _ object: Any) throws -> T {
70 | guard let returnValue = object as? T else {
71 | throw RxCocoaError.castingError(object: object, targetType: resultType)
72 | }
73 |
74 | return returnValue
75 | }
76 |
77 | func castOptionalOrThrow(_ resultType: T.Type, _ object: AnyObject) throws -> T? {
78 | if NSNull().isEqual(object) {
79 | return nil
80 | }
81 |
82 | guard let returnValue = object as? T else {
83 | throw RxCocoaError.castingError(object: object, targetType: resultType)
84 | }
85 |
86 | return returnValue
87 | }
88 |
89 | func castOrFatalError(_ value: AnyObject!, message: String) -> T {
90 | let maybeResult: T? = value as? T
91 | guard let result = maybeResult else {
92 | rxFatalError(message)
93 | }
94 |
95 | return result
96 | }
97 |
98 | func castOrFatalError(_ value: Any!) -> T {
99 | let maybeResult: T? = value as? T
100 | guard let result = maybeResult else {
101 | rxFatalError("Failure converting from \(String(describing: value)) to \(T.self)")
102 | }
103 |
104 | return result
105 | }
106 |
107 | // MARK: Error messages
108 |
109 | let dataSourceNotSet = "DataSource not set"
110 | let delegateNotSet = "Delegate not set"
111 |
--------------------------------------------------------------------------------
/Example/GithubRepos/RepoCollectionViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RepoCollectionViewController.swift
3 | // GithubRepos
4 | //
5 | // Created by Kanghoon on 22/02/2019.
6 | // Copyright © 2019 CocoaPods. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AsyncDisplayKit
11 | import RxDataSources_Texture
12 | import RxSwift
13 | import RxCocoa
14 | import RxOptional
15 |
16 | class RepoCollectionViewController: ASDKViewController {
17 |
18 | // MARK: - Properties
19 |
20 | var flowLayout: UICollectionViewFlowLayout = {
21 | let layout = UICollectionViewFlowLayout()
22 | layout.scrollDirection = .vertical
23 | layout.minimumInteritemSpacing = 0
24 | layout.minimumLineSpacing = 0
25 | return layout
26 | }()
27 |
28 | lazy var collectionNode: ASCollectionNode = {
29 | let node = ASCollectionNode(collectionViewLayout: flowLayout)
30 | return node
31 | }()
32 |
33 | private let animatedDataSource = RxASCollectionSectionedAnimatedDataSource(
34 | configureCellBlock: { _, _, _, sectionItem in
35 | switch sectionItem {
36 | case .repo(let repoItem):
37 | return { RepoCellNode(.collection, repo: repoItem) }
38 | }
39 | })
40 | private let dataSource = RxASCollectionSectionedReloadDataSource(
41 | configureCellBlock: { _, _, _, sectionItem in
42 | switch sectionItem {
43 | case .repo(let repoItem):
44 | return { RepoCellNode(.collection, repo: repoItem) }
45 | }
46 | })
47 |
48 | var batchContext: ASBatchContext?
49 | let viewModel: RepoViewModel
50 | let disposeBag = DisposeBag()
51 |
52 |
53 | // MARK: Initialization
54 |
55 | init(viewModel: RepoViewModel) {
56 | self.viewModel = viewModel
57 | super.init(node: ASDisplayNode())
58 | self.node.automaticallyManagesSubnodes = true
59 | self.node.automaticallyRelayoutOnSafeAreaChanges = true
60 | self.node.layoutSpecBlock = { [weak self] (_, sizeRange) -> ASLayoutSpec in
61 | return self?.layoutSpecThatFits(sizeRange) ?? ASLayoutSpec()
62 | }
63 |
64 | self.title = "Collection DataSources"
65 | self.bindViewModel()
66 | viewModel.refreshRelay.accept(())
67 | }
68 |
69 | required init?(coder aDecoder: NSCoder) {
70 | fatalError("init(coder:) has not been implemented")
71 | }
72 |
73 |
74 | // MARK: - View Life Cycle
75 |
76 | override func viewDidLoad() {
77 | super.viewDidLoad()
78 | self.collectionNode.view.alwaysBounceVertical = true
79 | }
80 |
81 |
82 | // MARK: - Binding
83 |
84 | private func bindViewModel() {
85 | self.collectionNode.rx
86 | .setDelegate(self)
87 | .disposed(by: disposeBag)
88 |
89 | self.viewModel.sections
90 | .do(onNext: { [weak self] _ in
91 | self?.batchContext?.completeBatchFetching(true)
92 | })
93 | .bind(to: collectionNode.rx.items(dataSource: animatedDataSource))
94 | .disposed(by: disposeBag)
95 |
96 | self.collectionNode.rx.willBeginBatchFetch
97 | .do(onNext: { [weak self] context in
98 | self?.batchContext = context
99 | }).map { _ in return }
100 | .bind(to: viewModel.loadMoreRelay)
101 | .disposed(by: disposeBag)
102 | }
103 |
104 |
105 | // MARK: - LayoutSpec
106 |
107 | func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
108 | return ASInsetLayoutSpec(insets: self.node.safeAreaInsets,
109 | child: self.collectionNode)
110 | }
111 | }
112 |
113 | extension RepoCollectionViewController: ASCollectionDelegate {
114 | func shouldBatchFetch(for collectionNode: ASCollectionNode) -> Bool {
115 | return self.viewModel.since.value != nil
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASCollectionNode/RxASCollectionSectionedAnimatedDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxASCollectionSectionedAnimatedDataSource.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 21/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 | import Foundation
10 | import AsyncDisplayKit
11 | #if !RX_NO_MODULE
12 | import RxSwift
13 | import RxCocoa
14 | #endif
15 | import Differentiator
16 |
17 | open class RxASCollectionSectionedAnimatedDataSource
18 | : ASCollectionSectionedDataSource
19 | , RxASCollectionDataSourceType {
20 |
21 | public typealias Element = [S]
22 | public typealias DecideNodeTransition = (ASCollectionSectionedDataSource, ASCollectionNode, [Changeset]) -> NodeTransition
23 |
24 | /// Animation configuration for data source
25 | public var animationConfiguration: AnimationConfiguration
26 |
27 | /// Calculates view transition depending on type of changes
28 | public var decideNodeTransition: DecideNodeTransition
29 |
30 | public init(
31 | animationConfiguration: AnimationConfiguration = AnimationConfiguration(),
32 | decideNodeTransition: @escaping DecideNodeTransition = { _, _, _ in .animated },
33 | configureCellBlock: @escaping ConfigureCellBlock,
34 | configureSupplementaryNode: ConfigureSupplementaryNode? = nil,
35 | moveItem: @escaping MoveItem = { _, _, _ in () },
36 | canMoveItemWith: @escaping CanMoveItemWith = { _, _ in false }
37 | ) {
38 | self.animationConfiguration = animationConfiguration
39 | self.decideNodeTransition = decideNodeTransition
40 | super.init(
41 | configureCellBlock: configureCellBlock,
42 | configureSupplementaryNode: configureSupplementaryNode,
43 | moveItem: moveItem,
44 | canMoveItemWith: canMoveItemWith
45 | )
46 | }
47 |
48 | // there is no longer limitation to load initial sections with reloadData
49 | // but it is kept as a feature everyone got used to
50 | var dataSet = false
51 |
52 | open func collectionNode(_ collectionNode: ASCollectionNode, observedEvent: Event) {
53 | Binder(self) { dataSource, newSections in
54 | #if DEBUG
55 | dataSource._dataSourceBound = true
56 | #endif
57 | if !dataSource.dataSet {
58 | dataSource.dataSet = true
59 | dataSource.setSections(newSections)
60 | collectionNode.reloadData()
61 | }
62 | else {
63 | let oldSections = dataSource.sectionModels
64 | do {
65 | let differences = try Diff.differencesForSectionedView(initialSections: oldSections, finalSections: newSections)
66 |
67 | switch dataSource.decideNodeTransition(dataSource, collectionNode, differences) {
68 | case .animated:
69 | // each difference must be run in a separate 'performBatchUpdates', otherwise it crashes.
70 | // this is a limitation of Diff tool
71 | for difference in differences {
72 | let updateBlock = {
73 | // sections must be set within updateBlock in 'performBatchUpdates'
74 | dataSource.setSections(difference.finalSections)
75 |
76 | collectionNode.batchUpdates(difference, animationConfiguration: dataSource.animationConfiguration)
77 | }
78 | collectionNode.performBatch(animated: dataSource.animationConfiguration.animated,
79 | updates: updateBlock,
80 | completion: nil)
81 | }
82 |
83 | case .reload:
84 | dataSource.setSections(newSections)
85 | collectionNode.reloadData()
86 | return
87 | }
88 | }
89 | catch let e {
90 | rxDebugFatalError(e)
91 | dataSource.setSections(newSections)
92 | collectionNode.reloadData()
93 | }
94 | }
95 | }.on(observedEvent)
96 | }
97 | }
98 | #endif
99 |
--------------------------------------------------------------------------------
/Example/GithubRepos/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASTableNode/RxASTableSectionedAnimatedDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxASTableSectionedAnimatedDataSource.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 | import Foundation
10 | import AsyncDisplayKit
11 | #if !RX_NO_MODULE
12 | import RxSwift
13 | import RxCocoa
14 | #endif
15 | import Differentiator
16 |
17 | open class RxASTableSectionedAnimatedDataSource
18 | : ASTableSectionedDataSource
19 | , RxASTableDataSourceType {
20 |
21 | public typealias Element = [S]
22 | public typealias DecideNodeTransition = (ASTableSectionedDataSource, ASTableNode, [Changeset]) -> NodeTransition
23 |
24 | /// Animation configuration for data source
25 | public var animationConfiguration: AnimationConfiguration
26 |
27 | /// Calculates view transition depending on type of changes
28 | public var decideNodeTransition: DecideNodeTransition
29 | private var dataSet = false
30 |
31 | #if os(iOS)
32 | public init(
33 | animationConfiguration: AnimationConfiguration = AnimationConfiguration(),
34 | decideNodeTransition: @escaping DecideNodeTransition = { _, _, _ in .animated },
35 | configureCellBlock: @escaping ConfigureCellBlock,
36 | titleForHeaderInSection: @escaping TitleForHeaderInSection = { _, _ in nil },
37 | titleForFooterInSection: @escaping TitleForFooterInSection = { _, _ in nil },
38 | canEditRowAtIndexPath: @escaping CanEditRowAtIndexPath = { _, _ in false },
39 | canMoveRowAtIndexPath: @escaping CanMoveRowAtIndexPath = { _, _ in false },
40 | sectionIndexTitles: @escaping SectionIndexTitles = { _ in nil },
41 | sectionForSectionIndexTitle: @escaping SectionForSectionIndexTitle = { _, _, index in index }
42 | ) {
43 | self.animationConfiguration = animationConfiguration
44 | self.decideNodeTransition = decideNodeTransition
45 | super.init(
46 | configureCellBlock: configureCellBlock,
47 | titleForHeaderInSection: titleForHeaderInSection,
48 | titleForFooterInSection: titleForFooterInSection,
49 | canEditRowAtIndexPath: canEditRowAtIndexPath,
50 | canMoveRowAtIndexPath: canMoveRowAtIndexPath,
51 | sectionIndexTitles: sectionIndexTitles,
52 | sectionForSectionIndexTitle: sectionForSectionIndexTitle
53 | )
54 | }
55 | #else
56 | public init(
57 | animationConfiguration: AnimationConfiguration = AnimationConfiguration(),
58 | decideNodeTransition: @escaping DecideNodeTransition = { _, _, _ in .animated },
59 | configureCellBlock: @escaping ConfigureCellBlock,
60 | titleForHeaderInSection: @escaping TitleForHeaderInSection = { _, _ in nil },
61 | titleForFooterInSection: @escaping TitleForFooterInSection = { _, _ in nil },
62 | canEditRowAtIndexPath: @escaping CanEditRowAtIndexPath = { _, _ in false },
63 | canMoveRowAtIndexPath: @escaping CanMoveRowAtIndexPath = { _, _ in false }
64 | ) {
65 | self.animationConfiguration = animationConfiguration
66 | self.decideNodeTransition = decideNodeTransition
67 | super.init(
68 | configureCellBlock: configureCellBlock,
69 | titleForHeaderInSection: titleForHeaderInSection,
70 | titleForFooterInSection: titleForFooterInSection,
71 | canEditRowAtIndexPath: canEditRowAtIndexPath,
72 | canMoveRowAtIndexPath: canMoveRowAtIndexPath
73 | )
74 | }
75 | #endif
76 |
77 | public func tableNode(_ tableNode: ASTableNode, observedEvent: Event<[S]>) {
78 | Binder(self) { dataSource, newSections in
79 | #if DEBUG
80 | dataSource._dataSourceBound = true
81 | #endif
82 | if !dataSource.dataSet {
83 | dataSource.dataSet = true
84 | dataSource.setSections(newSections)
85 | tableNode.reloadData()
86 | }
87 | else {
88 | let oldSections = dataSource.sectionModels
89 | do {
90 | let differences = try Diff.differencesForSectionedView(initialSections: oldSections, finalSections: newSections)
91 |
92 | switch dataSource.decideNodeTransition(dataSource, tableNode, differences) {
93 | case .animated:
94 | for difference in differences {
95 | let updateBlock = {
96 | // sections must be set within updateBlock in 'performBatchUpdates'
97 | dataSource.setSections(difference.finalSections)
98 | tableNode.batchUpdates(difference, animationConfiguration: dataSource.animationConfiguration)
99 | }
100 | tableNode.performBatch(animated: dataSource.animationConfiguration.animated,
101 | updates: updateBlock,
102 | completion: nil)
103 | }
104 | case .reload:
105 | dataSource.setSections(newSections)
106 | tableNode.reloadData()
107 | return
108 | }
109 | }
110 | catch let e {
111 | rxDebugFatalError(e)
112 | dataSource.setSections(newSections)
113 | tableNode.reloadData()
114 | }
115 | }
116 | }.on(observedEvent)
117 | }
118 | }
119 | #endif
120 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/UI+SectionNodeType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UI+SectionNodeType.swift
3 | // RxTextureDataSources
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 | import Foundation
10 | import AsyncDisplayKit
11 | import Differentiator
12 |
13 | #if swift(>=4.2)
14 | public typealias UITableViewRowAnimation = UITableView.RowAnimation
15 | #endif
16 |
17 | func indexSet(_ values: [Int]) -> IndexSet {
18 | let indexSet = NSMutableIndexSet()
19 | for i in values {
20 | indexSet.add(i)
21 | }
22 | return indexSet as IndexSet
23 | }
24 |
25 | extension ASTableNode: SectionedNodeType {
26 |
27 | public func insertItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation) {
28 | self.insertRows(at: paths, with: animationStyle)
29 | }
30 |
31 | public func deleteItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation) {
32 | self.deleteRows(at: paths, with: animationStyle)
33 | }
34 |
35 | public func moveItemAtIndexPath(_ from: IndexPath, to: IndexPath) {
36 | self.moveRow(at: from, to: to)
37 | }
38 |
39 | public func reloadItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation) {
40 | self.reloadRows(at: paths, with: animationStyle)
41 | }
42 |
43 | public func insertSections(_ sections: [Int], animationStyle: UITableViewRowAnimation) {
44 | self.insertSections(indexSet(sections), with: animationStyle)
45 | }
46 |
47 | public func deleteSections(_ sections: [Int], animationStyle: UITableViewRowAnimation) {
48 | self.deleteSections(indexSet(sections), with: animationStyle)
49 | }
50 |
51 | public func moveSection(_ from: Int, to: Int) {
52 | self.moveSection(from, toSection: to)
53 | }
54 |
55 | public func reloadSections(_ sections: [Int], animationStyle: UITableViewRowAnimation) {
56 | self.reloadSections(indexSet(sections), with: animationStyle)
57 | }
58 | }
59 |
60 | extension ASCollectionNode: SectionedNodeType {
61 |
62 | public func insertItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation) {
63 | self.insertItems(at: paths)
64 | }
65 |
66 | public func deleteItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation) {
67 | self.deleteItems(at: paths)
68 | }
69 |
70 | public func moveItemAtIndexPath(_ from: IndexPath, to: IndexPath) {
71 | self.moveItem(at: from, to: to)
72 | }
73 |
74 | public func reloadItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation) {
75 | self.reloadItems(at: paths)
76 | }
77 |
78 | public func insertSections(_ sections: [Int], animationStyle: UITableViewRowAnimation) {
79 | self.insertSections(indexSet(sections))
80 | }
81 |
82 | public func deleteSections(_ sections: [Int], animationStyle: UITableViewRowAnimation) {
83 | self.deleteSections(indexSet(sections))
84 | }
85 |
86 | public func moveSection(_ from: Int, to: Int) {
87 | self.moveSection(from, toSection: to)
88 | }
89 |
90 | public func reloadSections(_ sections: [Int], animationStyle: UITableViewRowAnimation) {
91 | self.reloadSections(indexSet(sections))
92 | }
93 | }
94 |
95 | public protocol SectionedNodeType {
96 | func insertItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation)
97 | func deleteItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation)
98 | func moveItemAtIndexPath(_ from: IndexPath, to: IndexPath)
99 | func reloadItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation)
100 |
101 | func insertSections(_ sections: [Int], animationStyle: UITableViewRowAnimation)
102 | func deleteSections(_ sections: [Int], animationStyle: UITableViewRowAnimation)
103 | func moveSection(_ from: Int, to: Int)
104 | func reloadSections(_ sections: [Int], animationStyle: UITableViewRowAnimation)
105 | }
106 |
107 | extension SectionedNodeType {
108 |
109 | public func batchUpdates(_ changes: Changeset, animationConfiguration: AnimationConfiguration) {
110 | typealias I = S.Item
111 |
112 | deleteSections(changes.deletedSections, animationStyle: animationConfiguration.deleteAnimation)
113 | // Updated sections doesn't mean reload entire section, somebody needs to update the section view manually
114 | // otherwise all cells will be reloaded for nothing.
115 | //view.reloadSections(changes.updatedSections, animationStyle: rowAnimation)
116 | insertSections(changes.insertedSections, animationStyle: animationConfiguration.insertAnimation)
117 | for (from, to) in changes.movedSections {
118 | moveSection(from, to: to)
119 | }
120 |
121 | deleteItemsAtIndexPaths(
122 | changes.deletedItems.map { IndexPath(item: $0.itemIndex, section: $0.sectionIndex) },
123 | animationStyle: animationConfiguration.deleteAnimation
124 | )
125 | insertItemsAtIndexPaths(
126 | changes.insertedItems.map { IndexPath(item: $0.itemIndex, section: $0.sectionIndex) },
127 | animationStyle: animationConfiguration.insertAnimation
128 | )
129 | reloadItemsAtIndexPaths(
130 | changes.updatedItems.map { IndexPath(item: $0.itemIndex, section: $0.sectionIndex) },
131 | animationStyle: animationConfiguration.reloadAnimation
132 | )
133 |
134 | for (from, to) in changes.movedItems {
135 | moveItemAtIndexPath(
136 | IndexPath(item: from.itemIndex, section: from.sectionIndex),
137 | to: IndexPath(item: to.itemIndex, section: to.sectionIndex)
138 | )
139 | }
140 | }
141 |
142 | }
143 | #endif
144 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASCollectionNode/ASCollectionSectionedDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ASCollectionSectionedDataSource.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 | import Foundation
10 | import AsyncDisplayKit
11 | #if !RX_NO_MODULE
12 | import RxCocoa
13 | #endif
14 | import Differentiator
15 |
16 | open class ASCollectionSectionedDataSource
17 | : NSObject
18 | , ASCollectionDataSource
19 | , SectionedViewDataSourceType {
20 |
21 | public typealias I = S.Item
22 | public typealias Section = S
23 |
24 | public typealias ConfigureCellBlock = (ASCollectionSectionedDataSource, ASCollectionNode, IndexPath, I) -> ASCellNodeBlock
25 | public typealias ConfigureSupplementaryNode = (ASCollectionSectionedDataSource, ASCollectionNode, String, IndexPath) -> ASCellNode
26 | public typealias MoveItem = (ASCollectionSectionedDataSource, _ sourceIndexPath: IndexPath, _ destinationIndexPath:IndexPath) -> Void
27 | public typealias CanMoveItemWith = (ASCollectionSectionedDataSource, ASCellNode) -> Bool
28 |
29 | public init(
30 | configureCellBlock: @escaping ConfigureCellBlock,
31 | configureSupplementaryNode: ConfigureSupplementaryNode? = nil,
32 | moveItem: @escaping MoveItem = { _, _, _ in () },
33 | canMoveItemWith: @escaping CanMoveItemWith = { _, _ in false }
34 | ) {
35 | self.configureCellBlock = configureCellBlock
36 | self.configureSupplementaryNode = configureSupplementaryNode
37 | self.moveItem = moveItem
38 | self.canMoveItemWith = canMoveItemWith
39 | }
40 |
41 | #if DEBUG
42 | // If data source has already been bound, then mutating it
43 | // afterwards isn't something desired.
44 | // This simulates immutability after binding
45 | var _dataSourceBound: Bool = false
46 |
47 | private func ensureNotMutatedAfterBinding() {
48 | assert(!_dataSourceBound, "Data source is already bound. Please write this line before binding call (`bindTo`, `drive`). Data source must first be completely configured, and then bound after that, otherwise there could be runtime bugs, glitches, or partial malfunctions.")
49 | }
50 |
51 | #endif
52 |
53 | // This structure exists because model can be mutable
54 | // In that case current state value should be preserved.
55 | // The state that needs to be preserved is ordering of items in section
56 | // and their relationship with section.
57 | // If particular item is mutable, that is irrelevant for this logic to function
58 | // properly.
59 | public typealias SectionModelSnapshot = SectionModel
60 |
61 | private var _sectionModels: [SectionModelSnapshot] = []
62 |
63 | open var sectionModels: [S] {
64 | return _sectionModels.map { Section(original: $0.model, items: $0.items) }
65 | }
66 |
67 | open subscript(section: Int) -> S {
68 | let sectionModel = self._sectionModels[section]
69 | return S(original: sectionModel.model, items: sectionModel.items)
70 | }
71 |
72 | open subscript(indexPath: IndexPath) -> I {
73 | get {
74 | return self._sectionModels[indexPath.section].items[indexPath.item]
75 | }
76 | set(item) {
77 | var section = self._sectionModels[indexPath.section]
78 | section.items[indexPath.item] = item
79 | self._sectionModels[indexPath.section] = section
80 | }
81 | }
82 |
83 | open func model(at indexPath: IndexPath) throws -> Any {
84 | guard indexPath.section < self._sectionModels.count,
85 | indexPath.item < self._sectionModels[indexPath.section].items.count else {
86 | throw RxDataSourceTextureError.outOfBounds(indexPath: indexPath)
87 | }
88 | return self[indexPath]
89 | }
90 |
91 | open func setSections(_ sections: [S]) {
92 | self._sectionModels = sections.map { SectionModelSnapshot(model: $0, items: $0.items) }
93 | }
94 |
95 | open var configureCellBlock: ConfigureCellBlock {
96 | didSet {
97 | #if DEBUG
98 | ensureNotMutatedAfterBinding()
99 | #endif
100 | }
101 | }
102 |
103 | open var configureSupplementaryNode: ConfigureSupplementaryNode? {
104 | didSet {
105 | #if DEBUG
106 | ensureNotMutatedAfterBinding()
107 | #endif
108 | }
109 | }
110 |
111 | open var moveItem: MoveItem {
112 | didSet {
113 | #if DEBUG
114 | ensureNotMutatedAfterBinding()
115 | #endif
116 | }
117 | }
118 |
119 | open var canMoveItemWith: ((ASCollectionSectionedDataSource, ASCellNode) -> Bool)? {
120 | didSet {
121 | #if DEBUG
122 | ensureNotMutatedAfterBinding()
123 | #endif
124 | }
125 | }
126 |
127 | // ASCollectionDataSource
128 |
129 | open func numberOfSections(in collectionNode: ASCollectionNode) -> Int {
130 | return _sectionModels.count
131 | }
132 |
133 | open func collectionNode(_ collectionNode: ASCollectionNode, numberOfItemsInSection section: Int) -> Int {
134 | return _sectionModels[section].items.count
135 | }
136 |
137 | open func collectionNode(_ collectionNode: ASCollectionNode, nodeBlockForItemAt indexPath: IndexPath) -> ASCellNodeBlock {
138 | precondition(indexPath.item < _sectionModels[indexPath.section].items.count)
139 |
140 | return configureCellBlock(self, collectionNode, indexPath, self[indexPath])
141 | }
142 |
143 | open func collectionNode(_ collectionNode: ASCollectionNode, nodeForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> ASCellNode {
144 | return configureSupplementaryNode!(self, collectionNode, kind, indexPath)
145 | }
146 |
147 | open func collectionNode(_ collectionNode: ASCollectionNode, canMoveItemWith node: ASCellNode) -> Bool {
148 | guard let canMoveItem = canMoveItemWith?(self, node) else {
149 | return false
150 | }
151 | return canMoveItem
152 | }
153 |
154 | open func collectionNode(_ collectionNode: ASCollectionNode, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
155 | self._sectionModels.moveFromSourceIndexPath(sourceIndexPath, destinationIndexPath: destinationIndexPath)
156 | self.moveItem(self, sourceIndexPath, destinationIndexPath)
157 | }
158 |
159 | open override func responds(to aSelector: Selector!) -> Bool {
160 | if aSelector == #selector(ASCollectionDataSource.collectionNode(_:nodeForSupplementaryElementOfKind:at:)) {
161 | return configureSupplementaryNode != nil
162 | }
163 | else {
164 | return super.responds(to: aSelector)
165 | }
166 | }
167 | }
168 | #endif
169 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASTableNode/ASTableSectionedDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ASTableSectionedDataSource.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 | import Foundation
10 | import AsyncDisplayKit
11 | #if !RX_NO_MODULE
12 | import RxCocoa
13 | #endif
14 | import Differentiator
15 |
16 | open class ASTableSectionedDataSource
17 | : NSObject
18 | , ASTableDataSource
19 | , SectionedViewDataSourceType {
20 |
21 | public typealias I = S.Item
22 | public typealias Section = S
23 |
24 | public typealias ConfigureCellBlock = (ASTableSectionedDataSource, ASTableNode, IndexPath, I) -> ASCellNodeBlock
25 | public typealias TitleForHeaderInSection = (ASTableSectionedDataSource, Int) -> String?
26 | public typealias TitleForFooterInSection = (ASTableSectionedDataSource, Int) -> String?
27 | public typealias CanEditRowAtIndexPath = (ASTableSectionedDataSource, IndexPath) -> Bool
28 | public typealias CanMoveRowAtIndexPath = (ASTableSectionedDataSource, IndexPath) -> Bool
29 |
30 | #if os(iOS)
31 | public typealias SectionIndexTitles = (ASTableSectionedDataSource) -> [String]?
32 | public typealias SectionForSectionIndexTitle = (ASTableSectionedDataSource, _ title: String, _ index: Int) -> Int
33 | #endif
34 |
35 | #if os(iOS)
36 | public init(
37 | configureCellBlock: @escaping ConfigureCellBlock,
38 | titleForHeaderInSection: @escaping TitleForHeaderInSection = { _, _ in nil },
39 | titleForFooterInSection: @escaping TitleForFooterInSection = { _, _ in nil },
40 | canEditRowAtIndexPath: @escaping CanEditRowAtIndexPath = { _, _ in false },
41 | canMoveRowAtIndexPath: @escaping CanMoveRowAtIndexPath = { _, _ in false },
42 | sectionIndexTitles: @escaping SectionIndexTitles = { _ in nil },
43 | sectionForSectionIndexTitle: @escaping SectionForSectionIndexTitle = { _, _, index in index }
44 | ) {
45 | self.configureCellBlock = configureCellBlock
46 | self.titleForHeaderInSection = titleForHeaderInSection
47 | self.titleForFooterInSection = titleForFooterInSection
48 | self.canEditRowAtIndexPath = canEditRowAtIndexPath
49 | self.canMoveRowAtIndexPath = canMoveRowAtIndexPath
50 | self.sectionIndexTitles = sectionIndexTitles
51 | self.sectionForSectionIndexTitle = sectionForSectionIndexTitle
52 | }
53 |
54 | #else
55 | public init(
56 | configureCellBlock: @escaping configureCellBlock,
57 | titleForHeaderInSection: @escaping TitleForHeaderInSection = { _, _ in nil },
58 | titleForFooterInSection: @escaping TitleForFooterInSection = { _, _ in nil },
59 | canEditRowAtIndexPath: @escaping CanEditRowAtIndexPath = { _, _ in false },
60 | canMoveRowAtIndexPath: @escaping CanMoveRowAtIndexPath = { _, _ in false }
61 | ) {
62 | self.configureCellBlock = configureCellBlock
63 | self.titleForHeaderInSection = titleForHeaderInSection
64 | self.titleForFooterInSection = titleForFooterInSection
65 | self.canEditRowAtIndexPath = canEditRowAtIndexPath
66 | self.canMoveRowAtIndexPath = canMoveRowAtIndexPath
67 | }
68 | #endif
69 |
70 | #if DEBUG
71 | // If data source has already been bound, then mutating it
72 | // afterwards isn't something desired.
73 | // This simulates immutability after binding
74 | var _dataSourceBound: Bool = false
75 |
76 | private func ensureNotMutatedAfterBinding() {
77 | assert(!_dataSourceBound, "Data source is already bound. Please write this line before binding call (`bindTo`, `drive`). Data source must first be completely configured, and then bound after that, otherwise there could be runtime bugs, glitches, or partial malfunctions.")
78 | }
79 |
80 | #endif
81 |
82 | // This structure exists because model can be mutable
83 | // In that case current state value should be preserved.
84 | // The state that needs to be preserved is ordering of items in section
85 | // and their relationship with section.
86 | // If particular item is mutable, that is irrelevant for this logic to function
87 | // properly.
88 | public typealias SectionModelSnapshot = SectionModel
89 |
90 | private var _sectionModels: [SectionModelSnapshot] = []
91 |
92 | open var sectionModels: [S] {
93 | return _sectionModels.map { Section(original: $0.model, items: $0.items) }
94 | }
95 |
96 | open subscript(section: Int) -> S {
97 | let sectionModel = self._sectionModels[section]
98 | return S(original: sectionModel.model, items: sectionModel.items)
99 | }
100 |
101 | open subscript(indexPath: IndexPath) -> I {
102 | get {
103 | return self._sectionModels[indexPath.section].items[indexPath.row]
104 | }
105 | set(item) {
106 | var section = self._sectionModels[indexPath.section]
107 | section.items[indexPath.row] = item
108 | self._sectionModels[indexPath.section] = section
109 | }
110 | }
111 |
112 | open func model(at indexPath: IndexPath) throws -> Any {
113 | guard indexPath.section < self._sectionModels.count,
114 | indexPath.row < self._sectionModels[indexPath.section].items.count else {
115 | throw RxDataSourceTextureError.outOfBounds(indexPath: indexPath)
116 | }
117 | return self[indexPath]
118 | }
119 |
120 | open func setSections(_ sections: [S]) {
121 | self._sectionModels = sections.map { SectionModelSnapshot(model: $0, items: $0.items) }
122 | }
123 |
124 | open var configureCellBlock: ConfigureCellBlock {
125 | didSet {
126 | #if DEBUG
127 | ensureNotMutatedAfterBinding()
128 | #endif
129 | }
130 | }
131 |
132 | open var titleForHeaderInSection: TitleForHeaderInSection {
133 | didSet {
134 | #if DEBUG
135 | ensureNotMutatedAfterBinding()
136 | #endif
137 | }
138 | }
139 | open var titleForFooterInSection: TitleForFooterInSection {
140 | didSet {
141 | #if DEBUG
142 | ensureNotMutatedAfterBinding()
143 | #endif
144 | }
145 | }
146 |
147 | open var canEditRowAtIndexPath: CanEditRowAtIndexPath {
148 | didSet {
149 | #if DEBUG
150 | ensureNotMutatedAfterBinding()
151 | #endif
152 | }
153 | }
154 | open var canMoveRowAtIndexPath: CanMoveRowAtIndexPath {
155 | didSet {
156 | #if DEBUG
157 | ensureNotMutatedAfterBinding()
158 | #endif
159 | }
160 | }
161 |
162 | open var rowAnimation: UITableViewRowAnimation = .automatic
163 |
164 | #if os(iOS)
165 | open var sectionIndexTitles: SectionIndexTitles {
166 | didSet {
167 | #if DEBUG
168 | ensureNotMutatedAfterBinding()
169 | #endif
170 | }
171 | }
172 | open var sectionForSectionIndexTitle: SectionForSectionIndexTitle {
173 | didSet {
174 | #if DEBUG
175 | ensureNotMutatedAfterBinding()
176 | #endif
177 | }
178 | }
179 | #endif
180 |
181 | // ASTableDataSource
182 |
183 | open func numberOfSections(in tableNode: ASTableNode) -> Int {
184 | return _sectionModels.count
185 | }
186 |
187 | open func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int {
188 | guard _sectionModels.count > section else { return 0 }
189 | return _sectionModels[section].items.count
190 | }
191 |
192 | public func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock {
193 | precondition(indexPath.row < _sectionModels[indexPath.section].items.count)
194 |
195 | return configureCellBlock(self, tableNode, indexPath, self[indexPath])
196 | }
197 |
198 | open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
199 | return titleForHeaderInSection(self, section)
200 | }
201 |
202 | open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
203 | return titleForFooterInSection(self, section)
204 | }
205 |
206 | open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
207 | return canEditRowAtIndexPath(self, indexPath)
208 | }
209 |
210 | open func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
211 | return canMoveRowAtIndexPath(self, indexPath)
212 | }
213 |
214 | open func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
215 | self._sectionModels.moveFromSourceIndexPath(sourceIndexPath, destinationIndexPath: destinationIndexPath)
216 | }
217 |
218 | #if os(iOS)
219 | open func sectionIndexTitles(for tableView: UITableView) -> [String]? {
220 | return sectionIndexTitles(self)
221 | }
222 |
223 | open func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
224 | return sectionForSectionIndexTitle(self, title, index)
225 | }
226 | #endif
227 | }
228 | #endif
229 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASCollectionNode/ASCollectionNode+Rx.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ASCollectionNode+Rx.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 |
10 | import Foundation
11 | import AsyncDisplayKit
12 | import RxSwift
13 | import RxCocoa
14 |
15 | // Items
16 |
17 | extension Reactive where Base: ASCollectionNode {
18 |
19 | /**
20 | Binds sequences of elements to collection node items using a custom reactive data used to perform the transformation.
21 |
22 | - parameter dataSource: Data source used to transform elements to cell nodes.
23 | - parameter source: Observable sequence of items.
24 | - returns: Disposable object that can be used to unbind.
25 |
26 | */
27 | public func items<
28 | DataSource: RxASCollectionDataSourceType & ASCollectionDataSource,
29 | O: ObservableType>
30 | (dataSource: DataSource)
31 | -> (_ source: O)
32 | -> Disposable where DataSource.Element == O.Element {
33 | return { source in
34 | return source.subscribeProxyDataSource(ofObject: self.base, dataSource: dataSource, retainDataSource: true) { [weak collectionNode = self.base] (_: RxASCollectionDataSourceProxy, event) -> Void in
35 | guard let collectionNode = collectionNode else {
36 | return
37 | }
38 | dataSource.collectionNode(collectionNode, observedEvent: event)
39 | }
40 | }
41 | }
42 | }
43 |
44 | extension Reactive where Base: ASCollectionNode {
45 |
46 | /**
47 | Reactive wrapper for `delegate`.
48 |
49 | For more information take a look at `DelegateProxyType` protocol documentation.
50 | */
51 | public var delegate: DelegateProxy {
52 | return RxASCollectionDelegateProxy.proxy(for: base)
53 | }
54 |
55 | /**
56 | Reactive wrapper for `dataSource`.
57 |
58 | For more information take a look at `DelegateProxyType` protocol documentation.
59 | */
60 | public var dataSource: DelegateProxy {
61 | return RxASCollectionDataSourceProxy.proxy(for: base)
62 | }
63 |
64 | /**
65 | Installs data source as forwarding delegate on `rx.dataSource`.
66 | Data source won't be retained.
67 |
68 | It enables using normal delegate mechanism with reactive delegate mechanism.
69 |
70 | - parameter dataSource: Data source object.
71 | - returns: Disposable object that can be used to unbind the data source.
72 | */
73 | public func setDataSource(_ dataSource: ASCollectionDataSource) -> Disposable {
74 | return ScheduledDisposable(
75 | scheduler: MainScheduler.instance,
76 | disposable: RxASCollectionDataSourceProxy.installForwardDelegate(dataSource, retainDelegate: false, onProxyForObject: self.base)
77 | )
78 | }
79 |
80 | /**
81 | Installs delegate as forwarding delegate on `rx.delegate`.
82 | Data source won't be retained.
83 |
84 | It enables using normal delegate mechanism with reactive delegate mechanism.
85 |
86 | - parameter delegate: Delegate object
87 | - returns: Disposable object that can be used to unbind the delegate.
88 | */
89 | public func setDelegate(_ delegate: ASCollectionDelegate) -> Disposable {
90 | return ScheduledDisposable(
91 | scheduler: MainScheduler.instance,
92 | disposable: RxASCollectionDelegateProxy.installForwardDelegate(delegate, retainDelegate: false, onProxyForObject: self.base)
93 | )
94 | }
95 |
96 | /// Reactive wrapper for `contentOffset`.
97 | public var contentOffset: ControlProperty {
98 | let proxy = RxASCollectionDelegateProxy.proxy(for: base)
99 |
100 | let bindingObserver = Binder(self.base) { collectionNode, contentOffset in
101 | collectionNode.contentOffset = contentOffset
102 | }
103 |
104 | return ControlProperty(values: proxy.contentOffsetBehaviorSubject, valueSink: bindingObserver)
105 | }
106 |
107 | /// Reactive wrapper for delegate method `scrollViewDidScroll`
108 | public var didScroll: ControlEvent {
109 | let source = RxASCollectionDelegateProxy.proxy(for: base).contentOffsetPublishSubject
110 | return ControlEvent(events: source)
111 | }
112 |
113 | /// Reactive wrapper for delegate method `scrollViewWillBeginDecelerating`
114 | public var willBeginDecelerating: ControlEvent {
115 | let source = delegate.methodInvoked(#selector(ASCollectionDelegate.scrollViewWillBeginDecelerating(_:))).map { _ in }
116 | return ControlEvent(events: source)
117 | }
118 |
119 | /// Reactive wrapper for delegate method `scrollViewDidEndDecelerating`
120 | public var didEndDecelerating: ControlEvent {
121 | let source = delegate.methodInvoked(#selector(ASCollectionDelegate.scrollViewDidEndDecelerating(_:))).map { _ in }
122 | return ControlEvent(events: source)
123 | }
124 |
125 | /// Reactive wrapper for delegate method `scrollViewWillBeginDragging`
126 | public var willBeginDragging: ControlEvent {
127 | let source = delegate.methodInvoked(#selector(ASCollectionDelegate.scrollViewWillBeginDragging(_:))).map { _ in }
128 | return ControlEvent(events: source)
129 | }
130 |
131 | /// Reactive wrapper for delegate method `scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)`
132 | public var willEndDragging: ControlEvent.WillEndDraggingEvent> {
133 | let source = delegate.methodInvoked(#selector(ASCollectionDelegate.scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)))
134 | .map { value -> Reactive.WillEndDraggingEvent in
135 | let velocity = try castOrThrow(CGPoint.self, value[1])
136 | let targetContentOffsetValue = try castOrThrow(NSValue.self, value[2])
137 |
138 | guard let rawPointer = targetContentOffsetValue.pointerValue else { throw RxCocoaError.unknown }
139 | let typedPointer = rawPointer.bindMemory(to: CGPoint.self, capacity: MemoryLayout.size)
140 |
141 | return (velocity, typedPointer)
142 | }
143 | return ControlEvent(events: source)
144 | }
145 |
146 | /// Reactive wrapper for delegate method `scrollViewDidEndDragging(_:willDecelerate:)`
147 | public var didEndDragging: ControlEvent {
148 | let source = delegate.methodInvoked(#selector(ASCollectionDelegate.scrollViewDidEndDragging(_:willDecelerate:))).map { value -> Bool in
149 | return try castOrThrow(Bool.self, value[1])
150 | }
151 | return ControlEvent(events: source)
152 | }
153 |
154 | /// Reactive wrapper for `delegate` message `collectionNode(_:didSelectItemAtIndexPath:)`.
155 | public var itemSelected: ControlEvent {
156 | let source = delegate.methodInvoked(#selector(ASCollectionDelegate.collectionNode(_:didSelectItemAt:)))
157 | .map { a in
158 | return try castOrThrow(IndexPath.self, a[1])
159 | }
160 |
161 | return ControlEvent(events: source)
162 | }
163 |
164 | /// Reactive wrapper for `delegate` message `collectionNode(_:didSelectItemAtIndexPath:)`.
165 | public var itemDeselected: ControlEvent {
166 | let source = delegate.methodInvoked(#selector(ASCollectionDelegate.collectionNode(_:didDeselectItemAt:)))
167 | .map { a in
168 | return try castOrThrow(IndexPath.self, a[1])
169 | }
170 |
171 | return ControlEvent(events: source)
172 | }
173 |
174 | /// Reactive wrapper for `delegate` message `ASCollectionDelegate(_:didHighlightItemAt:)`.
175 | public var itemHighlighted: ControlEvent {
176 | let source = delegate.methodInvoked(#selector(ASCollectionDelegate.collectionNode(_:didHighlightItemAt:)))
177 | .map { a in
178 | return try castOrThrow(IndexPath.self, a[1])
179 | }
180 |
181 | return ControlEvent(events: source)
182 | }
183 |
184 | /// Reactive wrapper for `delegate` message `collectionNode(_:didUnhighlightItemAt:)`.
185 | public var itemUnhighlighted: ControlEvent {
186 | let source = delegate.methodInvoked(#selector(ASCollectionDelegate.collectionNode(_:didUnhighlightItemAt:)))
187 | .map { a in
188 | return try castOrThrow(IndexPath.self, a[1])
189 | }
190 |
191 | return ControlEvent(events: source)
192 | }
193 |
194 | /// Reactive wrapper for `delegate` message `collectionNode:willDisplayItemWith:`.
195 | public var willDisplayItem: ControlEvent {
196 | let source: Observable = self.delegate.methodInvoked(#selector(ASCollectionDelegate.collectionNode(_:willDisplayItemWith:)))
197 | .map { a in
198 | return (try castOrThrow(ASCellNode.self, a[1]))
199 | }
200 |
201 | return ControlEvent(events: source)
202 | }
203 |
204 | /// Reactive wrapper for `delegate` message `collectionNode:willDisplaySupplementaryElementWith:`.
205 | public var willDisplaySupplementaryElement: ControlEvent {
206 | let source: Observable = self.delegate.methodInvoked(#selector(ASCollectionDelegate.collectionNode(_:willDisplaySupplementaryElementWith:)))
207 | .map { a in
208 | return (try castOrThrow(ASCellNode.self, a[1]))
209 | }
210 |
211 | return ControlEvent(events: source)
212 | }
213 |
214 | /// Reactive wrapper for `delegate` message `collectionView:didEndDisplaying:forItemAt:`.
215 | public var didEndDisplayingItem: ControlEvent {
216 | let source: Observable = self.delegate.methodInvoked(#selector(ASCollectionDelegate.collectionNode(_:didEndDisplayingItemWith:)))
217 | .map { a in
218 | return (try castOrThrow(ASCellNode.self, a[1]))
219 | }
220 |
221 | return ControlEvent(events: source)
222 | }
223 |
224 | /// Reactive wrapper for `delegate` message `collectionNode(_:didEndDisplayingSupplementaryElementWith:)`.
225 | public var didEndDisplayingSupplementaryElement: ControlEvent {
226 | let source: Observable = self.delegate.methodInvoked(#selector(ASCollectionDelegate.collectionNode(_:didEndDisplayingSupplementaryElementWith:)))
227 | .map { a in
228 | return (try castOrThrow(ASCellNode.self, a[1]))
229 | }
230 |
231 | return ControlEvent(events: source)
232 | }
233 |
234 | /// Reactive wrapper for `delegate` message `collectionNode(_:willBeginBatchFetchWith:)`
235 | public var willBeginBatchFetch: ControlEvent {
236 | let source: Observable = self.delegate.methodInvoked(#selector(ASCollectionDelegate.collectionNode(_:willBeginBatchFetchWith:)))
237 | .map { a in
238 | return try castOrThrow(ASBatchContext.self, a[1])
239 | }
240 |
241 | return ControlEvent(events: source)
242 | }
243 |
244 | /// Reactive wrapper for `delegate` message `collectionNode(_:didSelectItemAtIndexPath:)`.
245 | ///
246 | /// It can be only used when one of the `rx.itemsWith*` methods is used to bind observable sequence,
247 | /// or any other data source conforming to `SectionedViewDataSourceType` protocol.
248 | ///
249 | /// ```
250 | /// collectionNode.rx.modelSelected(MyModel.self)
251 | /// .map { ...
252 | /// ```
253 | public func modelSelected(_ modelType: T.Type) -> ControlEvent {
254 | let source: Observable = itemSelected.flatMap { [weak node = self.base as ASCollectionNode] indexPath -> Observable in
255 | guard let node = node else {
256 | return Observable.empty()
257 | }
258 |
259 | return Observable.just(try node.rx.model(at: indexPath))
260 | }
261 |
262 | return ControlEvent(events: source)
263 | }
264 |
265 | /// Reactive wrapper for `delegate` message `collectionNode(_:didSelectItemAtIndexPath:)`.
266 | ///
267 | /// It can be only used when one of the `rx.itemsWith*` methods is used to bind observable sequence,
268 | /// or any other data source conforming to `SectionedViewDataSourceType` protocol.
269 | ///
270 | /// ```
271 | /// collectionNode.rx.modelDeselected(MyModel.self)
272 | /// .map { ...
273 | /// ```
274 | public func modelDeselected(_ modelType: T.Type) -> ControlEvent {
275 | let source: Observable = itemDeselected.flatMap { [weak node = self.base as ASCollectionNode] indexPath -> Observable in
276 | guard let node = node else {
277 | return Observable.empty()
278 | }
279 |
280 | return Observable.just(try node.rx.model(at: indexPath))
281 | }
282 |
283 | return ControlEvent(events: source)
284 | }
285 |
286 | /// Synchronous helper method for retrieving a model at indexPath through a reactive data source
287 | public func model(at indexPath: IndexPath) throws -> T {
288 | let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.itemsWith*` methods was used.")
289 |
290 | let element = try dataSource.model(at: indexPath)
291 |
292 | return try castOrThrow(T.self, element)
293 | }
294 | }
295 |
296 | #endif
297 |
--------------------------------------------------------------------------------
/Sources/RxDataSources-Texture/ASTableNode/ASTableNode+Rx.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ASTableNode+Rx.swift
3 | // RxDataSources-Texture
4 | //
5 | // Created by Kanghoon on 19/02/2019.
6 | //
7 |
8 | #if os(iOS) || os(tvOS)
9 |
10 | import Foundation
11 | import AsyncDisplayKit
12 | import RxSwift
13 | import RxCocoa
14 |
15 | #if swift(>=4.2)
16 | public typealias UITableViewCellEditingStyle = UITableViewCell.EditingStyle
17 | #endif
18 |
19 | // Items
20 |
21 | extension Reactive where Base: ASTableNode {
22 |
23 | /**
24 | Binds sequences of elements to table node rows using a custom reactive data used to perform the transformation.
25 | This method will retain the data source for as long as the subscription isn't disposed (result `Disposable`
26 | being disposed).
27 | In case `source` observable sequence terminates successfully, the data source will present latest element
28 | until the subscription isn't disposed.
29 |
30 | - parameter dataSource: Data source used to transform elements to cell nodes.
31 | - parameter source: Observable sequence of items.
32 | - returns: Disposable object that can be used to unbind.
33 | */
34 | public func items<
35 | DataSource: RxASTableDataSourceType & ASTableDataSource,
36 | O: ObservableType>
37 | (dataSource: DataSource)
38 | -> (_ source: O)
39 | -> Disposable
40 | where DataSource.Element == O.Element {
41 | return { source in
42 | // Strong reference is needed because data source is in use until result subscription is disposed
43 | return source.subscribeProxyDataSource(ofObject: self.base, dataSource: dataSource as ASTableDataSource, retainDataSource: true) { [weak tableNode = self.base] (_: RxASTableDataSourceProxy, event) -> Void in
44 | guard let tableNode = tableNode else {
45 | return
46 | }
47 | dataSource.tableNode(tableNode, observedEvent: event)
48 | }
49 | }
50 | }
51 |
52 | }
53 |
54 | extension Reactive where Base: ASTableNode {
55 |
56 | /**
57 | Reactive wrapper for `delegate`.
58 |
59 | For more information take a look at `DelegateProxyType` protocol documentation.
60 | */
61 | public var delegate: DelegateProxy {
62 | return RxASTableDelegateProxy.proxy(for: base)
63 | }
64 |
65 | /**
66 | Reactive wrapper for `dataSource`.
67 |
68 | For more information take a look at `DelegateProxyType` protocol documentation.
69 | */
70 | public var dataSource: DelegateProxy {
71 | return RxASTableDataSourceProxy.proxy(for: base)
72 | }
73 |
74 | /**
75 | Installs data source as forwarding delegate on `rx.dataSource`.
76 | Data source won't be retained.
77 |
78 | It enables using normal delegate mechanism with reactive delegate mechanism.
79 |
80 | - parameter dataSource: Data source object.
81 | - returns: Disposable object that can be used to unbind the data source.
82 | */
83 | public func setDataSource(_ dataSource: ASTableDataSource) -> Disposable {
84 | return ScheduledDisposable(
85 | scheduler: MainScheduler.instance,
86 | disposable: RxASTableDataSourceProxy.installForwardDelegate(dataSource, retainDelegate: false, onProxyForObject: self.base)
87 | )
88 | }
89 |
90 | /**
91 | Installs delegate as forwarding delegate on `rx.delegate`.
92 | Data source won't be retained.
93 |
94 | It enables using normal delegate mechanism with reactive delegate mechanism.
95 |
96 | - parameter delegate: Delegate object
97 | - returns: Disposable object that can be used to unbind the delegate.
98 | */
99 | public func setDelegate(_ delegate: ASTableDelegate) -> Disposable {
100 | return ScheduledDisposable(
101 | scheduler: MainScheduler.instance,
102 | disposable: RxASTableDelegateProxy.installForwardDelegate(delegate, retainDelegate: false, onProxyForObject: self.base)
103 | )
104 | }
105 |
106 | /// Reactive wrapper for `contentOffset`.
107 | public var contentOffset: ControlProperty {
108 | let proxy = RxASTableDelegateProxy.proxy(for: base)
109 |
110 | let bindingObserver = Binder(self.base) { tableNode, contentOffset in
111 | tableNode.contentOffset = contentOffset
112 | }
113 |
114 | return ControlProperty(values: proxy.contentOffsetBehaviorSubject, valueSink: bindingObserver)
115 | }
116 |
117 | /// Reactive wrapper for delegate method `scrollViewDidScroll`
118 | public var didScroll: ControlEvent {
119 | let source = RxASTableDelegateProxy.proxy(for: base).contentOffsetPublishSubject
120 | return ControlEvent(events: source)
121 | }
122 |
123 | /// Reactive wrapper for delegate method `scrollViewWillBeginDecelerating`
124 | public var willBeginDecelerating: ControlEvent {
125 | let source = delegate.methodInvoked(#selector(ASTableDelegate.scrollViewWillBeginDecelerating(_:))).map { _ in }
126 | return ControlEvent(events: source)
127 | }
128 |
129 | /// Reactive wrapper for delegate method `scrollViewDidEndDecelerating`
130 | public var didEndDecelerating: ControlEvent {
131 | let source = delegate.methodInvoked(#selector(ASTableDelegate.scrollViewDidEndDecelerating(_:))).map { _ in }
132 | return ControlEvent(events: source)
133 | }
134 |
135 | /// Reactive wrapper for delegate method `scrollViewWillBeginDragging`
136 | public var willBeginDragging: ControlEvent {
137 | let source = delegate.methodInvoked(#selector(ASTableDelegate.scrollViewWillBeginDragging(_:))).map { _ in }
138 | return ControlEvent(events: source)
139 | }
140 |
141 | /// Reactive wrapper for delegate method `scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)`
142 | public var willEndDragging: ControlEvent.WillEndDraggingEvent> {
143 | let source = delegate.methodInvoked(#selector(ASTableDelegate.scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)))
144 | .map { value -> Reactive.WillEndDraggingEvent in
145 | let velocity = try castOrThrow(CGPoint.self, value[1])
146 | let targetContentOffsetValue = try castOrThrow(NSValue.self, value[2])
147 |
148 | guard let rawPointer = targetContentOffsetValue.pointerValue else { throw RxCocoaError.unknown }
149 | let typedPointer = rawPointer.bindMemory(to: CGPoint.self, capacity: MemoryLayout.size)
150 |
151 | return (velocity, typedPointer)
152 | }
153 | return ControlEvent(events: source)
154 | }
155 |
156 | /// Reactive wrapper for delegate method `scrollViewDidEndDragging(_:willDecelerate:)`
157 | public var didEndDragging: ControlEvent {
158 | let source = delegate.methodInvoked(#selector(ASTableDelegate.scrollViewDidEndDragging(_:willDecelerate:))).map { value -> Bool in
159 | return try castOrThrow(Bool.self, value[1])
160 | }
161 | return ControlEvent(events: source)
162 | }
163 |
164 | /**
165 | Reactive wrapper for `delegate` message `tableNode:didSelectRowAtIndexPath:`.
166 | */
167 | public var itemSelected: ControlEvent {
168 | let source = self.delegate.methodInvoked(#selector(ASTableDelegate.tableNode(_:didSelectRowAt:)))
169 | .map { a in
170 | return try castOrThrow(IndexPath.self, a[1])
171 | }
172 |
173 | return ControlEvent(events: source)
174 | }
175 |
176 | /**
177 | Reactive wrapper for `delegate` message `tableNode:didDeselectRowAtIndexPath:`.
178 | */
179 | public var itemDeselected: ControlEvent {
180 | let source = self.delegate.methodInvoked(#selector(ASTableDelegate.tableNode(_:didDeselectRowAt:)))
181 | .map { a in
182 | return try castOrThrow(IndexPath.self, a[1])
183 | }
184 |
185 | return ControlEvent(events: source)
186 | }
187 |
188 | /**
189 | Reactive wrapper for `delegate` message `tableView:commitEditingStyle:forRowAtIndexPath:`.
190 | */
191 | public var itemInserted: ControlEvent {
192 | let source = self.dataSource.methodInvoked(#selector(ASTableDataSource.tableView(_:commit:forRowAt:)))
193 | .filter { a in
194 | return UITableViewCellEditingStyle(rawValue: (try castOrThrow(NSNumber.self, a[1])).intValue) == .insert
195 | }
196 | .map { a in
197 | return (try castOrThrow(IndexPath.self, a[2]))
198 | }
199 |
200 | return ControlEvent(events: source)
201 | }
202 |
203 | /**
204 | Reactive wrapper for `delegate` message `tableView:commitEditingStyle:forRowAtIndexPath:`.
205 | */
206 | public var itemDeleted: ControlEvent {
207 | let source = self.dataSource.methodInvoked(#selector(ASTableDataSource.tableView(_:commit:forRowAt:)))
208 | .filter { a in
209 | return UITableViewCellEditingStyle(rawValue: (try castOrThrow(NSNumber.self, a[1])).intValue) == .delete
210 | }
211 | .map { a in
212 | return try castOrThrow(IndexPath.self, a[2])
213 | }
214 |
215 | return ControlEvent(events: source)
216 | }
217 |
218 | /**
219 | Reactive wrapper for `delegate` message `tableView:moveRowAtIndexPath:toIndexPath:`.
220 | */
221 | public var itemMoved: ControlEvent {
222 | let source: Observable = self.dataSource.methodInvoked(#selector(ASTableDataSource.tableView(_:moveRowAt:to:)))
223 | .map { a in
224 | return (try castOrThrow(IndexPath.self, a[1]), try castOrThrow(IndexPath.self, a[2]))
225 | }
226 |
227 | return ControlEvent(events: source)
228 | }
229 |
230 | /**
231 | Reactive wrapper for `delegate` message `tableNode:willDisplayCell:forRowAtIndexPath:`.
232 | */
233 | public var willDisplayCell: ControlEvent {
234 | let source: Observable = self.delegate.methodInvoked(#selector(ASTableDelegate.tableNode(_:willDisplayRowWith:)))
235 | .map { a in
236 | return try castOrThrow(ASCellNode.self, a[1])
237 | }
238 |
239 | return ControlEvent(events: source)
240 | }
241 |
242 | /**
243 | Reactive wrapper for `delegate` message `tableNode:didEndDisplayingCell:forRowAtIndexPath:`.
244 | */
245 | public var didEndDisplayingCell: ControlEvent {
246 | let source: Observable = self.delegate.methodInvoked(#selector(ASTableDelegate.tableNode(_:didEndDisplayingRowWith:)))
247 | .map { a in
248 | return try castOrThrow(ASCellNode.self, a[1])
249 | }
250 |
251 | return ControlEvent(events: source)
252 | }
253 |
254 | /**
255 | Reactive wrapper for `delegate` message `tableNode:willBeginBatchFetchWith`
256 | */
257 | public var willBeginBatchFetch: ControlEvent {
258 | let source: Observable = self.delegate.methodInvoked(#selector(ASTableDelegate.tableNode(_:willBeginBatchFetchWith:)))
259 | .map { a in
260 | return try castOrThrow(ASBatchContext.self, a[1])
261 | }
262 |
263 | return ControlEvent(events: source)
264 | }
265 |
266 | /**
267 | Reactive wrapper for `delegate` message `tableNode:didSelectRowAtIndexPath:`.
268 |
269 | It can be only used when one of the `rx.itemsWith*` methods is used to bind observable sequence,
270 | or any other data source conforming to `SectionedViewDataSourceType` protocol.
271 |
272 | ```
273 | tableNode.rx.modelSelected(MyModel.self)
274 | .map { ...
275 | ```
276 | */
277 | public func modelSelected(_ modelType: T.Type) -> ControlEvent {
278 | let source: Observable = self.itemSelected.flatMap { [weak view = self.base as ASTableNode] indexPath -> Observable in
279 | guard let view = view else {
280 | return Observable.empty()
281 | }
282 |
283 | return Observable.just(try view.rx.model(at: indexPath))
284 | }
285 |
286 | return ControlEvent(events: source)
287 | }
288 |
289 | /**
290 | Reactive wrapper for `delegate` message `tableNode:didDeselectRowAtIndexPath:`.
291 |
292 | It can be only used when one of the `rx.itemsWith*` methods is used to bind observable sequence,
293 | or any other data source conforming to `SectionedViewDataSourceType` protocol.
294 |
295 | ```
296 | tableNode.rx.modelDeselected(MyModel.self)
297 | .map { ...
298 | ```
299 | */
300 | public func modelDeselected(_ modelType: T.Type) -> ControlEvent {
301 | let source: Observable = self.itemDeselected.flatMap { [weak view = self.base as ASTableNode] indexPath -> Observable in
302 | guard let view = view else {
303 | return Observable.empty()
304 | }
305 |
306 | return Observable.just(try view.rx.model(at: indexPath))
307 | }
308 |
309 | return ControlEvent(events: source)
310 | }
311 |
312 | /**
313 | Reactive wrapper for `delegate` message `tableNode:commitEditingStyle:forRowAtIndexPath:`.
314 |
315 | It can be only used when one of the `rx.itemsWith*` methods is used to bind observable sequence,
316 | or any other data source conforming to `SectionedViewDataSourceType` protocol.
317 |
318 | ```
319 | tableNode.rx.modelDeleted(MyModel.self)
320 | .map { ...
321 | ```
322 | */
323 | public func modelDeleted(_ modelType: T.Type) -> ControlEvent {
324 | let source: Observable = self.itemDeleted.flatMap { [weak view = self.base as ASTableNode] indexPath -> Observable in
325 | guard let view = view else {
326 | return Observable.empty()
327 | }
328 |
329 | return Observable.just(try view.rx.model(at: indexPath))
330 | }
331 |
332 | return ControlEvent(events: source)
333 | }
334 |
335 | /**
336 | Synchronous helper method for retrieving a model at indexPath through a reactive data source.
337 | */
338 | public func model(at indexPath: IndexPath) throws -> T {
339 | let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.items*` methods was used.")
340 |
341 | let element = try dataSource.model(at: indexPath)
342 |
343 | return castOrFatalError(element)
344 | }
345 | }
346 |
347 | #endif
348 |
--------------------------------------------------------------------------------
/Example/GithubRepos.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 51;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | BA49E5D0243310F600173DEB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49E5CF243310F600173DEB /* AppDelegate.swift */; };
11 | BA49E5D9243310F700173DEB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BA49E5D8243310F700173DEB /* Assets.xcassets */; };
12 | BA49E5DC243310F700173DEB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BA49E5DA243310F700173DEB /* LaunchScreen.storyboard */; };
13 | BA49E5FB2433158D00173DEB /* MainSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49E5F32433158D00173DEB /* MainSection.swift */; };
14 | BA49E5FC2433158D00173DEB /* RepoCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49E5F42433158D00173DEB /* RepoCollectionViewController.swift */; };
15 | BA49E5FD2433158D00173DEB /* Repo.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49E5F52433158D00173DEB /* Repo.swift */; };
16 | BA49E5FE2433158D00173DEB /* CategoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49E5F62433158D00173DEB /* CategoryViewController.swift */; };
17 | BA49E5FF2433158D00173DEB /* GithubService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49E5F72433158D00173DEB /* GithubService.swift */; };
18 | BA49E6002433158D00173DEB /* RepoTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49E5F82433158D00173DEB /* RepoTableViewController.swift */; };
19 | BA49E6012433158D00173DEB /* RepoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49E5F92433158D00173DEB /* RepoViewModel.swift */; };
20 | BA49E6022433158D00173DEB /* RepoCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49E5FA2433158D00173DEB /* RepoCellNode.swift */; };
21 | FE51C2432BFAFAA7BCDBBCF3 /* Pods_GithubRepos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDB2D3249C3CFA839DC09BFD /* Pods_GithubRepos.framework */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXFileReference section */
25 | 0D21F617731032C2765DE48E /* Pods-GithubRepos.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GithubRepos.release.xcconfig"; path = "Target Support Files/Pods-GithubRepos/Pods-GithubRepos.release.xcconfig"; sourceTree = ""; };
26 | 89E31213DA9072326427B2AC /* Pods-GithubRepos.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GithubRepos.debug.xcconfig"; path = "Target Support Files/Pods-GithubRepos/Pods-GithubRepos.debug.xcconfig"; sourceTree = ""; };
27 | BA49E5CC243310F600173DEB /* GithubRepos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GithubRepos.app; sourceTree = BUILT_PRODUCTS_DIR; };
28 | BA49E5CF243310F600173DEB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
29 | BA49E5D8243310F700173DEB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
30 | BA49E5DB243310F700173DEB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
31 | BA49E5DD243310F700173DEB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
32 | BA49E5F32433158D00173DEB /* MainSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainSection.swift; sourceTree = ""; };
33 | BA49E5F42433158D00173DEB /* RepoCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepoCollectionViewController.swift; sourceTree = ""; };
34 | BA49E5F52433158D00173DEB /* Repo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Repo.swift; sourceTree = ""; };
35 | BA49E5F62433158D00173DEB /* CategoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryViewController.swift; sourceTree = ""; };
36 | BA49E5F72433158D00173DEB /* GithubService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GithubService.swift; sourceTree = ""; };
37 | BA49E5F82433158D00173DEB /* RepoTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepoTableViewController.swift; sourceTree = ""; };
38 | BA49E5F92433158D00173DEB /* RepoViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepoViewModel.swift; sourceTree = ""; };
39 | BA49E5FA2433158D00173DEB /* RepoCellNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepoCellNode.swift; sourceTree = ""; };
40 | BDB2D3249C3CFA839DC09BFD /* Pods_GithubRepos.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GithubRepos.framework; sourceTree = BUILT_PRODUCTS_DIR; };
41 | /* End PBXFileReference section */
42 |
43 | /* Begin PBXFrameworksBuildPhase section */
44 | BA49E5C9243310F600173DEB /* Frameworks */ = {
45 | isa = PBXFrameworksBuildPhase;
46 | buildActionMask = 2147483647;
47 | files = (
48 | FE51C2432BFAFAA7BCDBBCF3 /* Pods_GithubRepos.framework in Frameworks */,
49 | );
50 | runOnlyForDeploymentPostprocessing = 0;
51 | };
52 | /* End PBXFrameworksBuildPhase section */
53 |
54 | /* Begin PBXGroup section */
55 | 651354D5072A9140EE4FCC8F /* Pods */ = {
56 | isa = PBXGroup;
57 | children = (
58 | 89E31213DA9072326427B2AC /* Pods-GithubRepos.debug.xcconfig */,
59 | 0D21F617731032C2765DE48E /* Pods-GithubRepos.release.xcconfig */,
60 | );
61 | path = Pods;
62 | sourceTree = "";
63 | };
64 | BA49E5C3243310F600173DEB = {
65 | isa = PBXGroup;
66 | children = (
67 | BA49E5CE243310F600173DEB /* GithubRepos */,
68 | BA49E5CD243310F600173DEB /* Products */,
69 | 651354D5072A9140EE4FCC8F /* Pods */,
70 | F5A318878258C967C374CA2D /* Frameworks */,
71 | );
72 | sourceTree = "";
73 | };
74 | BA49E5CD243310F600173DEB /* Products */ = {
75 | isa = PBXGroup;
76 | children = (
77 | BA49E5CC243310F600173DEB /* GithubRepos.app */,
78 | );
79 | name = Products;
80 | sourceTree = "";
81 | };
82 | BA49E5CE243310F600173DEB /* GithubRepos */ = {
83 | isa = PBXGroup;
84 | children = (
85 | BA49E5F62433158D00173DEB /* CategoryViewController.swift */,
86 | BA49E5F72433158D00173DEB /* GithubService.swift */,
87 | BA49E5F32433158D00173DEB /* MainSection.swift */,
88 | BA49E5F52433158D00173DEB /* Repo.swift */,
89 | BA49E5FA2433158D00173DEB /* RepoCellNode.swift */,
90 | BA49E5F42433158D00173DEB /* RepoCollectionViewController.swift */,
91 | BA49E5F82433158D00173DEB /* RepoTableViewController.swift */,
92 | BA49E5F92433158D00173DEB /* RepoViewModel.swift */,
93 | BA49E5CF243310F600173DEB /* AppDelegate.swift */,
94 | BA49E5D8243310F700173DEB /* Assets.xcassets */,
95 | BA49E5DA243310F700173DEB /* LaunchScreen.storyboard */,
96 | BA49E5DD243310F700173DEB /* Info.plist */,
97 | );
98 | path = GithubRepos;
99 | sourceTree = "";
100 | };
101 | F5A318878258C967C374CA2D /* Frameworks */ = {
102 | isa = PBXGroup;
103 | children = (
104 | BDB2D3249C3CFA839DC09BFD /* Pods_GithubRepos.framework */,
105 | );
106 | name = Frameworks;
107 | sourceTree = "";
108 | };
109 | /* End PBXGroup section */
110 |
111 | /* Begin PBXNativeTarget section */
112 | BA49E5CB243310F600173DEB /* GithubRepos */ = {
113 | isa = PBXNativeTarget;
114 | buildConfigurationList = BA49E5E0243310F700173DEB /* Build configuration list for PBXNativeTarget "GithubRepos" */;
115 | buildPhases = (
116 | 82D0BE1985141EE1B1F8C31C /* [CP] Check Pods Manifest.lock */,
117 | BA49E5C8243310F600173DEB /* Sources */,
118 | BA49E5C9243310F600173DEB /* Frameworks */,
119 | BA49E5CA243310F600173DEB /* Resources */,
120 | 8A53AC4E57C28518119D0262 /* [CP] Embed Pods Frameworks */,
121 | );
122 | buildRules = (
123 | );
124 | dependencies = (
125 | );
126 | name = GithubRepos;
127 | productName = GithubUsers;
128 | productReference = BA49E5CC243310F600173DEB /* GithubRepos.app */;
129 | productType = "com.apple.product-type.application";
130 | };
131 | /* End PBXNativeTarget section */
132 |
133 | /* Begin PBXProject section */
134 | BA49E5C4243310F600173DEB /* Project object */ = {
135 | isa = PBXProject;
136 | attributes = {
137 | LastSwiftUpdateCheck = 1130;
138 | LastUpgradeCheck = 1130;
139 | ORGANIZATIONNAME = kanghoon;
140 | TargetAttributes = {
141 | BA49E5CB243310F600173DEB = {
142 | CreatedOnToolsVersion = 11.3.1;
143 | };
144 | };
145 | };
146 | buildConfigurationList = BA49E5C7243310F600173DEB /* Build configuration list for PBXProject "GithubRepos" */;
147 | compatibilityVersion = "Xcode 9.3";
148 | developmentRegion = en;
149 | hasScannedForEncodings = 0;
150 | knownRegions = (
151 | en,
152 | Base,
153 | );
154 | mainGroup = BA49E5C3243310F600173DEB;
155 | productRefGroup = BA49E5CD243310F600173DEB /* Products */;
156 | projectDirPath = "";
157 | projectRoot = "";
158 | targets = (
159 | BA49E5CB243310F600173DEB /* GithubRepos */,
160 | );
161 | };
162 | /* End PBXProject section */
163 |
164 | /* Begin PBXResourcesBuildPhase section */
165 | BA49E5CA243310F600173DEB /* Resources */ = {
166 | isa = PBXResourcesBuildPhase;
167 | buildActionMask = 2147483647;
168 | files = (
169 | BA49E5DC243310F700173DEB /* LaunchScreen.storyboard in Resources */,
170 | BA49E5D9243310F700173DEB /* Assets.xcassets in Resources */,
171 | );
172 | runOnlyForDeploymentPostprocessing = 0;
173 | };
174 | /* End PBXResourcesBuildPhase section */
175 |
176 | /* Begin PBXShellScriptBuildPhase section */
177 | 82D0BE1985141EE1B1F8C31C /* [CP] Check Pods Manifest.lock */ = {
178 | isa = PBXShellScriptBuildPhase;
179 | buildActionMask = 2147483647;
180 | files = (
181 | );
182 | inputFileListPaths = (
183 | );
184 | inputPaths = (
185 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
186 | "${PODS_ROOT}/Manifest.lock",
187 | );
188 | name = "[CP] Check Pods Manifest.lock";
189 | outputFileListPaths = (
190 | );
191 | outputPaths = (
192 | "$(DERIVED_FILE_DIR)/Pods-GithubRepos-checkManifestLockResult.txt",
193 | );
194 | runOnlyForDeploymentPostprocessing = 0;
195 | shellPath = /bin/sh;
196 | 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";
197 | showEnvVarsInLog = 0;
198 | };
199 | 8A53AC4E57C28518119D0262 /* [CP] Embed Pods Frameworks */ = {
200 | isa = PBXShellScriptBuildPhase;
201 | buildActionMask = 2147483647;
202 | files = (
203 | );
204 | inputFileListPaths = (
205 | "${PODS_ROOT}/Target Support Files/Pods-GithubRepos/Pods-GithubRepos-frameworks-${CONFIGURATION}-input-files.xcfilelist",
206 | );
207 | name = "[CP] Embed Pods Frameworks";
208 | outputFileListPaths = (
209 | "${PODS_ROOT}/Target Support Files/Pods-GithubRepos/Pods-GithubRepos-frameworks-${CONFIGURATION}-output-files.xcfilelist",
210 | );
211 | runOnlyForDeploymentPostprocessing = 0;
212 | shellPath = /bin/sh;
213 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GithubRepos/Pods-GithubRepos-frameworks.sh\"\n";
214 | showEnvVarsInLog = 0;
215 | };
216 | /* End PBXShellScriptBuildPhase section */
217 |
218 | /* Begin PBXSourcesBuildPhase section */
219 | BA49E5C8243310F600173DEB /* Sources */ = {
220 | isa = PBXSourcesBuildPhase;
221 | buildActionMask = 2147483647;
222 | files = (
223 | BA49E6012433158D00173DEB /* RepoViewModel.swift in Sources */,
224 | BA49E6002433158D00173DEB /* RepoTableViewController.swift in Sources */,
225 | BA49E5FE2433158D00173DEB /* CategoryViewController.swift in Sources */,
226 | BA49E5FC2433158D00173DEB /* RepoCollectionViewController.swift in Sources */,
227 | BA49E5FF2433158D00173DEB /* GithubService.swift in Sources */,
228 | BA49E6022433158D00173DEB /* RepoCellNode.swift in Sources */,
229 | BA49E5FB2433158D00173DEB /* MainSection.swift in Sources */,
230 | BA49E5D0243310F600173DEB /* AppDelegate.swift in Sources */,
231 | BA49E5FD2433158D00173DEB /* Repo.swift in Sources */,
232 | );
233 | runOnlyForDeploymentPostprocessing = 0;
234 | };
235 | /* End PBXSourcesBuildPhase section */
236 |
237 | /* Begin PBXVariantGroup section */
238 | BA49E5DA243310F700173DEB /* LaunchScreen.storyboard */ = {
239 | isa = PBXVariantGroup;
240 | children = (
241 | BA49E5DB243310F700173DEB /* Base */,
242 | );
243 | name = LaunchScreen.storyboard;
244 | sourceTree = "";
245 | };
246 | /* End PBXVariantGroup section */
247 |
248 | /* Begin XCBuildConfiguration section */
249 | BA49E5DE243310F700173DEB /* Debug */ = {
250 | isa = XCBuildConfiguration;
251 | buildSettings = {
252 | ALWAYS_SEARCH_USER_PATHS = NO;
253 | CLANG_ANALYZER_NONNULL = YES;
254 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
255 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
256 | CLANG_CXX_LIBRARY = "libc++";
257 | CLANG_ENABLE_MODULES = YES;
258 | CLANG_ENABLE_OBJC_ARC = YES;
259 | CLANG_ENABLE_OBJC_WEAK = YES;
260 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
261 | CLANG_WARN_BOOL_CONVERSION = YES;
262 | CLANG_WARN_COMMA = YES;
263 | CLANG_WARN_CONSTANT_CONVERSION = YES;
264 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
266 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
267 | CLANG_WARN_EMPTY_BODY = YES;
268 | CLANG_WARN_ENUM_CONVERSION = YES;
269 | CLANG_WARN_INFINITE_RECURSION = YES;
270 | CLANG_WARN_INT_CONVERSION = YES;
271 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
272 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
273 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
274 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
275 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
276 | CLANG_WARN_STRICT_PROTOTYPES = YES;
277 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
278 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
279 | CLANG_WARN_UNREACHABLE_CODE = YES;
280 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
281 | COPY_PHASE_STRIP = NO;
282 | DEBUG_INFORMATION_FORMAT = dwarf;
283 | ENABLE_STRICT_OBJC_MSGSEND = YES;
284 | ENABLE_TESTABILITY = YES;
285 | GCC_C_LANGUAGE_STANDARD = gnu11;
286 | GCC_DYNAMIC_NO_PIC = NO;
287 | GCC_NO_COMMON_BLOCKS = YES;
288 | GCC_OPTIMIZATION_LEVEL = 0;
289 | GCC_PREPROCESSOR_DEFINITIONS = (
290 | "DEBUG=1",
291 | "$(inherited)",
292 | );
293 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
294 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
295 | GCC_WARN_UNDECLARED_SELECTOR = YES;
296 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
297 | GCC_WARN_UNUSED_FUNCTION = YES;
298 | GCC_WARN_UNUSED_VARIABLE = YES;
299 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
300 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
301 | MTL_FAST_MATH = YES;
302 | ONLY_ACTIVE_ARCH = YES;
303 | SDKROOT = iphoneos;
304 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
305 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
306 | };
307 | name = Debug;
308 | };
309 | BA49E5DF243310F700173DEB /* Release */ = {
310 | isa = XCBuildConfiguration;
311 | buildSettings = {
312 | ALWAYS_SEARCH_USER_PATHS = NO;
313 | CLANG_ANALYZER_NONNULL = YES;
314 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
315 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
316 | CLANG_CXX_LIBRARY = "libc++";
317 | CLANG_ENABLE_MODULES = YES;
318 | CLANG_ENABLE_OBJC_ARC = YES;
319 | CLANG_ENABLE_OBJC_WEAK = YES;
320 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
321 | CLANG_WARN_BOOL_CONVERSION = YES;
322 | CLANG_WARN_COMMA = YES;
323 | CLANG_WARN_CONSTANT_CONVERSION = YES;
324 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
325 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
326 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
327 | CLANG_WARN_EMPTY_BODY = YES;
328 | CLANG_WARN_ENUM_CONVERSION = YES;
329 | CLANG_WARN_INFINITE_RECURSION = YES;
330 | CLANG_WARN_INT_CONVERSION = YES;
331 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
332 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
333 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
334 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
335 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
336 | CLANG_WARN_STRICT_PROTOTYPES = YES;
337 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
338 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
339 | CLANG_WARN_UNREACHABLE_CODE = YES;
340 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
341 | COPY_PHASE_STRIP = NO;
342 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
343 | ENABLE_NS_ASSERTIONS = NO;
344 | ENABLE_STRICT_OBJC_MSGSEND = YES;
345 | GCC_C_LANGUAGE_STANDARD = gnu11;
346 | GCC_NO_COMMON_BLOCKS = YES;
347 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
348 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
349 | GCC_WARN_UNDECLARED_SELECTOR = YES;
350 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
351 | GCC_WARN_UNUSED_FUNCTION = YES;
352 | GCC_WARN_UNUSED_VARIABLE = YES;
353 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
354 | MTL_ENABLE_DEBUG_INFO = NO;
355 | MTL_FAST_MATH = YES;
356 | SDKROOT = iphoneos;
357 | SWIFT_COMPILATION_MODE = wholemodule;
358 | SWIFT_OPTIMIZATION_LEVEL = "-O";
359 | VALIDATE_PRODUCT = YES;
360 | };
361 | name = Release;
362 | };
363 | BA49E5E1243310F700173DEB /* Debug */ = {
364 | isa = XCBuildConfiguration;
365 | baseConfigurationReference = 89E31213DA9072326427B2AC /* Pods-GithubRepos.debug.xcconfig */;
366 | buildSettings = {
367 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
368 | CODE_SIGN_STYLE = Automatic;
369 | INFOPLIST_FILE = "$(SRCROOT)/GithubRepos/Info.plist";
370 | LD_RUNPATH_SEARCH_PATHS = (
371 | "$(inherited)",
372 | "@executable_path/Frameworks",
373 | );
374 | PRODUCT_BUNDLE_IDENTIFIER = com.kanghoon.GithubUsers;
375 | PRODUCT_NAME = "$(TARGET_NAME)";
376 | SWIFT_VERSION = 5.0;
377 | TARGETED_DEVICE_FAMILY = "1,2";
378 | };
379 | name = Debug;
380 | };
381 | BA49E5E2243310F700173DEB /* Release */ = {
382 | isa = XCBuildConfiguration;
383 | baseConfigurationReference = 0D21F617731032C2765DE48E /* Pods-GithubRepos.release.xcconfig */;
384 | buildSettings = {
385 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
386 | CODE_SIGN_STYLE = Automatic;
387 | INFOPLIST_FILE = "$(SRCROOT)/GithubRepos/Info.plist";
388 | LD_RUNPATH_SEARCH_PATHS = (
389 | "$(inherited)",
390 | "@executable_path/Frameworks",
391 | );
392 | PRODUCT_BUNDLE_IDENTIFIER = com.kanghoon.GithubUsers;
393 | PRODUCT_NAME = "$(TARGET_NAME)";
394 | SWIFT_VERSION = 5.0;
395 | TARGETED_DEVICE_FAMILY = "1,2";
396 | };
397 | name = Release;
398 | };
399 | /* End XCBuildConfiguration section */
400 |
401 | /* Begin XCConfigurationList section */
402 | BA49E5C7243310F600173DEB /* Build configuration list for PBXProject "GithubRepos" */ = {
403 | isa = XCConfigurationList;
404 | buildConfigurations = (
405 | BA49E5DE243310F700173DEB /* Debug */,
406 | BA49E5DF243310F700173DEB /* Release */,
407 | );
408 | defaultConfigurationIsVisible = 0;
409 | defaultConfigurationName = Release;
410 | };
411 | BA49E5E0243310F700173DEB /* Build configuration list for PBXNativeTarget "GithubRepos" */ = {
412 | isa = XCConfigurationList;
413 | buildConfigurations = (
414 | BA49E5E1243310F700173DEB /* Debug */,
415 | BA49E5E2243310F700173DEB /* Release */,
416 | );
417 | defaultConfigurationIsVisible = 0;
418 | defaultConfigurationName = Release;
419 | };
420 | /* End XCConfigurationList section */
421 | };
422 | rootObject = BA49E5C4243310F600173DEB /* Project object */;
423 | }
424 |
--------------------------------------------------------------------------------