Skip to content

Feature/queued navigation #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: feature/swiftui-view-without-rx
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions ReMVVMExt/Sources/Navigation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import ReMVVMCore

public protocol NavigationState: StoreState {

var navigation: Navigation { get }
}

Expand All @@ -29,20 +28,16 @@ public struct NavigationRoot {
self.stacks = stacks.map { (AnyNavigationItem($0),$1)}
}


public enum Main: NavigationItem {

public var action: StoreAction { FakeAction() }
private struct FakeAction: StoreAction {}

case single


private struct FakeAction: StoreAction {}
public var action: StoreAction { FakeAction() }
}
}

public struct Navigation {

public let modals: [Modal]
public let root: NavigationRoot

Expand All @@ -52,7 +47,7 @@ public struct Navigation {
}

public var factory: ViewModelFactory {
return modals.last?.factory ?? root.currentStack.last ?? CompositeViewModelFactory()
modals.last?.factory ?? root.currentStack.last ?? CompositeViewModelFactory()
}

public enum Modal {
Expand Down Expand Up @@ -83,7 +78,6 @@ public struct Navigation {
}

public enum NavigationReducer: Reducer {

static let reducer = ShowOnRootReducer
.compose(with: ShowReducer.self)
.compose(with: SynchronizeStateReducer.self)
Expand All @@ -93,6 +87,6 @@ public enum NavigationReducer: Reducer {
.compose(with: DismissModalReducer.self)

public static func reduce(state: Navigation, with action: StoreAction) -> Navigation {
return reducer.reduce(state: state, with: action)
reducer.reduce(state: state, with: action)
}
}
38 changes: 38 additions & 0 deletions ReMVVMExt/Sources/NavigationDispatcher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// File.swift
//
//
// Created by Paweł Zgoda-Ferchmin on 30/05/2023.
//

import Foundation

final class NavigationDispatcher {
static let main = NavigationDispatcher()

let routingQueue = DispatchQueue(label: "RoutingQueue")
let semaphore = DispatchSemaphore(value: 0)

let queueingEnabled: Bool = true

private init() { }

func async(function: @escaping (@escaping ()-> Void) -> Void) {
if queueingEnabled {
routingQueue.async { [weak self] in
DispatchQueue.main.async { [weak self] in
function {
self?.semaphore.signal()
}
}
let waitUntil = DispatchTime.now() + Double(Int64(20 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
let result = self?.semaphore.wait(timeout: waitUntil)
if case .timedOut = result {
print("Navigation stuck on routing")
}
}
} else {
function { }
}
}
}
2 changes: 1 addition & 1 deletion ReMVVMExt/Sources/ReMVVMExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public enum ReMVVMExtension {
stateMappers: [StateMapper<ApplicationState>] = [],
reducer: R.Type,
middleware: [AnyMiddleware],
logger: Logger = .noLogger) -> AnyStore where R: Reducer, R.State == ApplicationState, R.Action == StoreAction {
logger: Logger = .defaultLogger) -> AnyStore where R: Reducer, R.State == ApplicationState, R.Action == StoreAction {

let appMapper = StateMapper<NavigationStateIOS<ApplicationState>>(for: \.appState)
let stateMappers = [appMapper] + stateMappers.map { $0.map(with: \.appState) }
Expand Down
24 changes: 8 additions & 16 deletions ReMVVMExt/Sources/Reducers/DismissModalReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,20 @@
import ReMVVMCore

public struct DismissModalReducer: Reducer {

public typealias Action = DismissModal

public static func reduce(state: Navigation, with action: DismissModal) -> Navigation {
guard !state.modals.isEmpty else { return state }

var modals = state.modals

guard !modals.isEmpty else { return Navigation(root: state.root, modals: modals) }
if action.dismissAllViews {
modals.removeAll()
} else {
modals.removeLast()
}
return Navigation(root: state.root, modals: modals)
}

}

public struct DismissModalMiddleware<State: NavigationState>: Middleware {

public let uiState: UIState

public init(uiState: UIState) {
Expand All @@ -42,18 +36,16 @@ public struct DismissModalMiddleware<State: NavigationState>: Middleware {

let uiState = self.uiState

guard !uiState.modalControllers.isEmpty else { return }

interceptor.next { _ in
// side effect

//dismiss not needed modals
if action.dismissAllViews {
uiState.dismissAll(animated: action.animated)
} else {
uiState.dismiss(animated: action.animated)
NavigationDispatcher.main.async { completion in
//dismiss not needed modals
if action.dismissAllViews {
uiState.dismissAll(animated: action.animated, completion: completion)
} else {
uiState.dismiss(animated: action.animated, completion: completion)
}
}
}

}
}
27 changes: 13 additions & 14 deletions ReMVVMExt/Sources/Reducers/PopReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@
import ReMVVMCore

public struct PopReducer: Reducer {

public typealias Action = Pop

public static func reduce(state: Navigation, with action: Pop) -> Navigation {
return updateStateTree(state, for: action.mode)
updateStateTree(state, for: action.mode)
}

private static func updateStateTree(_ stateTree: Navigation, for mode: PopMode) -> Navigation {
Expand Down Expand Up @@ -51,7 +48,6 @@ public struct PopReducer: Reducer {
}

public struct PopMiddleware<State: NavigationState>: Middleware {

public let uiState: UIState

public init(uiState: UIState) {
Expand All @@ -62,28 +58,31 @@ public struct PopMiddleware<State: NavigationState>: Middleware {
action: Pop,
interceptor: Interceptor<Pop, State>,
dispatcher: Dispatcher) {

guard state.navigation.topStack.count > 1 else { return }

interceptor.next { _ in
// side effect

switch action.mode {
case .popToRoot, .resetStack:
self.uiState.navigationController?.popToRootViewController(animated: action.animated)
NavigationDispatcher.main.async { completion in
self.uiState.navigationController?.popToRootViewController(animated: action.animated,
completion: completion)
}
case .pop(let count):
if count > 1 {
let viewControllers = self.uiState.navigationController?.viewControllers ?? []
let dropCount = min(count, viewControllers.count - 1) - 1
let newViewControllers = Array(viewControllers.dropLast(dropCount))
self.uiState.navigationController?.setViewControllers(newViewControllers, animated: false)
NavigationDispatcher.main.async { completion in
self.uiState.navigationController?.setViewControllers(newViewControllers,
animated: false,
completion: completion)
}
}
NavigationDispatcher.main.async { completion in
self.uiState.navigationController?.popViewController(animated: action.animated, completion: completion)
}

self.uiState.navigationController?.popViewController(animated: action.animated)
}

}

}

}
28 changes: 14 additions & 14 deletions ReMVVMExt/Sources/Reducers/PushReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@
import ReMVVMCore

public struct PushReducer: Reducer {

public typealias Action = Push

public static func reduce(state: Navigation, with action: Push) -> Navigation {

let root: NavigationRoot
// dismiss all modals without navigation
var modals: [Navigation.Modal] = state.modals.reversed().drop { !$0.hasNavigation }.reversed()
Expand Down Expand Up @@ -54,7 +50,6 @@ public struct PushReducer: Reducer {
}

public struct PushMiddleware<State: NavigationState>: Middleware {

public let uiState: UIState

public init(uiState: UIState) {
Expand All @@ -65,15 +60,16 @@ public struct PushMiddleware<State: NavigationState>: Middleware {
action: Push,
interceptor: Interceptor<Push, State>,
dispatcher: Dispatcher) {

let uiState = self.uiState

interceptor.next { state in
// side effect

//dismiss not needed modals
uiState.dismiss(animated: action.controllerInfo.animated,
number: uiState.modalControllers.count - state.navigation.modals.count)
NavigationDispatcher.main.async { completion in
//dismiss not needed modals
uiState.dismiss(animated: action.controllerInfo.animated,
number: uiState.modalControllers.count - state.navigation.modals.count,
completion: completion)
}

guard let navigationController = uiState.navigationController else {
assertionFailure("PushMiddleware: No navigation Controller")
Expand All @@ -95,12 +91,16 @@ public struct PushMiddleware<State: NavigationState>: Middleware {
viewControllers = viewControllers.dropLast(dropCount)
}

navigationController.setViewControllers(viewControllers + [controller],
animated: action.controllerInfo.animated)
NavigationDispatcher.main.async { completion in
navigationController.setViewControllers(viewControllers + [controller],
animated: action.controllerInfo.animated,
completion: completion)
}
} else {
navigationController.pushViewController(controller, animated: action.controllerInfo.animated)
NavigationDispatcher.main.async { completion in
navigationController.pushViewController(controller, animated: action.controllerInfo.animated, completion: completion)
}
}

}
}
}
25 changes: 13 additions & 12 deletions ReMVVMExt/Sources/Reducers/ShowModalReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ import ReMVVMCore
import UIKit

public struct ShowModalReducer: Reducer {

public typealias Action = ShowModal

public static func reduce(state: Navigation, with action: ShowModal) -> Navigation {

let factory = action.controllerInfo.factory ?? state.factory
let modal: Navigation.Modal = action.withNavigationController ? .navigation([factory]) : .single(factory)
// dismiss all modals without navigation
Expand All @@ -25,8 +21,8 @@ public struct ShowModalReducer: Reducer {
}

public struct ShowModalMiddleware<State: NavigationState>: Middleware {

public let uiState: UIState

public init(uiState: UIState) {
self.uiState = uiState
}
Expand All @@ -35,7 +31,6 @@ public struct ShowModalMiddleware<State: NavigationState>: Middleware {
action: ShowModal,
interceptor: Interceptor<ShowModal, State>,
dispatcher: Dispatcher) {

let uiState = self.uiState

var controller: UIViewController?
Expand All @@ -57,9 +52,13 @@ public struct ShowModalMiddleware<State: NavigationState>: Middleware {
interceptor.next { state in
// side effect

//dismiss not needed modals
uiState.dismiss(animated: action.controllerInfo.animated,
number: uiState.modalControllers.count - state.navigation.modals.count + 1)
NavigationDispatcher.main.async { completion in
//dismiss not needed modals
uiState.dismiss(animated: action.controllerInfo.animated,
number: uiState.modalControllers.count - state.navigation.modals.count + 1,
completion: completion)

}

let newModal: UIViewController
if action.withNavigationController {
Expand All @@ -78,13 +77,15 @@ public struct ShowModalMiddleware<State: NavigationState>: Middleware {
newModal.modalPresentationStyle = action.presentationStyle

if #available(iOS 15.0, *) {

if newModal.modalPresentationStyle == .pageSheet || newModal.modalPresentationStyle == .formSheet, let cornerRadius = action.preferredCornerRadius {
if newModal.modalPresentationStyle == .pageSheet || newModal.modalPresentationStyle == .formSheet,
let cornerRadius = action.preferredCornerRadius {
newModal.sheetPresentationController?.preferredCornerRadius = cornerRadius
}
}

uiState.present(newModal, animated: action.controllerInfo.animated)
NavigationDispatcher.main.async { completion in
uiState.present(newModal, animated: action.controllerInfo.animated, completion: completion)
}
}
}
}
19 changes: 10 additions & 9 deletions ReMVVMExt/Sources/Reducers/ShowOnRootReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
import ReMVVMCore

struct ShowOnRootReducer: Reducer {

public static func reduce(state: Navigation, with action: ShowOnRoot) -> Navigation {

let current = NavigationRoot.Main.single
let factory = action.controllerInfo.factory ?? state.factory
let stacks = [(current, [factory])]
Expand All @@ -21,7 +19,6 @@ struct ShowOnRootReducer: Reducer {
}

public struct ShowOnRootMiddleware<State: NavigationState>: Middleware {

public let uiState: UIState

public init(uiState: UIState) {
Expand All @@ -32,17 +29,21 @@ public struct ShowOnRootMiddleware<State: NavigationState>: Middleware {
action: ShowOnRoot,
interceptor: Interceptor<ShowOnRoot, State>,
dispatcher: Dispatcher) {

let uiState = self.uiState

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)

// dismiss modals
uiState.rootViewController.dismiss(animated: true, completion: nil)
NavigationDispatcher.main.async { completion in
uiState.rootViewController.dismiss(animated: true, completion: completion)
}

NavigationDispatcher.main.async { completion in
uiState.setRoot(controller: action.controllerInfo.loader.load(),
animated: action.controllerInfo.animated,
navigationBarHidden: action.navigationBarHidden,
completion: completion)
}
}
}
}
Loading