Skip to content

Commit

Permalink
Merge pull request #25 from erikdrobne/feature/transition-provider
Browse files Browse the repository at this point in the history
Feature/transition provider
  • Loading branch information
erikdrobne authored Nov 6, 2023
2 parents 37e1047 + 08e9935 commit bd55293
Show file tree
Hide file tree
Showing 14 changed files with 98 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import SwiftUICoordinator
@MainActor
final class DependencyContainer {

let navigationController = NavigationController()
lazy var transitionProvider = TransitionProvider(
transitions: [FadeTransition()]
)
lazy var navigationController = NavigationController(transitionProvider: transitionProvider)
let deepLinkHandler = DeepLinkHandler.shared

private(set) var appCoordinator: AppCoordinator?
Expand All @@ -29,8 +32,7 @@ extension DependencyContainer: CoordinatorFactory {
func makeAppCoordinator(window: UIWindow) -> AppCoordinator {
return AppCoordinator(
window: window,
navigationController: self.navigationController,
transitions: [FadeTransition()]
navigationController: self.navigationController
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import UIKit
import SwiftUICoordinator

class FadeTransition: NSObject, Transition {
class FadeTransition: NSObject, Transitionable {
func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute, operation: NavigationOperation) -> Bool {
return (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .star)
}
Expand Down
5 changes: 1 addition & 4 deletions Sources/SwiftUICoordinator/Coordinator/RootCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,14 @@ open class RootCoordinator: Coordinator {
/// RootCoordinator doesn't have a parent
public let parent: Coordinator? = nil
public var childCoordinators = [WeakCoordinator]()
private let transitions: [Transition]

public private(set) var window: UIWindow
public private(set) var navigationController: NavigationController

public init(window: UIWindow, navigationController: NavigationController, transitions: [Transition] = []) {
public init(window: UIWindow, navigationController: NavigationController) {
self.window = window
self.navigationController = navigationController
self.transitions = transitions

navigationController.register(transitions)
window.rootViewController = self.navigationController
window.makeKeyAndVisible()
}
Expand Down
56 changes: 13 additions & 43 deletions Sources/SwiftUICoordinator/Routing/NavigationController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ import SwiftUI
@MainActor
public class NavigationController: UINavigationController {

// MARK: - Internal Properties

/// The collection of registered transition objects.
private(set) var transitions = [WeakTransition]()
public private(set) var transitionProvider: TransitionProvidable?

// MARK: - Initialization

Expand All @@ -24,10 +21,11 @@ public class NavigationController: UINavigationController {
///
/// - Note: By default `isNavigationBarHidden` is set to `true`.
///
public convenience init(isNavigationBarHidden: Bool = true) {
public convenience init(isNavigationBarHidden: Bool = true, transitionProvider: TransitionProvidable? = nil) {
self.init(nibName: nil, bundle: nil)

self.isNavigationBarHidden = isNavigationBarHidden
self.transitionProvider = transitionProvider
}

private override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
Expand All @@ -39,34 +37,6 @@ public class NavigationController: UINavigationController {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - Public methods

/// Registers a single `Transition` for use in navigation animations.
///
/// - Parameter transition: The `Transition` to be registered.
public func register(_ transition: Transition) {
transitions.append(WeakTransition(transition))
}

/// Registers multiple `Transition` objects for use in navigation animations.
///
/// - Parameter transitions: An array of `Transition` objects to be registered.
public func register(_ transitions: [Transition]) {
self.transitions += transitions.map { WeakTransition($0) }
}

/// Unregisters all `Transition` instances of the specified type.
///
/// - Parameter type: The type of `Transition` to be unregistered.
public func unregister<T: Transition>(_ type: T.Type) {
transitions.removeAll { $0.transition is T }
}

/// Removes all registered `Transition` objects from the navigation controller.
public func unregisterAllTransitions() {
transitions.removeAll()
}
}

// MARK: - UINavigationControllerDelegate
Expand All @@ -78,17 +48,17 @@ extension NavigationController: UINavigationControllerDelegate {
from fromVC: UIViewController,
to toVC: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
let transitions = self.transitions.compactMap({ $0.transition })
guard
let provider = self.transitionProvider,
let from = (fromVC as? RouteProvider)?.route,
let to = (toVC as? RouteProvider)?.route
else {
return nil
}

for transition in transitions {
guard
let from = (fromVC as? RouteProvider)?.route,
let to = (toVC as? RouteProvider)?.route,
transition.isEligible(from: from,to: to, operation: operation)
else {
continue
}

if let transition = provider.transitions
.compactMap({ $0.transition })
.first(where: { $0.isEligible(from: from, to: to, operation: operation) }) {
return transition
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftUICoordinator/Transition/Transition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public typealias NavigationOperation = UINavigationController.Operation
///
/// - Returns: `true` if the transition is eligible; otherwise, `false`.
@MainActor
public protocol Transition: UIViewControllerAnimatedTransitioning {
public protocol Transitionable: UIViewControllerAnimatedTransitioning {
func isEligible(
from fromRoute: NavigationRoute,
to toRoute: NavigationRoute,
Expand Down
14 changes: 14 additions & 0 deletions Sources/SwiftUICoordinator/Transition/TransitionProvidable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// TransitionProvidable.swift
//
//
// Created by Erik Drobne on 12. 10. 23.
//

import Foundation

@MainActor
public protocol TransitionProvidable {
/// An array of transitions wrapped in weak references.
var transitions: [WeakTransition] { get }
}
20 changes: 20 additions & 0 deletions Sources/SwiftUICoordinator/Transition/TransitionProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// TransitionProvider.swift
//
//
// Created by Erik Drobne on 12. 10. 23.
//

import Foundation

@MainActor
public struct TransitionProvider: TransitionProvidable {
public private(set) var transitions: [WeakTransition]
/// A private array of transitions ensuring to retain them in memory as long as needed.
private var _transitions: [Transitionable]

public init(transitions: [Transitionable]) {
self._transitions = transitions
self.transitions = transitions.map { WeakTransition($0) }
}
}
4 changes: 2 additions & 2 deletions Sources/SwiftUICoordinator/Transition/WeakTransition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import Foundation
/// `WeakTransition` is used internally to store weak references to transitions.
public final class WeakTransition {
/// The weak reference to the transition.
public private(set) weak var transition: Transition?
public private(set) weak var transition: Transitionable?

/// Initializes a `WeakTransition` with the provided transition.
///
/// - Parameter transition: The transition to wrap with a weak reference.
init(_ transition: Transition) {
init(_ transition: Transitionable) {
self.transition = transition
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ class MockCoordinator: Routing {
self.startRoute = startRoute
}

func handle(_ action: CoordinatorAction) {

}
func handle(_ action: CoordinatorAction) {}
}

extension MockCoordinator: RouterViewFactory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import UIKit
import SwiftUICoordinator

class MockTransition: NSObject, Transition {
class MockTransition: NSObject, Transitionable {
func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute, operation: NavigationOperation) -> Bool {
return (fromRoute as? MockRoute == .circle && toRoute as? MockRoute == .rectangle)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import XCTest
@MainActor
final class NavigationControllerTests: XCTestCase {

func test_NavigationBarIsHiddenByDefault() {
func test_navigationBarIsHiddenByDefault() {
let sut = NavigationController()
XCTAssertTrue(sut.isNavigationBarHidden)
}
Expand Down
6 changes: 2 additions & 4 deletions Tests/SwiftUICoordinatorTests/RootCoordinatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import XCTest
@MainActor
final class RootCoordinatorTests: XCTestCase {

func test_rootViewController() {
func test_rootViewControllerInitialization() {
let navigationController = NavigationController()
let window = UIWindow()
let sut = MockAppCoordinator(window: window, navigationController: navigationController)
Expand All @@ -20,8 +20,6 @@ final class RootCoordinatorTests: XCTestCase {
XCTAssertTrue(sut.window.isKeyWindow)
XCTAssertFalse(sut.window.isHidden)
XCTAssertEqual(sut.window.rootViewController, navigationController)
XCTAssertTrue(sut.childCoordinators.isEmpty)
}



}
30 changes: 30 additions & 0 deletions Tests/SwiftUICoordinatorTests/TransitionProviderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// TransitionProviderTests.swift
//
//
// Created by Erik Drobne on 23. 10. 23.
//

import XCTest
@testable import SwiftUICoordinator

@MainActor
class TransitionProviderTests: XCTestCase {

func test_transitionProviderInitialization() {
let transitions = [MockTransition(), MockTransition()]
let sut = TransitionProvider(transitions: transitions)

XCTAssertEqual(sut.transitions.count, transitions.count)
XCTAssertNotNil(sut.transitions.first?.transition)
}

func test_weakTransitionDoesNotRetain() {
var transition: Transitionable? = MockTransition()
let weakTransition: WeakTransition? = WeakTransition(transition!)

XCTAssertNotNil(weakTransition?.transition)
transition = nil
XCTAssertNil(weakTransition?.transition)
}
}
33 changes: 6 additions & 27 deletions Tests/SwiftUICoordinatorTests/TransitionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,16 @@ import Foundation
@MainActor
final class TransitionTests: XCTestCase {

func test_registerTransition() {
let coordinator = MockCoordinator(parent: nil, startRoute: .circle, navigationController: NavigationController())
let transitions = [MockTransition()]
func test_registerTransitions() {
let transition = MockTransition()
let provider = TransitionProvider(transitions: [transition])
let navigationController = NavigationController(transitionProvider: provider)

coordinator.navigationController.register(transitions)

guard let sut = coordinator.navigationController.transitions[0].transition as? MockTransition else {
guard let sut = navigationController.transitionProvider?.transitions[0].transition as? MockTransition else {
XCTFail("Cannot cast to MockTransition.")
return
}

XCTAssertEqual(sut, transitions[0])
}

func test_unregisterTransition() {
let sut = MockCoordinator(parent: nil, startRoute: .circle, navigationController: NavigationController())
let transitions = [MockTransition(), MockTransition()]
sut.navigationController.register(transitions)
XCTAssertEqual(sut.navigationController.transitions.count, 2)

sut.navigationController.unregister(MockTransition.self)
XCTAssertEqual(sut.navigationController.transitions.count, 0)
}

func test_unregisterAllTransitions() {
let sut = MockCoordinator(parent: nil, startRoute: .circle, navigationController: NavigationController())
let transitions = [MockTransition(), MockTransition(), MockTransition()]
sut.navigationController.register(transitions)
XCTAssertEqual(sut.navigationController.transitions.count, 3)

sut.navigationController.unregisterAllTransitions()
XCTAssertEqual(sut.navigationController.transitions.count, 0)
XCTAssertEqual(sut, transition)
}
}

0 comments on commit bd55293

Please sign in to comment.