From 71490b040d0db419a3a92b33a05cb1430ec7ad59 Mon Sep 17 00:00:00 2001 From: Dariusz Grzeszczak Date: Fri, 17 Sep 2021 18:20:56 +0200 Subject: [PATCH 01/20] - RxSwift removed --- Package.swift | 12 +- ReMVVMExt/Sources/ReMVVMExtension.swift | 29 ---- .../Reducers/SynchronizeStateReducer.swift | 134 ++++++++++++++---- ReMVVMExt/Sources/StoreActions.swift | 1 - .../Sources/TabBar/NavigationViewModel.swift | 72 ++++++---- .../Sources/TabBar/TabBarViewController.swift | 34 +++-- 6 files changed, 170 insertions(+), 112 deletions(-) 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..7f99d5f 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 { @@ -90,35 +89,7 @@ public enum ReMVVMExtension { middleware: middleware, stateMappers: stateMappers) - 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/SynchronizeStateReducer.swift b/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift index 3e9b2e5..50d91f6 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,17 +24,15 @@ 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, @@ -52,42 +48,120 @@ public final class SynchronizeStateMiddleware: Middlewar } 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) + self?.setupSynchronizeDelegate(dispatcher: dispatcher) } } } 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) + self?.setupSynchronizeDelegate(dispatcher: dispatcher) + } + } + } + + private var synchronizeDelegate: SynchronizeDelegate? + private func setupSynchronizeDelegate(dispatcher: Dispatcher) { + synchronizeDelegate = SynchronizeDelegate(dispatcher: dispatcher, + navigationController: uiState.navigationController, + modal: uiState.modalControllers.last) + } +} + +// swizzle +private class SynchronizeDelegate: NSObject, UINavigationControllerDelegate { + + let dispatcher: Dispatcher + + // navigation controller delegate setup outside ReMVVMExt + weak var externalDelegate: UINavigationControllerDelegate? + + init(dispatcher: Dispatcher, navigationController: UINavigationController?, modal: UIViewController?) { + self.dispatcher = dispatcher + super.init() + + if let navigationController = navigationController { + + if let delegate = navigationController.delegate as? SynchronizeDelegate { + externalDelegate = delegate.externalDelegate + } else { + externalDelegate = navigationController.delegate } } + + navigationController?.delegate = self + modal?.synchronizeDelegate = self } - private func subscribeLastModal(dispatcher: Dispatcher) { - guard let modal = self.uiState.modalControllers.last else { return } + override func forwardingTarget(for aSelector: Selector!) -> Any? { + externalDelegate?.responds(to: aSelector) == true ? externalDelegate : self + } + + override func responds(to aSelector: Selector!) -> Bool { + super.responds(to: aSelector) || externalDelegate?.responds(to: aSelector) ?? false + } + + func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { + + externalDelegate?.navigationController?(navigationController, didShow: viewController, animated: animated) + dispatcher.dispatch(action: SynchronizeState(.navigation)) + } - modal.rx.viewDidDisappear - .subscribe(onNext: { _ in - dispatcher.dispatch(action: SynchronizeState(.modal)) - }) - .disposed(by: disposeBag) + func vievDidDisapear(controller: UIViewController, animated: Bool) { + dispatcher.dispatch(action: SynchronizeState(.modal)) } } +private extension UIViewController { -private extension Reactive where Base: UIViewController { + private struct AssociatedKeys { + static var didDisapearClosureKey = "com.db.didDisapear" + } + + var synchronizeDelegate: SynchronizeDelegate? { + get { (objc_getAssociatedObject(self, &AssociatedKeys.didDisapearClosureKey) as? WeakObjectContainer)?.object as? SynchronizeDelegate } + set { objc_setAssociatedObject(self, &AssociatedKeys.didDisapearClosureKey, WeakObjectContainer(with: newValue), .OBJC_ASSOCIATION_RETAIN) } + } + + private class WeakObjectContainer { + + weak var object: AnyObject? + + public init(with object: AnyObject?) { + self.object = object + } + } + + 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(_:)) - var viewDidDisappear: ControlEvent { - let source = self.methodInvoked(#selector(Base.viewDidDisappear)).map { $0.first as? Bool ?? false } - return ControlEvent(events: source) - } + calledViewController.synchronizeDelegate?.vievDidDisapear(controller: calledViewController, animated: true) + + if let implementation = implementation { + let viewDidAppear: ViewDidDisappearFunction = unsafeBitCast(implementation, to: ViewDidDisappearFunction.self) + viewDidAppear(calledViewController, selector, animated) + } + + } + implementation = swizzleViewDidDisappear(UIViewController.self, to: swizzledBlock) + } + + private static func swizzleViewDidDisappear(_ class_: AnyClass, to block: @escaping ViewDidDisappearBlock) -> IMP? { + + 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 + } + } } diff --git a/ReMVVMExt/Sources/StoreActions.swift b/ReMVVMExt/Sources/StoreActions.swift index cf3d939..7f67c9a 100644 --- a/ReMVVMExt/Sources/StoreActions.swift +++ b/ReMVVMExt/Sources/StoreActions.swift @@ -8,7 +8,6 @@ import Loaders import ReMVVMCore -import RxSwift import UIKit public struct SynchronizeState: StoreAction { 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 } From e78d9b9b94ed30a9effc74410c81f575df3c2747 Mon Sep 17 00:00:00 2001 From: Greg Jurzak Date: Tue, 18 Oct 2022 19:25:41 +0200 Subject: [PATCH 02/20] support swift ui views fix crash with dismiss modal --- .../Reducers/DismissModalReducer.swift | 2 + .../Sources/Reducers/ShowOnRootReducer.swift | 1 - ReMVVMExt/Sources/StoreActions.swift | 113 ++++++++++++++---- 3 files changed, 95 insertions(+), 21 deletions(-) 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/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/StoreActions.swift b/ReMVVMExt/Sources/StoreActions.swift index cf3d939..f34384c 100644 --- a/ReMVVMExt/Sources/StoreActions.swift +++ b/ReMVVMExt/Sources/StoreActions.swift @@ -9,35 +9,49 @@ import Loaders import ReMVVMCore import RxSwift +import SwiftUI import UIKit public struct SynchronizeState: StoreAction { - + public let type: SynchronizeType public init(_ type: SynchronizeType) { self.type = type } - + public enum SynchronizeType { case navigation, modal } } public struct ShowOnRoot: StoreAction { - + 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 { @@ -45,13 +59,13 @@ public struct Show: StoreAction { public let navigationBarHidden: Bool public let item: AnyNavigationItem 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) { + self.controllerInfo = LoaderWithFactory(loader: loader, factory: factory, animated: animated) @@ -59,12 +73,28 @@ public struct Show: StoreAction { self.item = AnyNavigationItem(item) self.navigationType = Item.navigationType } + + @available(iOS 13.0, *) + public init(on item: Item, + 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 + self.item = AnyNavigationItem(item) + self.navigationType = Item.navigationType + } } public struct Push: StoreAction { - + public let controllerInfo: LoaderWithFactory public let pop: PopMode? + public init(loader: Loader, factory: ViewModelFactory? = nil, pop: PopMode? = nil, @@ -74,6 +104,18 @@ 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) where V: View { + self.pop = pop + self.controllerInfo = LoaderWithFactory(view: view, + factory: factory, + animated: animated) + } + } public enum PopMode { @@ -90,13 +132,13 @@ public struct Pop: StoreAction { } public struct ShowModal: StoreAction { - + public let controllerInfo: LoaderWithFactory public let withNavigationController: Bool public let showOverSplash: Bool public let showOverSelfType: Bool public let presentationStyle: UIModalPresentationStyle - + public init(loader: Loader, factory: ViewModelFactory? = nil, animated: Bool = true, @@ -104,7 +146,7 @@ public struct ShowModal: StoreAction { showOverSplash: Bool = true, showOverSelfType: Bool = true, presentationStyle: UIModalPresentationStyle = .fullScreen) { - + self.controllerInfo = LoaderWithFactory(loader: loader, factory: factory, animated: animated) @@ -113,13 +155,30 @@ public struct ShowModal: StoreAction { self.showOverSelfType = showOverSelfType self.presentationStyle = presentationStyle } + + @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) where V: View { + self.controllerInfo = LoaderWithFactory(view: view, + factory: factory, + animated: animated) + self.withNavigationController = withNavigationController + self.showOverSplash = showOverSplash + self.showOverSelfType = showOverSelfType + self.presentationStyle = presentationStyle + } } public struct DismissModal: StoreAction { - + public let dismissAllViews: Bool public let animated: Bool - + public init(dismissAllViews: Bool = false, animated: Bool = true) { self.dismissAllViews = dismissAllViews self.animated = animated @@ -127,14 +186,28 @@ 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) where V: View { + + let hostLoader: Loader = Loader { + Loader(view).load() + } + + self.init(loader: hostLoader, factory: factory, animated: animated) + } } From 0092eec73f18ea2a83751c4df63e46a692233cbd Mon Sep 17 00:00:00 2001 From: Greg Jurzak Date: Thu, 20 Oct 2022 16:49:55 +0200 Subject: [PATCH 03/20] add reset stack to pop option --- ReMVVMExt/Sources/Reducers/PopReducer.swift | 4 ++-- ReMVVMExt/Sources/Reducers/PushReducer.swift | 6 +++++- ReMVVMExt/Sources/StoreActions.swift | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) 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/StoreActions.swift b/ReMVVMExt/Sources/StoreActions.swift index f34384c..dbcbcf9 100644 --- a/ReMVVMExt/Sources/StoreActions.swift +++ b/ReMVVMExt/Sources/StoreActions.swift @@ -120,6 +120,7 @@ public struct Push: StoreAction { public enum PopMode { case popToRoot, pop(Int) + case resetStack } public struct Pop: StoreAction { From d81b83be7757c8f8066bd971a022fc7cb0c8f40b Mon Sep 17 00:00:00 2001 From: Greg Jurzak Date: Fri, 21 Oct 2022 23:06:29 +0200 Subject: [PATCH 04/20] Add clear background for modals --- ReMVVMExt/Sources/StoreActions.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/ReMVVMExt/Sources/StoreActions.swift b/ReMVVMExt/Sources/StoreActions.swift index dbcbcf9..972c176 100644 --- a/ReMVVMExt/Sources/StoreActions.swift +++ b/ReMVVMExt/Sources/StoreActions.swift @@ -164,10 +164,12 @@ public struct ShowModal: StoreAction { withNavigationController: Bool = true, showOverSplash: Bool = true, showOverSelfType: Bool = true, - presentationStyle: UIModalPresentationStyle = .fullScreen) where V: View { + presentationStyle: UIModalPresentationStyle = .fullScreen, + clearBackground: Bool = false) where V: View { self.controllerInfo = LoaderWithFactory(view: view, factory: factory, - animated: animated) + animated: animated, + clearBackground: clearBackground) self.withNavigationController = withNavigationController self.showOverSplash = showOverSplash self.showOverSelfType = showOverSelfType @@ -203,10 +205,16 @@ public struct LoaderWithFactory { @available(iOS 13.0, *) public init(view: V, factory: ViewModelFactory?, - animated: Bool = true) where V: View { + animated: Bool = true, + clearBackground: Bool = false) where V: View { let hostLoader: Loader = Loader { - Loader(view).load() + let loader = Loader(view).load() + if clearBackground { + loader.view.backgroundColor = .clear + } + return loader + } self.init(loader: hostLoader, factory: factory, animated: animated) From 636a0ac42e92022e64665119d143361f79bed6e5 Mon Sep 17 00:00:00 2001 From: Dariusz Grzeszczak Date: Wed, 26 Oct 2022 08:22:56 +0200 Subject: [PATCH 05/20] Synchronization disabled for now --- .../Reducers/SynchronizeStateReducer.swift | 229 ++++++++++-------- 1 file changed, 122 insertions(+), 107 deletions(-) diff --git a/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift b/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift index 50d91f6..36d6956 100644 --- a/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift +++ b/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift @@ -24,12 +24,12 @@ struct SynchronizeStateReducer: Reducer { } } -private var swizzle: Void = UIViewController.swizzleDidDisapear() +//private var swizzle: Void = UIViewController.swizzleDidDisapear() public final class SynchronizeStateMiddleware: Middleware { public let uiState: UIState public init(uiState: UIState) { - _ = swizzle +// _ = swizzle self.uiState = uiState } @@ -37,7 +37,12 @@ public final class SynchronizeStateMiddleware: Middlewar action: StoreAction, interceptor: Interceptor, dispatcher: Dispatcher) { - +// +// DispatchQueue.main.async { [self] in +// print(uiState.navigationController) +// print(UINavigationController().delegate) +// } + if let action = action as? SynchronizeState { if action.type == .navigation, @@ -58,110 +63,120 @@ public final class SynchronizeStateMiddleware: Middlewar } } - private var synchronizeDelegate: SynchronizeDelegate? +// private var synchronizeDelegate: SynchronizeDelegate? private func setupSynchronizeDelegate(dispatcher: Dispatcher) { - synchronizeDelegate = SynchronizeDelegate(dispatcher: dispatcher, - navigationController: uiState.navigationController, - modal: uiState.modalControllers.last) - } -} - -// swizzle -private class SynchronizeDelegate: NSObject, UINavigationControllerDelegate { - - let dispatcher: Dispatcher - - // navigation controller delegate setup outside ReMVVMExt - weak var externalDelegate: UINavigationControllerDelegate? - - init(dispatcher: Dispatcher, navigationController: UINavigationController?, modal: UIViewController?) { - self.dispatcher = dispatcher - super.init() - - if let navigationController = navigationController { - - if let delegate = navigationController.delegate as? SynchronizeDelegate { - externalDelegate = delegate.externalDelegate - } else { - externalDelegate = navigationController.delegate - } - } - - navigationController?.delegate = self - modal?.synchronizeDelegate = self - } - - override func forwardingTarget(for aSelector: Selector!) -> Any? { - externalDelegate?.responds(to: aSelector) == true ? externalDelegate : self - } - - override func responds(to aSelector: Selector!) -> Bool { - super.responds(to: aSelector) || externalDelegate?.responds(to: aSelector) ?? false - } - - func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { - - externalDelegate?.navigationController?(navigationController, didShow: viewController, animated: animated) - dispatcher.dispatch(action: SynchronizeState(.navigation)) - } - - func vievDidDisapear(controller: UIViewController, animated: Bool) { - dispatcher.dispatch(action: SynchronizeState(.modal)) - } -} - -private extension UIViewController { - - private struct AssociatedKeys { - static var didDisapearClosureKey = "com.db.didDisapear" - } - - var synchronizeDelegate: SynchronizeDelegate? { - get { (objc_getAssociatedObject(self, &AssociatedKeys.didDisapearClosureKey) as? WeakObjectContainer)?.object as? SynchronizeDelegate } - set { objc_setAssociatedObject(self, &AssociatedKeys.didDisapearClosureKey, WeakObjectContainer(with: newValue), .OBJC_ASSOCIATION_RETAIN) } - } - - private class WeakObjectContainer { - - weak var object: AnyObject? - - public init(with object: AnyObject?) { - self.object = object - } - } - - 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(_:)) - - calledViewController.synchronizeDelegate?.vievDidDisapear(controller: calledViewController, animated: true) - - if let implementation = implementation { - let viewDidAppear: ViewDidDisappearFunction = unsafeBitCast(implementation, to: ViewDidDisappearFunction.self) - viewDidAppear(calledViewController, selector, animated) - } - - } - implementation = swizzleViewDidDisappear(UIViewController.self, to: swizzledBlock) - } - - private static func swizzleViewDidDisappear(_ class_: AnyClass, to block: @escaping ViewDidDisappearBlock) -> IMP? { - - 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 - } +// let navController = UINavigationController() +// navController.delegate = nil +// let d: UINavigationControllerDelegate? = navController.delegate +// print("db: \(d == nil)") + +// DispatchQueue.main.async { [uiState] in +// print(uiState.navigationController) +// print(uiState.navigationController?.delegate) +// } +// +// synchronizeDelegate = SynchronizeDelegate(dispatcher: dispatcher, +// navigationController: uiState.navigationController, +// modal: uiState.modalControllers.last) } } +// +//// swizzle +//private class SynchronizeDelegate: NSObject, UINavigationControllerDelegate { +// +// let dispatcher: Dispatcher +// +// // navigation controller delegate setup outside ReMVVMExt +// weak var externalDelegate: UINavigationControllerDelegate? +// +// init(dispatcher: Dispatcher, navigationController: UINavigationController?, modal: UIViewController?) { +// self.dispatcher = dispatcher +// super.init() +// +// if let navigationController = navigationController { +// +// if let delegate = navigationController.delegate as? SynchronizeDelegate { +// externalDelegate = delegate.externalDelegate +// } else { +// externalDelegate = navigationController.delegate +// } +// } +// +// navigationController?.delegate = self +// modal?.synchronizeDelegate = self +// } +// +// override func forwardingTarget(for aSelector: Selector!) -> Any? { +// externalDelegate?.responds(to: aSelector) == true ? externalDelegate : self +// } +// +// override func responds(to aSelector: Selector!) -> Bool { +// super.responds(to: aSelector) || externalDelegate?.responds(to: aSelector) ?? false +// } +// +// func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { +// +// externalDelegate?.navigationController?(navigationController, didShow: viewController, animated: animated) +// dispatcher.dispatch(action: SynchronizeState(.navigation)) +// } +// +// func vievDidDisapear(controller: UIViewController, animated: Bool) { +// dispatcher.dispatch(action: SynchronizeState(.modal)) +// } +//} +// +//private extension UIViewController { +// +// private struct AssociatedKeys { +// static var didDisapearClosureKey = "com.db.didDisapear" +// } +// +// var synchronizeDelegate: SynchronizeDelegate? { +// get { (objc_getAssociatedObject(self, &AssociatedKeys.didDisapearClosureKey) as? WeakObjectContainer)?.object as? SynchronizeDelegate } +// set { objc_setAssociatedObject(self, &AssociatedKeys.didDisapearClosureKey, WeakObjectContainer(with: newValue), .OBJC_ASSOCIATION_RETAIN) } +// } +// +// private class WeakObjectContainer { +// +// weak var object: AnyObject? +// +// public init(with object: AnyObject?) { +// self.object = object +// } +// } +// +// 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(_:)) +// +// calledViewController.synchronizeDelegate?.vievDidDisapear(controller: calledViewController, animated: true) +// +// if let implementation = implementation { +// let viewDidAppear: ViewDidDisappearFunction = unsafeBitCast(implementation, to: ViewDidDisappearFunction.self) +// viewDidAppear(calledViewController, selector, animated) +// } +// +// } +// implementation = swizzleViewDidDisappear(UIViewController.self, to: swizzledBlock) +// } +// +// private static func swizzleViewDidDisappear(_ class_: AnyClass, to block: @escaping ViewDidDisappearBlock) -> IMP? { +// +// 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 +// } +// } +//} From 90d1f373f6a9fd7efd9aba8466361471d3c22ae2 Mon Sep 17 00:00:00 2001 From: Dariusz Grzeszczak Date: Wed, 26 Oct 2022 22:16:26 +0200 Subject: [PATCH 06/20] - ReMVVMViewController --- .../Reducers/SynchronizeStateReducer.swift | 222 ++++++++---------- ReMVVMExt/Sources/UIState.swift | 6 +- 2 files changed, 98 insertions(+), 130 deletions(-) diff --git a/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift b/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift index 36d6956..a06a9d0 100644 --- a/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift +++ b/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift @@ -24,12 +24,12 @@ struct SynchronizeStateReducer: Reducer { } } -//private var swizzle: Void = UIViewController.swizzleDidDisapear() +private var swizzle: Void = UIViewController.swizzleDidDisapear() public final class SynchronizeStateMiddleware: Middleware { public let uiState: UIState public init(uiState: UIState) { -// _ = swizzle + _ = swizzle self.uiState = uiState } @@ -37,11 +37,6 @@ public final class SynchronizeStateMiddleware: Middlewar action: StoreAction, interceptor: Interceptor, dispatcher: Dispatcher) { -// -// DispatchQueue.main.async { [self] in -// print(uiState.navigationController) -// print(UINavigationController().delegate) -// } if let action = action as? SynchronizeState { @@ -52,131 +47,104 @@ 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 - self?.setupSynchronizeDelegate(dispatcher: dispatcher) - } + + interceptor.next() } } else { - interceptor.next { [weak self] _ in - self?.setupSynchronizeDelegate(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(_:)) + + 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 var synchronizeDelegate: SynchronizeDelegate? - private func setupSynchronizeDelegate(dispatcher: Dispatcher) { -// let navController = UINavigationController() -// navController.delegate = nil -// let d: UINavigationControllerDelegate? = navController.delegate -// print("db: \(d == nil)") - -// DispatchQueue.main.async { [uiState] in -// print(uiState.navigationController) -// print(uiState.navigationController?.delegate) -// } -// -// synchronizeDelegate = SynchronizeDelegate(dispatcher: dispatcher, -// navigationController: uiState.navigationController, -// modal: uiState.modalControllers.last) + private static func swizzleViewDidDisappear(_ class_: AnyClass, to block: @escaping ViewDidDisappearBlock) -> IMP? { + + 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 { + + 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) { + + delegate?.navigationController?(navigationController, didShow: viewController, animated: animated) + dispatcher.dispatch(action: SynchronizeState(.navigation)) + } } } -// -//// swizzle -//private class SynchronizeDelegate: NSObject, UINavigationControllerDelegate { -// -// let dispatcher: Dispatcher -// -// // navigation controller delegate setup outside ReMVVMExt -// weak var externalDelegate: UINavigationControllerDelegate? -// -// init(dispatcher: Dispatcher, navigationController: UINavigationController?, modal: UIViewController?) { -// self.dispatcher = dispatcher -// super.init() -// -// if let navigationController = navigationController { -// -// if let delegate = navigationController.delegate as? SynchronizeDelegate { -// externalDelegate = delegate.externalDelegate -// } else { -// externalDelegate = navigationController.delegate -// } -// } -// -// navigationController?.delegate = self -// modal?.synchronizeDelegate = self -// } -// -// override func forwardingTarget(for aSelector: Selector!) -> Any? { -// externalDelegate?.responds(to: aSelector) == true ? externalDelegate : self -// } -// -// override func responds(to aSelector: Selector!) -> Bool { -// super.responds(to: aSelector) || externalDelegate?.responds(to: aSelector) ?? false -// } -// -// func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { -// -// externalDelegate?.navigationController?(navigationController, didShow: viewController, animated: animated) -// dispatcher.dispatch(action: SynchronizeState(.navigation)) -// } -// -// func vievDidDisapear(controller: UIViewController, animated: Bool) { -// dispatcher.dispatch(action: SynchronizeState(.modal)) -// } -//} -// -//private extension UIViewController { -// -// private struct AssociatedKeys { -// static var didDisapearClosureKey = "com.db.didDisapear" -// } -// -// var synchronizeDelegate: SynchronizeDelegate? { -// get { (objc_getAssociatedObject(self, &AssociatedKeys.didDisapearClosureKey) as? WeakObjectContainer)?.object as? SynchronizeDelegate } -// set { objc_setAssociatedObject(self, &AssociatedKeys.didDisapearClosureKey, WeakObjectContainer(with: newValue), .OBJC_ASSOCIATION_RETAIN) } -// } -// -// private class WeakObjectContainer { -// -// weak var object: AnyObject? -// -// public init(with object: AnyObject?) { -// self.object = object -// } -// } -// -// 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(_:)) -// -// calledViewController.synchronizeDelegate?.vievDidDisapear(controller: calledViewController, animated: true) -// -// if let implementation = implementation { -// let viewDidAppear: ViewDidDisappearFunction = unsafeBitCast(implementation, to: ViewDidDisappearFunction.self) -// viewDidAppear(calledViewController, selector, animated) -// } -// -// } -// implementation = swizzleViewDidDisappear(UIViewController.self, to: swizzledBlock) -// } -// -// private static func swizzleViewDidDisappear(_ class_: AnyClass, to block: @escaping ViewDidDisappearBlock) -> IMP? { -// -// 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 -// } -// } -//} 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 } From 018be897007c7a35d77c366daed135d6acaf3746 Mon Sep 17 00:00:00 2001 From: Greg Jurzak Date: Thu, 27 Oct 2022 09:34:32 +0200 Subject: [PATCH 07/20] clear background for push --- ReMVVMExt/Sources/StoreActions.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ReMVVMExt/Sources/StoreActions.swift b/ReMVVMExt/Sources/StoreActions.swift index 0bd075b..bed05a0 100644 --- a/ReMVVMExt/Sources/StoreActions.swift +++ b/ReMVVMExt/Sources/StoreActions.swift @@ -108,11 +108,13 @@ public struct Push: StoreAction { public init(view: V, factory: ViewModelFactory? = nil, pop: PopMode? = nil, - animated: Bool = true) where V: View { + animated: Bool = true, + clearBackground: Bool = false) where V: View { self.pop = pop self.controllerInfo = LoaderWithFactory(view: view, factory: factory, - animated: animated) + animated: animated, + clearBackground: clearBackground) } } From fdac40ec2f0d894198fac2362fa34137dd95b0b2 Mon Sep 17 00:00:00 2001 From: Greg Jurzak Date: Thu, 27 Oct 2022 12:20:52 +0200 Subject: [PATCH 08/20] ad corner radius to page sheet --- ReMVVMExt/Sources/Reducers/ShowModalReducer.swift | 8 ++++++++ ReMVVMExt/Sources/StoreActions.swift | 7 ++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ReMVVMExt/Sources/Reducers/ShowModalReducer.swift b/ReMVVMExt/Sources/Reducers/ShowModalReducer.swift index 8603611..4d66c63 100644 --- a/ReMVVMExt/Sources/Reducers/ShowModalReducer.swift +++ b/ReMVVMExt/Sources/Reducers/ShowModalReducer.swift @@ -70,6 +70,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/StoreActions.swift b/ReMVVMExt/Sources/StoreActions.swift index bed05a0..f4ef459 100644 --- a/ReMVVMExt/Sources/StoreActions.swift +++ b/ReMVVMExt/Sources/StoreActions.swift @@ -140,6 +140,7 @@ public struct ShowModal: StoreAction { public let showOverSplash: Bool public let showOverSelfType: Bool public let presentationStyle: UIModalPresentationStyle + public let preferredCornerRadius: CGFloat? public init(loader: Loader, factory: ViewModelFactory? = nil, @@ -147,7 +148,8 @@ public struct ShowModal: StoreAction { 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, @@ -156,6 +158,7 @@ public struct ShowModal: StoreAction { self.showOverSplash = showOverSplash self.showOverSelfType = showOverSelfType self.presentationStyle = presentationStyle + self.preferredCornerRadius = preferredCornerRadius } @available(iOS 13.0, *) @@ -166,6 +169,7 @@ public struct ShowModal: StoreAction { 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, @@ -175,6 +179,7 @@ public struct ShowModal: StoreAction { self.showOverSplash = showOverSplash self.showOverSelfType = showOverSelfType self.presentationStyle = presentationStyle + self.preferredCornerRadius = preferredCornerRadius } } From 7532e5d354fe197b31d7b88b86b228118e1d7dc4 Mon Sep 17 00:00:00 2001 From: Greg Jurzak Date: Fri, 28 Oct 2022 12:16:30 +0200 Subject: [PATCH 09/20] add reset stack to show on tab --- ReMVVMExt/Sources/Reducers/ShowTabReducer.swift | 6 ++++-- ReMVVMExt/Sources/StoreActions.swift | 9 +++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift index eef6374..689d378 100644 --- a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift +++ b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift @@ -52,7 +52,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 +79,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/StoreActions.swift b/ReMVVMExt/Sources/StoreActions.swift index f4ef459..300077b 100644 --- a/ReMVVMExt/Sources/StoreActions.swift +++ b/ReMVVMExt/Sources/StoreActions.swift @@ -57,13 +57,15 @@ public struct Show: StoreAction { 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) { + navigationBarHidden: Bool = true, + resetStack: Bool = false) { self.controllerInfo = LoaderWithFactory(loader: loader, factory: factory, @@ -71,6 +73,7 @@ public struct Show: StoreAction { self.navigationBarHidden = navigationBarHidden self.item = AnyNavigationItem(item) self.navigationType = Item.navigationType + self.resetStack = resetStack } @available(iOS 13.0, *) @@ -78,7 +81,8 @@ public struct Show: StoreAction { view: V, factory: ViewModelFactory? = nil, animated: Bool = true, - navigationBarHidden: Bool = true) where V: View { + navigationBarHidden: Bool = true, + resetStack: Bool = false) where V: View { self.controllerInfo = LoaderWithFactory(view: view, factory: factory, @@ -86,6 +90,7 @@ public struct Show: StoreAction { self.navigationBarHidden = navigationBarHidden self.item = AnyNavigationItem(item) self.navigationType = Item.navigationType + self.resetStack = resetStack } } From d1d0b3ac9fcbac01fee2379d3a622f1a8cfd7095 Mon Sep 17 00:00:00 2001 From: Greg Jurzak Date: Mon, 5 Dec 2022 13:17:24 +0100 Subject: [PATCH 10/20] fix reset stack factory on show action --- ReMVVMExt/Sources/Reducers/ShowTabReducer.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift index 689d378..c34ebf3 100644 --- a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift +++ b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift @@ -29,12 +29,21 @@ struct ShowReducer: Reducer { 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 } - return ($0.0, [factory]) + if action.resetStack { + return ($0.0, []) + } else { + return ($0.0, [factory]) + } + } } else { stacks = action.navigationType.map { guard $0 == current else { return ($0, []) } - return ($0, [factory]) + if action.resetStack { + return ($0.0, []) + } else { + return ($0.0, [factory]) + } } } let root = NavigationRoot(current: current, stacks: stacks) From 30bf2805dc946b6ab0f0f92d447c8b98888e4b27 Mon Sep 17 00:00:00 2001 From: Greg Jurzak Date: Mon, 5 Dec 2022 13:19:20 +0100 Subject: [PATCH 11/20] fix reset stack factory on show action --- ReMVVMExt/Sources/Reducers/ShowTabReducer.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift index c34ebf3..125bf15 100644 --- a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift +++ b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift @@ -40,9 +40,9 @@ struct ShowReducer: Reducer { stacks = action.navigationType.map { guard $0 == current else { return ($0, []) } if action.resetStack { - return ($0.0, []) + return ($0, []) } else { - return ($0.0, [factory]) + return ($0, [factory]) } } } From 857b89f276b875cc0119800653a4c2af1f8675fc Mon Sep 17 00:00:00 2001 From: Greg Jurzak Date: Mon, 5 Dec 2022 13:33:51 +0100 Subject: [PATCH 12/20] remove changes --- ReMVVMExt/Sources/Reducers/ShowTabReducer.swift | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift index 125bf15..16f16f8 100644 --- a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift +++ b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift @@ -29,21 +29,13 @@ struct ShowReducer: Reducer { 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 } - if action.resetStack { - return ($0.0, []) - } else { - return ($0.0, [factory]) - } + return ($0.0, [factory]) } } else { stacks = action.navigationType.map { guard $0 == current else { return ($0, []) } - if action.resetStack { - return ($0, []) - } else { - return ($0, [factory]) - } + return ($0.0, [factory]) } } let root = NavigationRoot(current: current, stacks: stacks) From e57609950462d7765a42a0683db388b0105cacf3 Mon Sep 17 00:00:00 2001 From: Greg Jurzak Date: Mon, 5 Dec 2022 13:36:37 +0100 Subject: [PATCH 13/20] remove changes --- ReMVVMExt/Sources/Reducers/ShowTabReducer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift index 16f16f8..55bd187 100644 --- a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift +++ b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift @@ -35,7 +35,7 @@ struct ShowReducer: Reducer { } else { stacks = action.navigationType.map { guard $0 == current else { return ($0, []) } - return ($0.0, [factory]) + return ($0, [factory]) } } let root = NavigationRoot(current: current, stacks: stacks) From 28bef06c26fca48a51f1f0f80b920085043ba3fb Mon Sep 17 00:00:00 2001 From: Greg Jurzak Date: Mon, 5 Dec 2022 14:07:00 +0100 Subject: [PATCH 14/20] test --- ReMVVMExt/Sources/Reducers/ShowTabReducer.swift | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift index 125bf15..1d36a2c 100644 --- a/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift +++ b/ReMVVMExt/Sources/Reducers/ShowTabReducer.swift @@ -28,22 +28,17 @@ 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 } - if action.resetStack { - return ($0.0, []) - } else { - return ($0.0, [factory]) + 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 { guard $0 == current else { return ($0, []) } - if action.resetStack { - return ($0, []) - } else { - return ($0, [factory]) - } + return ($0, [factory]) } } let root = NavigationRoot(current: current, stacks: stacks) From efbfae08a9cef6991e0f3a6aa49f166f36c6681e Mon Sep 17 00:00:00 2001 From: Dariusz Grzeszczak Date: Wed, 21 Dec 2022 22:53:45 +0100 Subject: [PATCH 15/20] - make ReMVVMNavigationController key-value compliant --- ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift b/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift index a06a9d0..a93815e 100644 --- a/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift +++ b/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift @@ -101,7 +101,7 @@ private extension UIViewController { open class ReMVVMNavigationController: UINavigationController { - private var _delegate = Delegate() + @objc private var _delegate = Delegate() public override init(nibName nibNameOrNil: String? = nil, bundle nibBundleOrNil: Bundle? = nil) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) From 42f738a315fc371c11d8c70c4d4e2c92d8c39aa4 Mon Sep 17 00:00:00 2001 From: Greg Jurzak Date: Thu, 22 Dec 2022 10:46:16 +0100 Subject: [PATCH 16/20] block if previously modal is not finish dismiss animation --- ReMVVMExt/Sources/Reducers/ShowModalReducer.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ReMVVMExt/Sources/Reducers/ShowModalReducer.swift b/ReMVVMExt/Sources/Reducers/ShowModalReducer.swift index 4d66c63..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 { From 05138f18670ff71ab08f77cbfb2a734c73c79c29 Mon Sep 17 00:00:00 2001 From: Dariusz Grzeszczak Date: Wed, 15 Mar 2023 00:04:19 +0100 Subject: [PATCH 17/20] - logger support added --- ReMVVMExt/Sources/ReMVVMExtension.swift | 28 ++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/ReMVVMExt/Sources/ReMVVMExtension.swift b/ReMVVMExt/Sources/ReMVVMExtension.swift index 7f99d5f..c78c61e 100644 --- a/ReMVVMExt/Sources/ReMVVMExtension.swift +++ b/ReMVVMExt/Sources/ReMVVMExtension.swift @@ -48,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) } @@ -62,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) @@ -87,7 +90,8 @@ public enum ReMVVMExtension { let store = Store(with: state, reducer: reducer, middleware: middleware, - stateMappers: stateMappers) + stateMappers: stateMappers, + logger: .noLogger) ReMVVM.initialize(with: store) return store.any From f78c7f33bfe50777bf1ae2b8da440787ba330bc0 Mon Sep 17 00:00:00 2001 From: Dariusz Grzeszczak Date: Wed, 15 Mar 2023 00:37:23 +0100 Subject: [PATCH 18/20] - logger fix --- ReMVVMExt/Sources/ReMVVMExtension.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReMVVMExt/Sources/ReMVVMExtension.swift b/ReMVVMExt/Sources/ReMVVMExtension.swift index c78c61e..0b41d21 100644 --- a/ReMVVMExt/Sources/ReMVVMExtension.swift +++ b/ReMVVMExt/Sources/ReMVVMExtension.swift @@ -91,7 +91,7 @@ public enum ReMVVMExtension { reducer: reducer, middleware: middleware, stateMappers: stateMappers, - logger: .noLogger) + logger: logger) ReMVVM.initialize(with: store) return store.any From 1c0295ebe568028ce828ee69add14636f57b6152 Mon Sep 17 00:00:00 2001 From: Dariusz Grzeszczak Date: Wed, 15 Mar 2023 01:06:03 +0100 Subject: [PATCH 19/20] - NavigationAction added --- .../Reducers/SynchronizeStateReducer.swift | 4 ++-- ReMVVMExt/Sources/StoreActions.swift | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift b/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift index a93815e..d9ec3f2 100644 --- a/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift +++ b/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift @@ -34,8 +34,8 @@ public final class SynchronizeStateMiddleware: Middlewar } public func onNext(for state: State, - action: StoreAction, - interceptor: Interceptor, + action: NavigationAction, + interceptor: Interceptor, dispatcher: Dispatcher) { if let action = action as? SynchronizeState { diff --git a/ReMVVMExt/Sources/StoreActions.swift b/ReMVVMExt/Sources/StoreActions.swift index 300077b..53ee7e9 100644 --- a/ReMVVMExt/Sources/StoreActions.swift +++ b/ReMVVMExt/Sources/StoreActions.swift @@ -11,7 +11,9 @@ import ReMVVMCore import SwiftUI import UIKit -public struct SynchronizeState: StoreAction { +public protocol NavigationAction: StoreAction { } + +public struct SynchronizeState: NavigationAction { public let type: SynchronizeType public init(_ type: SynchronizeType) { @@ -23,7 +25,7 @@ public struct SynchronizeState: StoreAction { } } -public struct ShowOnRoot: StoreAction { +public struct ShowOnRoot: NavigationAction { public let controllerInfo: LoaderWithFactory public let navigationBarHidden: Bool @@ -53,7 +55,7 @@ public struct ShowOnRoot: StoreAction { } } -public struct Show: StoreAction { +public struct Show: NavigationAction { public let controllerInfo: LoaderWithFactory public let navigationBarHidden: Bool public let item: AnyNavigationItem @@ -94,7 +96,7 @@ public struct Show: StoreAction { } } -public struct Push: StoreAction { +public struct Push: NavigationAction { public let controllerInfo: LoaderWithFactory public let pop: PopMode? @@ -129,7 +131,7 @@ public enum PopMode { 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) { @@ -138,7 +140,7 @@ public struct Pop: StoreAction { } } -public struct ShowModal: StoreAction { +public struct ShowModal: NavigationAction { public let controllerInfo: LoaderWithFactory public let withNavigationController: Bool @@ -188,7 +190,7 @@ public struct ShowModal: StoreAction { } } -public struct DismissModal: StoreAction { +public struct DismissModal: NavigationAction { public let dismissAllViews: Bool public let animated: Bool From 1cb2abca39f2dac2ef45be2d1884e958e47eefd9 Mon Sep 17 00:00:00 2001 From: Pawel Zgoda-Ferchmin Date: Mon, 3 Apr 2023 11:04:50 +0200 Subject: [PATCH 20/20] State synchronisation after interactive modal dismiss fixed --- ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift b/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift index d9ec3f2..86d38cd 100644 --- a/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift +++ b/ReMVVMExt/Sources/Reducers/SynchronizeStateReducer.swift @@ -72,7 +72,9 @@ private extension UIViewController { let swizzledBlock: ViewDidDisappearBlock = { calledViewController, animated in let selector = #selector(UIViewController.viewDidDisappear(_:)) - ReMVVM.Dispatcher().dispatch(action: SynchronizeState(.modal)) + if calledViewController.isBeingDismissed { + ReMVVM.Dispatcher().dispatch(action: SynchronizeState(.modal)) + } if let implementation = implementation { let viewDidAppear: ViewDidDisappearFunction = unsafeBitCast(implementation, to: ViewDidDisappearFunction.self)