diff --git a/Package.swift b/Package.swift index 7524dba..1f29b39 100644 --- a/Package.swift +++ b/Package.swift @@ -18,26 +18,18 @@ let package = Package( url: "https://github.com/dgrzeszczak/Loaders", .upToNextMajor(from: "1.2.0") ), - .package( - url: "https://github.com/ReactiveX/RxSwift", - .upToNextMajor(from: "6.0.0") - ), .package( url: "https://github.com/ReMVVM/ReMVVM", .upToNextMajor(from: "3.0.0") ), - //.package(path: "../ReMVVM"), - .package(url: "https://github.com/ReMVVM/ReMVVMRxSwift", - .upToNextMajor(from: "1.0.0") - ), -// .package(path: "../ReMVVMRxSwift"), + //.package(path: "../ReMVVM") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages which this package depends on. .target( name: "ReMVVMExt", - dependencies: ["RxSwift", "RxCocoa", "RxRelay", "Loaders", .product(name: "ReMVVMCore", package: "ReMVVM"), "ReMVVMRxSwift"], + dependencies: ["Loaders", .product(name: "ReMVVMCore", package: "ReMVVM")], path: "ReMVVMExt/Sources", exclude: []) ] diff --git a/ReMVVMExt/Sources/ReMVVMExtension.swift b/ReMVVMExt/Sources/ReMVVMExtension.swift index f3d8667..0b41d21 100644 --- a/ReMVVMExt/Sources/ReMVVMExtension.swift +++ b/ReMVVMExt/Sources/ReMVVMExtension.swift @@ -6,7 +6,6 @@ // import ReMVVMCore -import RxSwift import UIKit public struct NavigationStateIOS: NavigationState { @@ -49,11 +48,12 @@ private enum AppNavigationReducer: Reducer where R: Reducer, R.State = public enum ReMVVMExtension { public static func initialize(with state: ApplicationState, - window: UIWindow, - uiStateConfig: UIStateConfig, - stateMappers: [StateMapper] = [], - reducer: R.Type, - middleware: [AnyMiddleware]) -> AnyStore where R: Reducer, R.State == ApplicationState, R.Action == StoreAction { + window: UIWindow, + uiStateConfig: UIStateConfig, + stateMappers: [StateMapper] = [], + reducer: R.Type, + middleware: [AnyMiddleware], + logger: Logger = .noLogger) -> AnyStore where R: Reducer, R.State == ApplicationState, R.Action == StoreAction { let appMapper = StateMapper>(for: \.appState) let stateMappers = [appMapper] + stateMappers.map { $0.map(with: \.appState) } @@ -63,15 +63,17 @@ public enum ReMVVMExtension { uiStateConfig: uiStateConfig, stateMappers: stateMappers, reducer: AppNavigationReducer.self, - middleware: middleware) + middleware: middleware, + logger: logger) } public static func initialize(with state: State, - window: UIWindow, - uiStateConfig: UIStateConfig, - stateMappers: [StateMapper] = [], - reducer: R.Type, - middleware: [AnyMiddleware]) -> AnyStore where State: NavigationState, R: Reducer, R.State == State, R.Action == StoreAction { + window: UIWindow, + uiStateConfig: UIStateConfig, + stateMappers: [StateMapper] = [], + reducer: R.Type, + middleware: [AnyMiddleware], + logger: Logger = .noLogger) -> AnyStore where State: NavigationState, R: Reducer, R.State == State, R.Action == StoreAction { let uiState = UIState(window: window, config: uiStateConfig) @@ -88,37 +90,10 @@ public enum ReMVVMExtension { let store = Store(with: state, reducer: reducer, middleware: middleware, - stateMappers: stateMappers) + stateMappers: stateMappers, + logger: logger) - store.add(observer: EndEditingFormListener(uiState: uiState)) ReMVVM.initialize(with: store) return store.any } } - -public final class EndEditingFormListener: StateObserver { - - let uiState: UIState - var disposeBag = DisposeBag() - - public init(uiState: UIState) { - self.uiState = uiState - } - - public func willReduce(state: State) { - uiState.rootViewController.view.endEditing(true) - uiState.modalControllers.last?.view.endEditing(true) - } - - public func didReduce(state: State, oldState: State?) { - disposeBag = DisposeBag() - - uiState.navigationController?.rx - .methodInvoked(#selector(UINavigationController.popViewController(animated:))) - .subscribe(onNext: { [unowned self] _ in - self.uiState.rootViewController.view.endEditing(true) - self.uiState.modalControllers.last?.view.endEditing(true) - }) - .disposed(by: disposeBag) - } -} diff --git a/ReMVVMExt/Sources/Reducers/DismissModalReducer.swift b/ReMVVMExt/Sources/Reducers/DismissModalReducer.swift index 03a56cb..0ae405f 100644 --- a/ReMVVMExt/Sources/Reducers/DismissModalReducer.swift +++ b/ReMVVMExt/Sources/Reducers/DismissModalReducer.swift @@ -15,6 +15,8 @@ public struct DismissModalReducer: Reducer { public static func reduce(state: Navigation, with action: DismissModal) -> Navigation { var modals = state.modals + + guard !modals.isEmpty else { return Navigation(root: state.root, modals: modals) } if action.dismissAllViews { modals.removeAll() } else { diff --git a/ReMVVMExt/Sources/Reducers/PopReducer.swift b/ReMVVMExt/Sources/Reducers/PopReducer.swift index 1e794eb..ef39eb4 100644 --- a/ReMVVMExt/Sources/Reducers/PopReducer.swift +++ b/ReMVVMExt/Sources/Reducers/PopReducer.swift @@ -18,7 +18,7 @@ public struct PopReducer: Reducer { private static func updateStateTree(_ stateTree: Navigation, for mode: PopMode) -> Navigation { switch mode { - case .popToRoot: + case .popToRoot, .resetStack: return popStateTree(stateTree, dropCount: stateTree.topStack.count - 1) case .pop(let count): return popStateTree(stateTree, dropCount: count) @@ -69,7 +69,7 @@ public struct PopMiddleware: Middleware { // side effect switch action.mode { - case .popToRoot: + case .popToRoot, .resetStack: self.uiState.navigationController?.popToRootViewController(animated: action.animated) case .pop(let count): if count > 1 { diff --git a/ReMVVMExt/Sources/Reducers/PushReducer.swift b/ReMVVMExt/Sources/Reducers/PushReducer.swift index 47b4a8b..c75095a 100644 --- a/ReMVVMExt/Sources/Reducers/PushReducer.swift +++ b/ReMVVMExt/Sources/Reducers/PushReducer.swift @@ -38,9 +38,11 @@ public struct PushReducer: Reducer { } private static func updateStack(_ stack: [ViewModelFactory], for pop: PopMode?) -> [ViewModelFactory] { - guard let popMode = pop, stack.count > 1 else { return stack } + guard let popMode = pop, stack.count > 0 else { return stack } switch popMode { + case .resetStack: + return [] case .pop(let count): let dropCount = min(count, stack.count) return Array(stack.dropLast(dropCount)) @@ -84,6 +86,8 @@ public struct PushMiddleware: Middleware { if let pop = action.pop { var viewControllers = navigationController.viewControllers switch pop { + case .resetStack: + viewControllers = [] case .popToRoot: viewControllers = viewControllers.dropLast(viewControllers.count - 1) case .pop(let count): diff --git a/ReMVVMExt/Sources/Reducers/ShowModalReducer.swift b/ReMVVMExt/Sources/Reducers/ShowModalReducer.swift index 8603611..484e874 100644 --- a/ReMVVMExt/Sources/Reducers/ShowModalReducer.swift +++ b/ReMVVMExt/Sources/Reducers/ShowModalReducer.swift @@ -39,6 +39,12 @@ public struct ShowModalMiddleware: Middleware { let uiState = self.uiState var controller: UIViewController? + + // block if previously modal is not finish dismiss animation + if uiState.modalControllers.last?.isBeingDismissed == true { + return + } + // block if already on screen // TODO use some id maybe ? if !action.showOverSelfType { @@ -70,6 +76,14 @@ public struct ShowModalMiddleware: Middleware { } newModal.modalPresentationStyle = action.presentationStyle + + if #available(iOS 15.0, *) { + + if newModal.modalPresentationStyle == .pageSheet || newModal.modalPresentationStyle == .formSheet, let cornerRadius = action.preferredCornerRadius { + newModal.sheetPresentationController?.preferredCornerRadius = cornerRadius + } + } + uiState.present(newModal, animated: action.controllerInfo.animated) } } diff --git a/ReMVVMExt/Sources/Reducers/ShowOnRootReducer.swift b/ReMVVMExt/Sources/Reducers/ShowOnRootReducer.swift index 1861139..3d868ba 100644 --- a/ReMVVMExt/Sources/Reducers/ShowOnRootReducer.swift +++ b/ReMVVMExt/Sources/Reducers/ShowOnRootReducer.swift @@ -37,7 +37,6 @@ public struct ShowOnRootMiddleware: Middleware { interceptor.next { _ in // newState - state variable is used below // side effect - uiState.setRoot(controller: action.controllerInfo.loader.load(), animated: action.controllerInfo.animated, navigationBarHidden: action.navigationBarHidden) diff --git a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift index eef6374..1d36a2c 100644 --- a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift +++ b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift @@ -28,8 +28,12 @@ struct ShowReducer: Reducer { let factory = action.controllerInfo.factory ?? state.factory if action.navigationType == state.root.navigationType { //check the type is the same stacks = state.root.stacks.map { - guard $0.0 == current, $0.1.isEmpty else { return $0 } + guard $0.0 == current, $0.1.isEmpty else { + if action.resetStack { return ($0.0, [factory]) } + return $0 + } return ($0.0, [factory]) + } } else { stacks = action.navigationType.map { @@ -52,7 +56,7 @@ public struct ShowMiddleware: Middleware { public func onNext(for state: State, action: Show, interceptor: Interceptor, dispatcher: Dispatcher) { - guard state.navigation.root.currentItem != action.item else { + guard state.navigation.root.currentItem != action.item || action.resetStack else { dispatcher.dispatch(action: Pop(mode: .popToRoot, animated: true)) return @@ -79,7 +83,9 @@ public struct ShowMiddleware: Middleware { } //set up current if empty (or reset) - if let top = containerController.currentNavigationController, top.viewControllers.isEmpty { + if let top = containerController.currentNavigationController, + top.viewControllers.isEmpty + || action.resetStack { top.setViewControllers([action.controllerInfo.loader.load()], animated: false) } diff --git a/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift b/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift index 3e9b2e5..86d38cd 100644 --- a/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift +++ b/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift @@ -8,8 +8,6 @@ import Foundation import ReMVVMCore -import RxSwift -import RxCocoa import UIKit // needed to synchronize the state when user use back button or swipe gesture @@ -26,22 +24,20 @@ struct SynchronizeStateReducer: Reducer { } } - - +private var swizzle: Void = UIViewController.swizzleDidDisapear() public final class SynchronizeStateMiddleware: Middleware { public let uiState: UIState public init(uiState: UIState) { + _ = swizzle self.uiState = uiState } - private var disposeBag = DisposeBag() - public func onNext(for state: State, - action: StoreAction, - interceptor: Interceptor, + action: NavigationAction, + interceptor: Interceptor, dispatcher: Dispatcher) { - + if let action = action as? SynchronizeState { if action.type == .navigation, @@ -51,43 +47,106 @@ public final class SynchronizeStateMiddleware: Middlewar interceptor.next() } else if action.type == .modal, uiState.modalControllers.last?.isBeingDismissed == true { uiState.modalControllers.removeLast() - interceptor.next { [weak self] _ in - let disposeBag = DisposeBag() - self?.disposeBag = disposeBag - self?.subscribeLastModal(dispatcher: dispatcher) - } + + interceptor.next() } } else { - interceptor.next { [weak self] _ in - let disposeBag = DisposeBag() - self?.disposeBag = disposeBag - self?.uiState.navigationController?.rx.didShow - .subscribe(onNext: { con in - dispatcher.dispatch(action: SynchronizeState(.navigation)) - }) - .disposed(by: disposeBag) - - self?.subscribeLastModal(dispatcher: dispatcher) + interceptor.next() + } + } +} + +//swizzle viewDidDissapear +private extension UIViewController { + + private struct AssociatedKeys { + static var didDisapearClosureKey = "com.db.didDisapear" + } + + private typealias ViewDidDisappearFunction = @convention(c) (UIViewController, Selector, Bool) -> Void + private typealias ViewDidDisappearBlock = @convention(block) (UIViewController, Bool) -> Void + + static func swizzleDidDisapear() { + var implementation: IMP? + + let swizzledBlock: ViewDidDisappearBlock = { calledViewController, animated in + let selector = #selector(UIViewController.viewDidDisappear(_:)) + + if calledViewController.isBeingDismissed { + ReMVVM.Dispatcher().dispatch(action: SynchronizeState(.modal)) } + + if let implementation = implementation { + let viewDidAppear: ViewDidDisappearFunction = unsafeBitCast(implementation, to: ViewDidDisappearFunction.self) + viewDidAppear(calledViewController, selector, animated) + } + } + implementation = swizzleViewDidDisappear(UIViewController.self, to: swizzledBlock) } - private func subscribeLastModal(dispatcher: Dispatcher) { - guard let modal = self.uiState.modalControllers.last else { return } + private static func swizzleViewDidDisappear(_ class_: AnyClass, to block: @escaping ViewDidDisappearBlock) -> IMP? { - modal.rx.viewDidDisappear - .subscribe(onNext: { _ in - dispatcher.dispatch(action: SynchronizeState(.modal)) - }) - .disposed(by: disposeBag) + let selector = #selector(UIViewController.viewDidDisappear(_:)) + let method: Method? = class_getInstanceMethod(class_, selector) + let newImplementation: IMP = imp_implementationWithBlock(unsafeBitCast(block, to: AnyObject.self)) + + if let method = method { + let types = method_getTypeEncoding(method) + return class_replaceMethod(class_, selector, newImplementation, types) + } else { + class_addMethod(class_, selector, newImplementation, "") + return nil + } } } +open class ReMVVMNavigationController: UINavigationController { + + @objc private var _delegate = Delegate() + + public override init(nibName nibNameOrNil: String? = nil, bundle nibBundleOrNil: Bundle? = nil) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + super.delegate = _delegate + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + super.delegate = _delegate + } + + public override init(rootViewController: UIViewController) { + super.init(rootViewController: rootViewController) + super.delegate = _delegate + } + + public override init(navigationBarClass: AnyClass?, toolbarClass: AnyClass?) { + super.init(navigationBarClass: navigationBarClass, toolbarClass: toolbarClass) + super.delegate = _delegate + } + + open override var delegate: UINavigationControllerDelegate? { + get { _delegate.delegate } + set { _delegate.delegate = newValue } + } + + private class Delegate: NSObject, UINavigationControllerDelegate { + @ReMVVM.Dispatcher private var dispatcher + + var delegate: UINavigationControllerDelegate? + + override func forwardingTarget(for aSelector: Selector!) -> Any? { + delegate?.responds(to: aSelector) == true ? delegate : self + } + + override func responds(to aSelector: Selector!) -> Bool { + return super.responds(to: aSelector) || delegate?.responds(to: aSelector) ?? false + } + + func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { -private extension Reactive where Base: UIViewController { - - var viewDidDisappear: ControlEvent { - let source = self.methodInvoked(#selector(Base.viewDidDisappear)).map { $0.first as? Bool ?? false } - return ControlEvent(events: source) - } + delegate?.navigationController?(navigationController, didShow: viewController, animated: animated) + dispatcher.dispatch(action: SynchronizeState(.navigation)) + } + } } diff --git a/ReMVVMExt/Sources/StoreActions.swift b/ReMVVMExt/Sources/StoreActions.swift index cf3d939..53ee7e9 100644 --- a/ReMVVMExt/Sources/StoreActions.swift +++ b/ReMVVMExt/Sources/StoreActions.swift @@ -8,63 +8,99 @@ import Loaders import ReMVVMCore -import RxSwift +import SwiftUI import UIKit -public struct SynchronizeState: StoreAction { +public protocol NavigationAction: StoreAction { } +public struct SynchronizeState: NavigationAction { + public let type: SynchronizeType public init(_ type: SynchronizeType) { self.type = type } - + public enum SynchronizeType { case navigation, modal } } -public struct ShowOnRoot: StoreAction { - +public struct ShowOnRoot: NavigationAction { + public let controllerInfo: LoaderWithFactory public let navigationBarHidden: Bool - + public init(loader: Loader, factory: ViewModelFactory? = nil, animated: Bool = true, navigationBarHidden: Bool = true) { - + self.controllerInfo = LoaderWithFactory(loader: loader, factory: factory, animated: animated) self.navigationBarHidden = navigationBarHidden } + + @available(iOS 13.0, *) + public init(view: V, + factory: ViewModelFactory? = nil, + animated: Bool = true, + navigationBarHidden: Bool = true) where V: View { + + self.controllerInfo = LoaderWithFactory(view: view, + factory: factory, + animated: animated) + self.navigationBarHidden = navigationBarHidden + + } } -public struct Show: StoreAction { +public struct Show: NavigationAction { public let controllerInfo: LoaderWithFactory public let navigationBarHidden: Bool public let item: AnyNavigationItem + public let resetStack: Bool let navigationType: NavigationType - + public init(on item: Item, - loader: Loader, - factory: ViewModelFactory? = nil, - animated: Bool = true, - navigationBarHidden: Bool = true) { - + loader: Loader, + factory: ViewModelFactory? = nil, + animated: Bool = true, + navigationBarHidden: Bool = true, + resetStack: Bool = false) { + self.controllerInfo = LoaderWithFactory(loader: loader, factory: factory, animated: animated) self.navigationBarHidden = navigationBarHidden self.item = AnyNavigationItem(item) self.navigationType = Item.navigationType + self.resetStack = resetStack + } + + @available(iOS 13.0, *) + public init(on item: Item, + view: V, + factory: ViewModelFactory? = nil, + animated: Bool = true, + navigationBarHidden: Bool = true, + resetStack: Bool = false) where V: View { + + self.controllerInfo = LoaderWithFactory(view: view, + factory: factory, + animated: animated) + self.navigationBarHidden = navigationBarHidden + self.item = AnyNavigationItem(item) + self.navigationType = Item.navigationType + self.resetStack = resetStack } } -public struct Push: StoreAction { - +public struct Push: NavigationAction { + public let controllerInfo: LoaderWithFactory public let pop: PopMode? + public init(loader: Loader, factory: ViewModelFactory? = nil, pop: PopMode? = nil, @@ -74,13 +110,28 @@ public struct Push: StoreAction { factory: factory, animated: animated) } + + @available(iOS 13.0, *) + public init(view: V, + factory: ViewModelFactory? = nil, + pop: PopMode? = nil, + animated: Bool = true, + clearBackground: Bool = false) where V: View { + self.pop = pop + self.controllerInfo = LoaderWithFactory(view: view, + factory: factory, + animated: animated, + clearBackground: clearBackground) + } + } public enum PopMode { case popToRoot, pop(Int) + case resetStack } -public struct Pop: StoreAction { +public struct Pop: NavigationAction { public let animated: Bool public let mode: PopMode public init(mode: PopMode = .pop(1), animated: Bool = true) { @@ -89,22 +140,24 @@ public struct Pop: StoreAction { } } -public struct ShowModal: StoreAction { - +public struct ShowModal: NavigationAction { + public let controllerInfo: LoaderWithFactory public let withNavigationController: Bool public let showOverSplash: Bool public let showOverSelfType: Bool public let presentationStyle: UIModalPresentationStyle - + public let preferredCornerRadius: CGFloat? + public init(loader: Loader, factory: ViewModelFactory? = nil, animated: Bool = true, withNavigationController: Bool = true, showOverSplash: Bool = true, showOverSelfType: Bool = true, - presentationStyle: UIModalPresentationStyle = .fullScreen) { - + presentationStyle: UIModalPresentationStyle = .fullScreen, + preferredCornerRadius: CGFloat? = nil) { + self.controllerInfo = LoaderWithFactory(loader: loader, factory: factory, animated: animated) @@ -112,14 +165,36 @@ public struct ShowModal: StoreAction { self.showOverSplash = showOverSplash self.showOverSelfType = showOverSelfType self.presentationStyle = presentationStyle + self.preferredCornerRadius = preferredCornerRadius + } + + @available(iOS 13.0, *) + public init(view: V, + factory: ViewModelFactory? = nil, + animated: Bool = true, + withNavigationController: Bool = true, + showOverSplash: Bool = true, + showOverSelfType: Bool = true, + presentationStyle: UIModalPresentationStyle = .fullScreen, + preferredCornerRadius: CGFloat? = nil, + clearBackground: Bool = false) where V: View { + self.controllerInfo = LoaderWithFactory(view: view, + factory: factory, + animated: animated, + clearBackground: clearBackground) + self.withNavigationController = withNavigationController + self.showOverSplash = showOverSplash + self.showOverSelfType = showOverSelfType + self.presentationStyle = presentationStyle + self.preferredCornerRadius = preferredCornerRadius } } -public struct DismissModal: StoreAction { - +public struct DismissModal: NavigationAction { + public let dismissAllViews: Bool public let animated: Bool - + public init(dismissAllViews: Bool = false, animated: Bool = true) { self.dismissAllViews = dismissAllViews self.animated = animated @@ -127,14 +202,34 @@ public struct DismissModal: StoreAction { } public struct LoaderWithFactory { - + public let loader: Loader public let factory: ViewModelFactory? public let animated: Bool - - public init(loader: Loader, factory: ViewModelFactory?, animated: Bool = true) { + + public init(loader: Loader, + factory: ViewModelFactory?, + animated: Bool = true) { self.loader = loader self.factory = factory self.animated = animated } + + @available(iOS 13.0, *) + public init(view: V, + factory: ViewModelFactory?, + animated: Bool = true, + clearBackground: Bool = false) where V: View { + + let hostLoader: Loader = Loader { + let loader = Loader(view).load() + if clearBackground { + loader.view.backgroundColor = .clear + } + return loader + + } + + self.init(loader: hostLoader, factory: factory, animated: animated) + } } diff --git a/ReMVVMExt/Sources/TabBar/NavigationViewModel.swift b/ReMVVMExt/Sources/TabBar/NavigationViewModel.swift index ae19b43..1d9cfe9 100644 --- a/ReMVVMExt/Sources/TabBar/NavigationViewModel.swift +++ b/ReMVVMExt/Sources/TabBar/NavigationViewModel.swift @@ -8,40 +8,58 @@ import Foundation import ReMVVMCore -import ReMVVMRxSwift -import RxSwift -open class NavigationViewModel: Initializable { +@propertyWrapper +public final class ObservableValue { - public let items: Observable<[Item]> - public let selected: Observable + public typealias Observer = (T) -> Void - @ReMVVM.State var state: NavigationState? + public var wrappedValue: T { + didSet { + projectedValue?(wrappedValue) + } + } + + public init(wrappedValue: T) { + self.wrappedValue = wrappedValue + } + + public var projectedValue: Observer? +} + +open class NavigationViewModel: Initializable, StateObserver { + + @ObservableValue public var items: [Item] = [] + @ObservableValue public var selected: Item? - required public init() { + required public init() { } - let state = _state.rx.state + public func didReduce(state: NavigationState, oldState: NavigationState?) { if Item.self == AnyNavigationItem.self { - let tabType = state.map { type(of: $0.navigation.root.currentItem.base) }.take(1).share() - items = state.map { $0.navigation.root.stacks.map { $0.0 }} - .withLatestFrom(tabType) { items, tabType -> [Item] in - items - .filter { type(of: $0.base) == tabType } - .compactMap { $0 as? Item } - } - .filter { $0.count != 0} - .distinctUntilChanged() - - selected = state.compactMap { $0.navigation.root.currentItem as? Item } - .distinctUntilChanged() + let tabType = type(of: state.navigation.root.currentItem.base) + let items = state.navigation.root.stacks.map { $0.0 } + .filter { type(of: $0.base) == tabType } + .compactMap { $0 as? Item } + + if items.count != 0 && items != self.items { + self.items = items + } + + let selected = state.navigation.root.currentItem as? Item + if selected != nil && selected != self.selected { + self.selected = selected + } } else { - items = state.map { $0.navigation.root.stacks.compactMap { $0.0.base as? Item }} - .filter { $0.count != 0} - .distinctUntilChanged() - - selected = state.compactMap { $0.navigation.root.currentItem.base as? Item } - .distinctUntilChanged() - } + let items = state.navigation.root.stacks.compactMap { $0.0.base as? Item } + if items.count != 0 && items != self.items { + self.items = items + } + + let selected = state.navigation.root.currentItem.base as? Item + if selected != nil && selected != self.selected { + self.selected = selected + } + } } } diff --git a/ReMVVMExt/Sources/TabBar/TabBarViewController.swift b/ReMVVMExt/Sources/TabBar/TabBarViewController.swift index 838dd6a..7d82241 100644 --- a/ReMVVMExt/Sources/TabBar/TabBarViewController.swift +++ b/ReMVVMExt/Sources/TabBar/TabBarViewController.swift @@ -8,8 +8,6 @@ import Loaders import ReMVVMCore -import RxCocoa -import RxSwift import UIKit public struct NavigationConfig { @@ -246,17 +244,18 @@ class TabBarViewController: UITabBarController, NavigationContainerController { delegate = self guard let viewModel = viewModel else { return } + setup(items: viewModel.items) + setup(current: viewModel.selected) - viewModel.items.subscribe(onNext: { [unowned self] items in - self.setup(items: items) - }).disposed(by: disposeBag) + viewModel.$items = { [weak self] items in + self?.setup(items: items) + } - viewModel.selected.subscribe(onNext: { [unowned self] item in - self.setup(current: item) - }).disposed(by: disposeBag) + viewModel.$selected = { [weak self] item in + self?.setup(current: item) + } } - private let disposeBag = DisposeBag() private func setup(items: [AnyNavigationItem]) { let tabItems: [UITabBarItem] @@ -268,11 +267,8 @@ class TabBarViewController: UITabBarController, NavigationContainerController { let controlItems = result.controls controlItems.enumerated().forEach { index, elem in - elem.rx.controlEvent(.touchUpInside).subscribe(onNext: { [unowned self] in - if let viewController = self.viewControllers?[index] { - self.sendAction(for: viewController) - } - }).disposed(by: disposeBag) + elem.addTarget(self, action: #selector(touchUpInside(control: )), for: .touchUpInside) + elem.tag = index } tabItems = zip(items, controlItems).map { @@ -309,8 +305,16 @@ class TabBarViewController: UITabBarController, NavigationContainerController { return controller } } + + @objc dynamic func touchUpInside(control: UIControl) { + let index = control.tag + guard index >= 0 && index < viewControllers?.count ?? 0, + let viewController = self.viewControllers?[index] else { return } + + self.sendAction(for: viewController) + } - private func setup(current: AnyNavigationItem) { + private func setup(current: AnyNavigationItem?) { let selected = viewControllers?.first { guard let tab = $0.tabBarItem as? TabItem else { return false } diff --git a/ReMVVMExt/Sources/UIState.swift b/ReMVVMExt/Sources/UIState.swift index 5c756f4..4d2fcd6 100644 --- a/ReMVVMExt/Sources/UIState.swift +++ b/ReMVVMExt/Sources/UIState.swift @@ -11,18 +11,18 @@ import Loaders public struct UIStateConfig { let initialController: () -> UIViewController - let navigationController: () -> UINavigationController + let navigationController: () -> ReMVVMNavigationController let navigationConfigs: [NavigationConfig] let navigationBarHidden: Bool public init(initialController: @escaping @autoclosure () -> UIViewController, - navigationController: (() -> UINavigationController)? = nil, + navigationController: (() -> ReMVVMNavigationController)? = nil, navigationConfigs: [NavigationConfig] = [], navigationBarHidden: Bool = true ) { self.initialController = initialController - self.navigationController = navigationController ?? { UINavigationController() } + self.navigationController = navigationController ?? { ReMVVMNavigationController() } self.navigationConfigs = navigationConfigs self.navigationBarHidden = navigationBarHidden }