diff --git a/XCoordinator-Example.xcodeproj/project.pbxproj b/XCoordinator-Example.xcodeproj/project.pbxproj index 10a4ee7..85543c4 100644 --- a/XCoordinator-Example.xcodeproj/project.pbxproj +++ b/XCoordinator-Example.xcodeproj/project.pbxproj @@ -7,7 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - 9B0A753A2316C1810092CA3A /* XCoordinatorRx in Frameworks */ = {isa = PBXBuildFile; productRef = 9B0A75392316C1810092CA3A /* XCoordinatorRx */; }; + 2E7649E8240D9A3800F08477 /* CombineCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 2E7649E7240D9A3800F08477 /* CombineCocoa */; }; + 2E7649EB240D9CA800F08477 /* XCoordinatorCombine in Frameworks */ = {isa = PBXBuildFile; productRef = 2E7649EA240D9CA800F08477 /* XCoordinatorCombine */; }; + 2E7649ED240D9CA800F08477 /* XCoordinator in Frameworks */ = {isa = PBXBuildFile; productRef = 2E7649EC240D9CA800F08477 /* XCoordinator */; }; 9B56575B2315FA7E00F4F4F7 /* XCoordinator_ExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56575A2315FA7E00F4F4F7 /* XCoordinator_ExampleUITests.swift */; }; 9B5657A72315FB3500F4F4F7 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657692315FB3500F4F4F7 /* AppCoordinator.swift */; }; 9B5657A82315FB3500F4F4F7 /* UserListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B56576A2315FB3500F4F4F7 /* UserListCoordinator.swift */; }; @@ -56,8 +58,6 @@ 9B5657D32315FB3500F4F4F7 /* Presentable+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657A32315FB3500F4F4F7 /* Presentable+Rx.swift */; }; 9B5657D42315FB3500F4F4F7 /* TransitionAnimation+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657A42315FB3500F4F4F7 /* TransitionAnimation+Defaults.swift */; }; 9B5657D62315FB3500F4F4F7 /* CGAffineTransform+InPlace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5657A62315FB3500F4F4F7 /* CGAffineTransform+InPlace.swift */; }; - 9B5657D92315FB9700F4F4F7 /* Action in Frameworks */ = {isa = PBXBuildFile; productRef = 9B5657D82315FB9700F4F4F7 /* Action */; }; - 9B5657DC2315FC1000F4F4F7 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 9B5657DB2315FC1000F4F4F7 /* RxCocoa */; }; 9BD3EBF42330CE46005861BF /* Transitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EBF32330CE46005861BF /* Transitions.swift */; }; 9BD3EBFD2330D84F005861BF /* XCText+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EBF62330D84E005861BF /* XCText+Extras.swift */; }; 9BD3EBFE2330D84F005861BF /* XCTestManifests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD3EBF72330D84F005861BF /* XCTestManifests.swift */; }; @@ -166,9 +166,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9B0A753A2316C1810092CA3A /* XCoordinatorRx in Frameworks */, - 9B5657DC2315FC1000F4F4F7 /* RxCocoa in Frameworks */, - 9B5657D92315FB9700F4F4F7 /* Action in Frameworks */, + 2E7649EB240D9CA800F08477 /* XCoordinatorCombine in Frameworks */, + 2E7649ED240D9CA800F08477 /* XCoordinator in Frameworks */, + 2E7649E8240D9A3800F08477 /* CombineCocoa in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -448,9 +448,9 @@ ); name = "XCoordinator-Example"; packageProductDependencies = ( - 9B5657D82315FB9700F4F4F7 /* Action */, - 9B5657DB2315FC1000F4F4F7 /* RxCocoa */, - 9B0A75392316C1810092CA3A /* XCoordinatorRx */, + 2E7649E7240D9A3800F08477 /* CombineCocoa */, + 2E7649EA240D9CA800F08477 /* XCoordinatorCombine */, + 2E7649EC240D9CA800F08477 /* XCoordinator */, ); productName = "XCoordinator-Example"; productReference = 9B5657352315FA7B00F4F4F7 /* XCoordinator-Example.app */; @@ -525,9 +525,8 @@ ); mainGroup = 9B56572C2315FA7B00F4F4F7; packageReferences = ( - 9B5657D72315FB9700F4F4F7 /* XCRemoteSwiftPackageReference "Action" */, - 9B5657DA2315FC1000F4F4F7 /* XCRemoteSwiftPackageReference "RxSwift" */, - 9B5657DD2315FC5C00F4F4F7 /* XCRemoteSwiftPackageReference "XCoordinator" */, + 2E7649E6240D9A3800F08477 /* XCRemoteSwiftPackageReference "CombineCocoa" */, + 2E7649E9240D9CA800F08477 /* XCRemoteSwiftPackageReference "XCoordinator" */, ); productRefGroup = 9B5657362315FA7B00F4F4F7 /* Products */; projectDirPath = ""; @@ -948,47 +947,39 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 9B5657D72315FB9700F4F4F7 /* XCRemoteSwiftPackageReference "Action" */ = { + 2E7649E6240D9A3800F08477 /* XCRemoteSwiftPackageReference "CombineCocoa" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/RxSwiftCommunity/Action.git"; + repositoryURL = "git@github.com:CombineCommunity/CombineCocoa.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 4.0.1; + minimumVersion = 0.1.0; }; }; - 9B5657DA2315FC1000F4F4F7 /* XCRemoteSwiftPackageReference "RxSwift" */ = { + 2E7649E9240D9CA800F08477 /* XCRemoteSwiftPackageReference "XCoordinator" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/ReactiveX/RxSwift.git"; + repositoryURL = "git@github.com:quickbirdstudios/XCoordinator.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 5.0.1; - }; - }; - 9B5657DD2315FC5C00F4F4F7 /* XCRemoteSwiftPackageReference "XCoordinator" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/quickbirdstudios/XCoordinator.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.2; + minimumVersion = 2.0.7; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 9B0A75392316C1810092CA3A /* XCoordinatorRx */ = { + 2E7649E7240D9A3800F08477 /* CombineCocoa */ = { isa = XCSwiftPackageProductDependency; - package = 9B5657DD2315FC5C00F4F4F7 /* XCRemoteSwiftPackageReference "XCoordinator" */; - productName = XCoordinatorRx; + package = 2E7649E6240D9A3800F08477 /* XCRemoteSwiftPackageReference "CombineCocoa" */; + productName = CombineCocoa; }; - 9B5657D82315FB9700F4F4F7 /* Action */ = { + 2E7649EA240D9CA800F08477 /* XCoordinatorCombine */ = { isa = XCSwiftPackageProductDependency; - package = 9B5657D72315FB9700F4F4F7 /* XCRemoteSwiftPackageReference "Action" */; - productName = Action; + package = 2E7649E9240D9CA800F08477 /* XCRemoteSwiftPackageReference "XCoordinator" */; + productName = XCoordinatorCombine; }; - 9B5657DB2315FC1000F4F4F7 /* RxCocoa */ = { + 2E7649EC240D9CA800F08477 /* XCoordinator */ = { isa = XCSwiftPackageProductDependency; - package = 9B5657DA2315FC1000F4F4F7 /* XCRemoteSwiftPackageReference "RxSwift" */; - productName = RxCocoa; + package = 2E7649E9240D9CA800F08477 /* XCRemoteSwiftPackageReference "XCoordinator" */; + productName = XCoordinator; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/XCoordinator-Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/XCoordinator-Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2f97f0c..7f33a93 100644 --- a/XCoordinator-Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/XCoordinator-Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -2,12 +2,12 @@ "object": { "pins": [ { - "package": "Action", - "repositoryURL": "https://github.com/RxSwiftCommunity/Action.git", + "package": "CombineCocoa", + "repositoryURL": "git@github.com:CombineCommunity/CombineCocoa.git", "state": { "branch": null, - "revision": "cdade63f7bbe1f5e1eff7779e5858a796dc2c001", - "version": "4.0.1" + "revision": "c31c85b6f8494247b69997324312dce8bf55b34b", + "version": "0.1.0" } }, { @@ -21,11 +21,11 @@ }, { "package": "XCoordinator", - "repositoryURL": "https://github.com/quickbirdstudios/XCoordinator.git", + "repositoryURL": "git@github.com:quickbirdstudios/XCoordinator.git", "state": { "branch": null, - "revision": "0c16cc7061f93d278279137277efb13385e960a6", - "version": "2.0.5" + "revision": "4b6fc6f7359078c322e3b354854ed4ac5c12c5e1", + "version": "2.0.7" } } ] diff --git a/XCoordinator-Example/Models/News.swift b/XCoordinator-Example/Models/News.swift index bded488..c3c94fb 100644 --- a/XCoordinator-Example/Models/News.swift +++ b/XCoordinator-Example/Models/News.swift @@ -9,7 +9,7 @@ import Foundation import class UIKit.UIImage -struct News { +struct News: Hashable { var title: String var subtitle: String var image: UIImage diff --git a/XCoordinator-Example/Models/User.swift b/XCoordinator-Example/Models/User.swift index 93cdd1c..4d76611 100644 --- a/XCoordinator-Example/Models/User.swift +++ b/XCoordinator-Example/Models/User.swift @@ -8,6 +8,6 @@ import Foundation -struct User { +struct User: Hashable { var name: String } diff --git a/XCoordinator-Example/Scenes/About/AboutViewController.swift b/XCoordinator-Example/Scenes/About/AboutViewController.swift index cfb16bf..a2ce6a9 100644 --- a/XCoordinator-Example/Scenes/About/AboutViewController.swift +++ b/XCoordinator-Example/Scenes/About/AboutViewController.swift @@ -7,9 +7,8 @@ // import UIKit -import RxSwift -import RxCocoa import WebKit +import Combine class AboutViewController: UIViewController, BindableType { @@ -27,10 +26,6 @@ class AboutViewController: UIViewController, BindableType { UIBarButtonItem(title: "Website", style: .done, target: self, action: #selector(openWebsite)) - // MARK: Stored properties - - private let disposeBag = DisposeBag() - // MARK: Overrides override func viewDidLoad() { @@ -52,7 +47,7 @@ class AboutViewController: UIViewController, BindableType { @objc private func openWebsite() { - viewModel.input.openWebsiteTrigger.onNext(()) + viewModel.input.openWebsiteTrigger.send() } // MARK: Helpers @@ -71,5 +66,4 @@ class AboutViewController: UIViewController, BindableType { .constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), ]) } - } diff --git a/XCoordinator-Example/Scenes/About/AboutViewModel.swift b/XCoordinator-Example/Scenes/About/AboutViewModel.swift index 4ff901a..93f5f55 100644 --- a/XCoordinator-Example/Scenes/About/AboutViewModel.swift +++ b/XCoordinator-Example/Scenes/About/AboutViewModel.swift @@ -7,11 +7,10 @@ // import Foundation -import RxSwift -import Action +import Combine protocol AboutViewModelInput { - var openWebsiteTrigger: AnyObserver { get } + var openWebsiteTrigger: PassthroughSubject { get } } protocol AboutViewModelOutput { diff --git a/XCoordinator-Example/Scenes/About/AboutViewModelImpl.swift b/XCoordinator-Example/Scenes/About/AboutViewModelImpl.swift index 1b74f7f..ffd7fc3 100644 --- a/XCoordinator-Example/Scenes/About/AboutViewModelImpl.swift +++ b/XCoordinator-Example/Scenes/About/AboutViewModelImpl.swift @@ -7,22 +7,14 @@ // import Foundation -import RxSwift -import Action import XCoordinator +import Combine class AboutViewModelImpl: AboutViewModel, AboutViewModelInput, AboutViewModelOutput { - // MARK: Inputs - - private(set) lazy var openWebsiteTrigger = openWebsiteAction.inputs - - // MARK: Actions - - private lazy var openWebsiteAction = CocoaAction { [unowned self] in - self.router.rx.trigger(.website) - } - + + private(set) var openWebsiteTrigger = PassthroughSubject() + // MARK: Outputs let url = URL(string: "https://github.com/quickbirdstudios/XCoordinator")! @@ -30,11 +22,14 @@ class AboutViewModelImpl: AboutViewModel, AboutViewModelInput, AboutViewModelOut // MARK: Stored properties private let router: UnownedRouter + private var cancellables = Set() // MARK: Initialization init(router: UnownedRouter) { self.router = router + openWebsiteTrigger.sink { [unowned self] _ in + self.router.trigger(.website) + }.store(in: &cancellables) } - } diff --git a/XCoordinator-Example/Scenes/Home/HomeViewController.swift b/XCoordinator-Example/Scenes/Home/HomeViewController.swift index db73dd9..bf0676d 100644 --- a/XCoordinator-Example/Scenes/Home/HomeViewController.swift +++ b/XCoordinator-Example/Scenes/Home/HomeViewController.swift @@ -6,8 +6,8 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import RxCocoa -import RxSwift +import Combine +import CombineCocoa import UIKit class HomeViewController: UIViewController, BindableType { @@ -21,7 +21,7 @@ class HomeViewController: UIViewController, BindableType { // MARK: Stored properties - private let disposeBag = DisposeBag() + private var cancellables = Set() // MARK: Overrides @@ -34,19 +34,18 @@ class HomeViewController: UIViewController, BindableType { // MARK: BindableType func bindViewModel() { - logoutButton.rx.tap - .bind(to: viewModel.input.logoutTrigger) - .disposed(by: disposeBag) - - usersButton.rx.tap - .bind(to: viewModel.input.usersTrigger) - .disposed(by: disposeBag) - - aboutButton.rx.tap - .bind(to: viewModel.input.aboutTrigger) - .disposed(by: disposeBag) + logoutButton.tapPublisher + .sink(receiveValue: viewModel.input.logoutTrigger.send) + .store(in: &cancellables) + + usersButton.tapPublisher + .sink(receiveValue: viewModel.input.usersTrigger.send) + .store(in: &cancellables) + aboutButton.tapPublisher + .sink(receiveValue: viewModel.input.aboutTrigger.send) + .store(in: &cancellables) + viewModel.registerPeek(for: usersButton) } - } diff --git a/XCoordinator-Example/Scenes/Home/HomeViewModel.swift b/XCoordinator-Example/Scenes/Home/HomeViewModel.swift index d145f22..20a96c9 100644 --- a/XCoordinator-Example/Scenes/Home/HomeViewModel.swift +++ b/XCoordinator-Example/Scenes/Home/HomeViewModel.swift @@ -6,14 +6,13 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import Action -import RxSwift +import Combine import XCoordinator protocol HomeViewModelInput { - var logoutTrigger: AnyObserver { get } - var usersTrigger: AnyObserver { get } - var aboutTrigger: AnyObserver { get } + var logoutTrigger: PassthroughSubject { get } + var usersTrigger: PassthroughSubject { get } + var aboutTrigger: PassthroughSubject { get } } protocol HomeViewModelOutput {} diff --git a/XCoordinator-Example/Scenes/Home/HomeViewModelImpl.swift b/XCoordinator-Example/Scenes/Home/HomeViewModelImpl.swift index 13759fa..a826fee 100644 --- a/XCoordinator-Example/Scenes/Home/HomeViewModelImpl.swift +++ b/XCoordinator-Example/Scenes/Home/HomeViewModelImpl.swift @@ -6,35 +6,35 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import Action -import RxSwift +import Combine import XCoordinator -import XCoordinatorRx class HomeViewModelImpl: HomeViewModel, HomeViewModelInput, HomeViewModelOutput { // MARK: Inputs - private(set) lazy var logoutTrigger = logoutAction.inputs - private(set) lazy var usersTrigger = usersAction.inputs - private(set) lazy var aboutTrigger = aboutAction.inputs + private(set) lazy var logoutTrigger = PassthroughSubject() + private(set) lazy var usersTrigger = PassthroughSubject() + private(set) lazy var aboutTrigger = PassthroughSubject() // MARK: Actions - private lazy var logoutAction = CocoaAction { [unowned self] in - self.router.rx.trigger(.logout) + private lazy var logoutAction = { [unowned self] in + self.router.trigger(.logout) } - private lazy var usersAction = CocoaAction { [unowned self] in - self.router.rx.trigger(.users) + private lazy var usersAction = { [unowned self] in + self.router.trigger(.users) } - private lazy var aboutAction = CocoaAction { [unowned self] in - self.router.rx.trigger(.about) + private lazy var aboutAction = { [unowned self] in + self.router.trigger(.about) } + // MARK: Stored properties private let router: UnownedRouter + private var cancellables = Set() // MARK: Initialization @@ -46,6 +46,14 @@ class HomeViewModelImpl: HomeViewModel, HomeViewModelInput, HomeViewModelOutput func registerPeek(for sourceView: Container) { router.trigger(.registerUsersPeek(from: sourceView)) + logoutTrigger + .sink(receiveValue: logoutAction) + .store(in: &cancellables) + usersTrigger + .sink(receiveValue: usersAction) + .store(in: &cancellables) + aboutTrigger + .sink(receiveValue: aboutAction) + .store(in: &cancellables) } - } diff --git a/XCoordinator-Example/Scenes/Login/LoginViewController.swift b/XCoordinator-Example/Scenes/Login/LoginViewController.swift index b454058..9377584 100644 --- a/XCoordinator-Example/Scenes/Login/LoginViewController.swift +++ b/XCoordinator-Example/Scenes/Login/LoginViewController.swift @@ -6,8 +6,8 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import RxCocoa -import RxSwift +import Combine +import CombineCocoa import UIKit class LoginViewController: UIViewController, BindableType { @@ -19,7 +19,7 @@ class LoginViewController: UIViewController, BindableType { // MARK: Stored properties - private let disposeBag = DisposeBag() + private var cancellables = Set() // MARK: Overrides @@ -32,9 +32,9 @@ class LoginViewController: UIViewController, BindableType { // MARK: BindableType func bindViewModel() { - loginButton.rx.tap - .bind(to: viewModel.input.loginTrigger) - .disposed(by: disposeBag) + loginButton + .tapPublisher + .sink(receiveValue: viewModel.input.loginTrigger.send) + .store(in: &cancellables) } - } diff --git a/XCoordinator-Example/Scenes/Login/LoginViewModel.swift b/XCoordinator-Example/Scenes/Login/LoginViewModel.swift index e0f2019..b3ddb6c 100644 --- a/XCoordinator-Example/Scenes/Login/LoginViewModel.swift +++ b/XCoordinator-Example/Scenes/Login/LoginViewModel.swift @@ -6,15 +6,14 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import Action -import RxSwift import XCoordinator +import Combine protocol LoginViewModelInput { - var loginTrigger: AnyObserver { get } + var loginTrigger: PassthroughSubject { get } } -protocol LoginViewModelOutput {} +protocol LoginViewModelOutput { } protocol LoginViewModel { var input: LoginViewModelInput { get } diff --git a/XCoordinator-Example/Scenes/Login/LoginViewModelImpl.swift b/XCoordinator-Example/Scenes/Login/LoginViewModelImpl.swift index 14360a1..ea9cfc3 100644 --- a/XCoordinator-Example/Scenes/Login/LoginViewModelImpl.swift +++ b/XCoordinator-Example/Scenes/Login/LoginViewModelImpl.swift @@ -6,29 +6,32 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import Action -import RxSwift +import Combine import XCoordinator class LoginViewModelImpl: LoginViewModel, LoginViewModelInput, LoginViewModelOutput { // MARK: Inputs - private(set) lazy var loginTrigger = loginAction.inputs + private(set) var loginTrigger = PassthroughSubject() // MARK: Actions - private lazy var loginAction = CocoaAction { [unowned self] in - self.router.rx.trigger(.home(nil)) + private lazy var loginAction = { [unowned self] in + self.router.trigger(.home(nil)) } // MARK: Stored properties private let router: UnownedRouter + private var cancellables = Set() // MARK: Initialization init(router: UnownedRouter) { self.router = router + loginTrigger + .sink(receiveValue: loginAction) + .store(in: &cancellables) } } diff --git a/XCoordinator-Example/Scenes/News/NewsViewController.swift b/XCoordinator-Example/Scenes/News/NewsViewController.swift index b313416..44d5688 100644 --- a/XCoordinator-Example/Scenes/News/NewsViewController.swift +++ b/XCoordinator-Example/Scenes/News/NewsViewController.swift @@ -6,11 +6,16 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import RxCocoa -import RxSwift +import Combine +import CombineCocoa import UIKit -class NewsViewController: UIViewController, BindableType { +class NewsViewController: UIViewController, UITableViewDelegate, BindableType { + + enum Section { + case news + } + var viewModel: NewsViewModel! // MARK: Views @@ -19,8 +24,9 @@ class NewsViewController: UIViewController, BindableType { // MARK: Stored properties - private let disposeBag = DisposeBag() + private var cancellables = Set() private let tableViewCellIdentifier = String(describing: DetailTableViewCell.self) + private var dataSource: UITableViewDiffableDataSource! // MARK: Overrides @@ -29,27 +35,52 @@ class NewsViewController: UIViewController, BindableType { tableView.register(DetailTableViewCell.self, forCellReuseIdentifier: tableViewCellIdentifier) tableView.rowHeight = 44 + + dataSource = UITableViewDiffableDataSource(tableView: self.tableView) { (tableView, indexPath, model) -> UITableViewCell? in + let cell = self.tableView.dequeueReusableCell(type: DetailTableViewCell.self, forIndexPath: indexPath) + + cell.textLabel?.text = model.title + cell.detailTextLabel?.text = model.subtitle + cell.imageView?.image = model.image + cell.selectionStyle = .none + + return cell + } + + tableView.dataSource = dataSource + tableView.delegate = self } // MARK: BindableType func bindViewModel() { - viewModel.output.news - .bind(to: tableView.rx.items(cellIdentifier: tableViewCellIdentifier)) { _, model, cell in - cell.textLabel?.text = model.title - cell.detailTextLabel?.text = model.subtitle - cell.imageView?.image = model.image - cell.selectionStyle = .none + + viewModel + .output + .news + .sink { [unowned self] (news) in + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.news]) + snapshot.appendItems(news, toSection: .news) + + self.dataSource.apply(snapshot, animatingDifferences: true) } - .disposed(by: disposeBag) - - tableView.rx.modelSelected(News.self) - .bind(to: viewModel.input.selectedNews) - .disposed(by: disposeBag) - - viewModel.output.title - .bind(to: navigationItem.rx.title) - .disposed(by: disposeBag) + .store(in: &cancellables) + + viewModel + .output + .title + .receive(on: RunLoop.main) + .assign(to: \.title, on: navigationItem) + .store(in: &cancellables) + } + + // MARK: UITableViewDelegate + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let news = dataSource.itemIdentifier(for: indexPath) else { + fatalError("Did select unknown item at indexpath \(indexPath)") + } + viewModel.input.selectedNews.send(news) } - } diff --git a/XCoordinator-Example/Scenes/News/NewsViewModel.swift b/XCoordinator-Example/Scenes/News/NewsViewModel.swift index 375c422..67e33a4 100644 --- a/XCoordinator-Example/Scenes/News/NewsViewModel.swift +++ b/XCoordinator-Example/Scenes/News/NewsViewModel.swift @@ -6,16 +6,15 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import Action -import RxSwift +import Combine protocol NewsViewModelInput { - var selectedNews: AnyObserver { get } + var selectedNews: PassthroughSubject { get } } protocol NewsViewModelOutput { - var news: Observable<[News]> { get } - var title: Observable { get } + var news: AnyPublisher<[News], Never> { get } + var title: AnyPublisher { get } } protocol NewsViewModel { diff --git a/XCoordinator-Example/Scenes/News/NewsViewModelImpl.swift b/XCoordinator-Example/Scenes/News/NewsViewModelImpl.swift index d329ab8..81fe750 100644 --- a/XCoordinator-Example/Scenes/News/NewsViewModelImpl.swift +++ b/XCoordinator-Example/Scenes/News/NewsViewModelImpl.swift @@ -6,40 +6,44 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import Action -import RxSwift +import Combine import XCoordinator class NewsViewModelImpl: NewsViewModel, NewsViewModelInput, NewsViewModelOutput { // MARK: Inputs - private(set) lazy var selectedNews = newsSelectedAction.inputs + private(set) lazy var selectedNews = PassthroughSubject() // MARK: Actions - lazy var newsSelectedAction = Action { [unowned self] news in - self.router.rx.trigger(.newsDetail(news)) + lazy var newsSelectedAction: (News) -> Void = { [unowned self] news in + self.router.trigger(.newsDetail(news)) } // MARK: Outputs - private(set) lazy var news = newsObservable.map { $0.articles } - private(set) lazy var title = newsObservable.map { $0.title } + private(set) lazy var news = newsPublisher.map { $0.articles }.eraseToAnyPublisher() + private(set) lazy var title = newsPublisher.map { $0.title as String? }.eraseToAnyPublisher() - let newsObservable: Observable<(title: String, articles: [News])> + let newsPublisher: AnyPublisher<(title: String, articles: [News]), Never> // MARK: Stored properties private let newsService: NewsService private let router: UnownedRouter + private var cancellables = Set() // MARK: Initialization init(newsService: NewsService, router: UnownedRouter) { self.newsService = newsService - self.newsObservable = .just(newsService.mostRecentNews()) + self.newsPublisher = Just(newsService.mostRecentNews()) + .eraseToAnyPublisher() self.router = router + + selectedNews + .sink(receiveValue: newsSelectedAction) + .store(in: &cancellables) } - } diff --git a/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewController.swift b/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewController.swift index a0ca43e..3c7d547 100644 --- a/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewController.swift +++ b/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewController.swift @@ -6,8 +6,8 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import RxCocoa -import RxSwift +import Combine +import CombineCocoa import UIKit class NewsDetailViewController: UIViewController, BindableType { @@ -21,7 +21,7 @@ class NewsDetailViewController: UIViewController, BindableType { // MARK: Stored properties - private let disposeBag = DisposeBag() + private var cancellables = Set() // MARK: Overrides @@ -36,18 +36,20 @@ class NewsDetailViewController: UIViewController, BindableType { func bindViewModel() { viewModel.output.news .map { $0.title + "\n" + $0.subtitle } - .bind(to: titleLabel.rx.text) - .disposed(by: disposeBag) - + .receive(on: RunLoop.main) + .assign(to: \.text, on: titleLabel) + .store(in: &cancellables) + viewModel.output.news .map { $0.content } - .bind(to: contentTextView.rx.text) - .disposed(by: disposeBag) + .receive(on: RunLoop.main) + .assign(to: \.text, on: contentTextView) + .store(in: &cancellables) viewModel.output.news .map { $0.image } - .bind(to: imageView.rx.image) - .disposed(by: disposeBag) + .receive(on: RunLoop.main) + .assign(to: \.image, on: imageView) + .store(in: &cancellables) } - } diff --git a/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewModel.swift b/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewModel.swift index 3c4102e..92ab76d 100644 --- a/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewModel.swift +++ b/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewModel.swift @@ -6,14 +6,12 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import Action -import RxSwift +import Combine -protocol NewsDetailViewModelInput { -} +protocol NewsDetailViewModelInput { } protocol NewsDetailViewModelOutput { - var news: Observable { get } + var news: AnyPublisher { get } } protocol NewsDetailViewModel { diff --git a/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewModelImpl.swift b/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewModelImpl.swift index b352d68..85060cd 100644 --- a/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewModelImpl.swift +++ b/XCoordinator-Example/Scenes/NewsDetail/NewsDetailViewModelImpl.swift @@ -6,20 +6,18 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import Action +import Combine import Foundation -import RxSwift class NewsDetailViewModelImpl: NewsDetailViewModel, NewsDetailViewModelInput, NewsDetailViewModelOutput { // MARK: Outputs - let news: Observable + let news: AnyPublisher // MARK: Initialization init(news: News) { - self.news = .just(news) + self.news = Just(news).eraseToAnyPublisher() } - } diff --git a/XCoordinator-Example/Scenes/User/UserViewController.swift b/XCoordinator-Example/Scenes/User/UserViewController.swift index 1b701d1..5d0fba0 100644 --- a/XCoordinator-Example/Scenes/User/UserViewController.swift +++ b/XCoordinator-Example/Scenes/User/UserViewController.swift @@ -6,8 +6,8 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import RxCocoa -import RxSwift +import Combine +import CombineCocoa import UIKit class UserViewController: UIViewController, BindableType { @@ -21,7 +21,7 @@ class UserViewController: UIViewController, BindableType { // MARK: Stored properties - private let disposeBag = DisposeBag() + private var cancellables = Set() // MARK: Initialization @@ -35,16 +35,17 @@ class UserViewController: UIViewController, BindableType { func bindViewModel() { viewModel.output.username - .bind(to: username.rx.text) - .disposed(by: disposeBag) - - showAlertButton.rx.tap - .bind(to: viewModel.input.alertTrigger) - .disposed(by: disposeBag) - - closeBarButtonItem.rx.tap - .bind(to: viewModel.input.closeTrigger) - .disposed(by: disposeBag) + .receive(on: RunLoop.main) + .assign(to: \.text, on: username) + .store(in: &cancellables) + + showAlertButton.tapPublisher + .sink(receiveValue: viewModel.input.alertTrigger.send) + .store(in: &cancellables) + + closeBarButtonItem.tapPublisher + .sink(receiveValue: viewModel.input.closeTrigger.send) + .store(in: &cancellables) } // MARK: Helpers @@ -53,5 +54,4 @@ class UserViewController: UIViewController, BindableType { closeBarButtonItem = UIBarButtonItem(title: "Close", style: .plain, target: self, action: nil) navigationItem.leftBarButtonItem = closeBarButtonItem } - } diff --git a/XCoordinator-Example/Scenes/User/UserViewModel.swift b/XCoordinator-Example/Scenes/User/UserViewModel.swift index 4500701..1306a0c 100644 --- a/XCoordinator-Example/Scenes/User/UserViewModel.swift +++ b/XCoordinator-Example/Scenes/User/UserViewModel.swift @@ -6,17 +6,15 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import Action -import RxSwift -import XCoordinator +import Combine protocol UserViewModelInput { - var alertTrigger: AnyObserver { get } - var closeTrigger: AnyObserver { get } + var alertTrigger: PassthroughSubject { get } + var closeTrigger: PassthroughSubject { get } } protocol UserViewModelOutput { - var username: Observable { get } + var username: AnyPublisher { get } } protocol UserViewModel { diff --git a/XCoordinator-Example/Scenes/User/UserViewModelImpl.swift b/XCoordinator-Example/Scenes/User/UserViewModelImpl.swift index ea1b96b..8444251 100644 --- a/XCoordinator-Example/Scenes/User/UserViewModelImpl.swift +++ b/XCoordinator-Example/Scenes/User/UserViewModelImpl.swift @@ -6,39 +6,45 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import Action -import RxSwift +import Combine import XCoordinator class UserViewModelImpl: UserViewModel, UserViewModelInput, UserViewModelOutput { // MARK: Inputs - private(set) lazy var alertTrigger = alertAction.inputs - private(set) lazy var closeTrigger = closeAction.inputs + private(set) lazy var alertTrigger = PassthroughSubject() + private(set) lazy var closeTrigger = PassthroughSubject() // MARK: Actions - private lazy var alertAction = CocoaAction { [unowned self] in - self.router.rx.trigger(.alert(title: "Hey", message: "You are awesome!")) + private lazy var alertAction = { [unowned self] in + self.router.trigger(.alert(title: "Hey", message: "You are awesome!")) } - private lazy var closeAction = CocoaAction { [unowned self] in - self.router.rx.trigger(.users) + private lazy var closeAction = { [unowned self] in + self.router.trigger(.users) } // MARK: Outputs - let username: Observable + let username: AnyPublisher // MARK: Stored properties private let router: UnownedRouter + private var cancellables = Set() // MARK: Initialization init(router: UnownedRouter, username: String) { self.router = router - self.username = .just(username) + self.username = Just(username).eraseToAnyPublisher() + alertTrigger + .sink(receiveValue: alertAction) + .store(in: &cancellables) + closeTrigger + .sink(receiveValue: closeAction) + .store(in: &cancellables) } } diff --git a/XCoordinator-Example/Scenes/UserList/UsersViewController.swift b/XCoordinator-Example/Scenes/UserList/UsersViewController.swift index 983250d..f4e135e 100644 --- a/XCoordinator-Example/Scenes/UserList/UsersViewController.swift +++ b/XCoordinator-Example/Scenes/UserList/UsersViewController.swift @@ -6,11 +6,16 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import RxCocoa -import RxSwift +import Combine +import CombineCocoa import UIKit -class UsersViewController: UIViewController, BindableType { +class UsersViewController: UIViewController, UITableViewDelegate, BindableType { + + enum Section { + case users + } + var viewModel: UsersViewModel! // MARK: Views @@ -19,8 +24,9 @@ class UsersViewController: UIViewController, BindableType { // MARK: Stored properties - private let disposeBag = DisposeBag() + private var cancellables = Set() private let cellIdentifier = String(describing: DetailTableViewCell.self) + private var dataSource: UITableViewDiffableDataSource! // MARK: Initialization @@ -29,21 +35,37 @@ class UsersViewController: UIViewController, BindableType { configureTableViewCell() configureNavigationBar() + + dataSource = UITableViewDiffableDataSource(tableView: tableView) { (tableView, indexPath, user) -> UITableViewCell? in + let cell = tableView.dequeueReusableCell(type: DetailTableViewCell.self, forIndexPath: indexPath) + cell.textLabel?.text = user.name + cell.selectionStyle = .none + return cell + } + + tableView.delegate = self } // MARK: BindableType func bindViewModel() { viewModel.output.users - .bind(to: tableView.rx.items(cellIdentifier: cellIdentifier)) { _, element, cell in - cell.textLabel?.text = element.name - cell.selectionStyle = .none + .sink { (users) in + var snapshot = NSDiffableDataSourceSnapshot() + + snapshot.appendSections([.users]) + snapshot.appendItems(users, toSection: .users) + + self.dataSource.apply(snapshot, animatingDifferences: true) + } + .store(in: &cancellables) + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let user = dataSource.itemIdentifier(for: indexPath) else { + fatalError("Could not select item at indexpath \(indexPath)") } - .disposed(by: disposeBag) - - tableView.rx.modelSelected(User.self) - .bind(to: viewModel.input.showUserTrigger) - .disposed(by: disposeBag) + viewModel.input.showUserTrigger.send(user) } // MARK: Helpers @@ -55,5 +77,4 @@ class UsersViewController: UIViewController, BindableType { private func configureNavigationBar() { title = "Users" } - } diff --git a/XCoordinator-Example/Scenes/UserList/UsersViewModel.swift b/XCoordinator-Example/Scenes/UserList/UsersViewModel.swift index 29f80bf..2775377 100644 --- a/XCoordinator-Example/Scenes/UserList/UsersViewModel.swift +++ b/XCoordinator-Example/Scenes/UserList/UsersViewModel.swift @@ -6,16 +6,14 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import Action -import RxSwift -import XCoordinator +import Combine protocol UsersViewModelInput { - var showUserTrigger: AnyObserver { get } + var showUserTrigger: PassthroughSubject { get } } protocol UsersViewModelOutput { - var users: Observable<[User]> { get } + var users: AnyPublisher<[User], Never> { get } } protocol UsersViewModel { diff --git a/XCoordinator-Example/Scenes/UserList/UsersViewModelImpl.swift b/XCoordinator-Example/Scenes/UserList/UsersViewModelImpl.swift index c787695..da1ff44 100644 --- a/XCoordinator-Example/Scenes/UserList/UsersViewModelImpl.swift +++ b/XCoordinator-Example/Scenes/UserList/UsersViewModelImpl.swift @@ -6,36 +6,38 @@ // Copyright © 2018 QuickBird Studios. All rights reserved. // -import Action -import RxSwift +import Combine import XCoordinator class UsersViewModelImpl: UsersViewModel, UsersViewModelInput, UsersViewModelOutput { // MARK: Inputs - private(set) lazy var showUserTrigger = showUserAction.inputs + private(set) lazy var showUserTrigger = PassthroughSubject() // MARK: Actions - private lazy var showUserAction = Action { [unowned self] user in - self.router.rx.trigger(.user(user.name)) + private lazy var showUserAction: (User) -> Void = { [unowned self] user in + self.router.trigger(.user(user.name)) } // MARK: Outputs - private(set) lazy var users = Observable.just(userService.allUsers()) + private(set) lazy var users = Just(userService.allUsers()).eraseToAnyPublisher() // MARK: Stored properties private let userService: UserService private let router: UnownedRouter + private var cancellables = Set() // MARK: Initialization init(userService: UserService, router: UnownedRouter) { self.userService = userService self.router = router + showUserTrigger + .sink(receiveValue: showUserAction) + .store(in: &cancellables) } - } diff --git a/XCoordinator-Example/Services/NewsService.swift b/XCoordinator-Example/Services/NewsService.swift index a5b4cc1..bf205e0 100644 --- a/XCoordinator-Example/Services/NewsService.swift +++ b/XCoordinator-Example/Services/NewsService.swift @@ -7,7 +7,6 @@ // import UIKit -import RxSwift // swiftlint:disable line_length