From 70f9cff3e8c45e744313cde445777215df4d76fc Mon Sep 17 00:00:00 2001 From: Tomasz Kurylik Date: Wed, 7 Jun 2023 19:05:00 +0200 Subject: [PATCH] Code Refactorisation + Bug Fixes feat: - Added ability to change the background colour of screens within the entire application --- .../Extensions/Foundation/Array++.swift | 2 +- .../Navigattie/Extensions/Views/View++.swift | 22 ----------- .../Managers/NavigationManager.swift | 4 +- .../Protocols/NavigatableView.swift | 21 ++-------- .../Public/Public+NavigatableView.swift | 38 +++++++++++++++++++ .../Public+NavigationConfig.swift} | 8 ++-- .../Public+TransitionAnimation.swift} | 4 +- .../Type Erasers/AnyNavigatableView.swift | 2 +- .../AnimationCompletionModifier.swift | 1 - .../Views/NavigationStackView.swift | 16 +++++--- Sources/Navigattie/Views/NavigationView.swift | 6 ++- 11 files changed, 66 insertions(+), 58 deletions(-) delete mode 100644 Sources/Navigattie/Extensions/Views/View++.swift create mode 100644 Sources/Navigattie/Public/Public+NavigatableView.swift rename Sources/Navigattie/{Extensions/Views/NavigatableView++.swift => Public/Public+NavigationConfig.swift} (55%) rename Sources/Navigattie/{Configurables/TransitionAnimation.swift => Public/Public+TransitionAnimation.swift} (82%) diff --git a/Sources/Navigattie/Extensions/Foundation/Array++.swift b/Sources/Navigattie/Extensions/Foundation/Array++.swift index 0fb4c4c..cd02e46 100644 --- a/Sources/Navigattie/Extensions/Foundation/Array++.swift +++ b/Sources/Navigattie/Extensions/Foundation/Array++.swift @@ -25,5 +25,5 @@ extension Array { }} } extension Array { - var isNextToLast: Element? { count >= 2 ? self[count - 2] : nil } + var nextToLast: Element? { count >= 2 ? self[count - 2] : nil } } diff --git a/Sources/Navigattie/Extensions/Views/View++.swift b/Sources/Navigattie/Extensions/Views/View++.swift deleted file mode 100644 index 17e9c30..0000000 --- a/Sources/Navigattie/Extensions/Views/View++.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// View++.swift of Navigattie -// -// Created by Tomasz Kurylik -// - Twitter: https://twitter.com/tkurylik -// - Mail: tomasz.kurylik@mijick.com -// -// Copyright ©2023 Mijick. Licensed under MIT License. - - -import SwiftUI - -public extension View { - @ViewBuilder func matchedGeometryEffect(id: String, properties: MatchedGeometryProperties = .frame, anchor: UnitPoint = .center, isSource: Bool = true) -> some View { - if let namespace = NavigationManager.shared.namespace { matchedGeometryEffect(id: id, in: namespace, properties: properties, anchor: anchor, isSource: isSource) } - else { self } - } -} - -extension View { - func modify(_ builder: (Self) -> V) -> some View { builder(self) } -} diff --git a/Sources/Navigattie/Managers/NavigationManager.swift b/Sources/Navigattie/Managers/NavigationManager.swift index df3a2a3..ae0a64b 100644 --- a/Sources/Navigattie/Managers/NavigationManager.swift +++ b/Sources/Navigattie/Managers/NavigationManager.swift @@ -15,7 +15,7 @@ public extension NavigationManager { static func pop() { performOperation(.removeLast) } /// Returns to view with provided type - static func pop(to view: N) { performOperation(.removeAll(toID: view.id)) } + static func pop(to view: N.Type) { performOperation(.removeAll(toID: .init(describing: view))) } /// Returns to a root view static func popToRoot() { performOperation(.removeAllExceptFirst) } @@ -48,7 +48,7 @@ private extension NavigationManager { // MARK: - Setters extension NavigationManager { - static func setRoot(_ rootView: some NavigatableView) { shared.views = [.init(rootView, .no)] } + static func setRoot(_ rootView: some NavigatableView) { DispatchQueue.main.async { shared.views = [.init(rootView, .no)] }} static func setNamespace(_ value: Namespace.ID) { if shared.namespace == nil { shared.namespace = value } } static func blockTransitions(_ value: Bool) { shared.transitionsBlocked = value } } diff --git a/Sources/Navigattie/Protocols/NavigatableView.swift b/Sources/Navigattie/Protocols/NavigatableView.swift index 4eea891..811a704 100644 --- a/Sources/Navigattie/Protocols/NavigatableView.swift +++ b/Sources/Navigattie/Protocols/NavigatableView.swift @@ -11,26 +11,11 @@ import SwiftUI public protocol NavigatableView: View { - var backgroundColour: Color { get } + /// OPTIONAL: Changes the background colour of the selected view + var backgroundColour: Color? { get } } -// MARK: - Pushing and Removing From Stack -public extension NavigatableView { - /// Pushes a new view. Stacks previous one - func push(with animation: TransitionAnimation) { NavigationManager.push(self, animation) } -} -public extension NavigatableView { - /// Removes the current view from the stack - func pop() { NavigationManager.pop() } - - /// Removes all views up to the selected view in the stack. The view from the argument will be the new active view - func pop(to view: N) { NavigationManager.pop(to: view) } - - /// Removes all views from the stack. Root view will be the new active view - func popToRoot() { NavigationManager.popToRoot() } -} - -// MARK: - Others +// MARK: - Internals extension NavigatableView { var id: String { .init(describing: Self.self) } } diff --git a/Sources/Navigattie/Public/Public+NavigatableView.swift b/Sources/Navigattie/Public/Public+NavigatableView.swift new file mode 100644 index 0000000..324d813 --- /dev/null +++ b/Sources/Navigattie/Public/Public+NavigatableView.swift @@ -0,0 +1,38 @@ +// +// Public+NavigatableView.swift of Navigattie +// +// Created by Tomasz Kurylik +// - Twitter: https://twitter.com/tkurylik +// - Mail: tomasz.kurylik@mijick.com +// +// Copyright ©2023 Mijick. Licensed under MIT License. + + +import SwiftUI + +// MARK: - Initialising +public extension NavigatableView { + func implementNavigationView(config: NavigationConfig? = nil) -> some View { NavigationView(rootView: self, config: config) } +} + +// MARK: - Pushing And Removing Views From Stack +public extension NavigatableView { + /// Pushes a new view. Stacks previous one + func push(with animation: TransitionAnimation) { NavigationManager.push(self, animation) } +} +public extension View { + /// Removes the presented view from the stack + func pop() { NavigationManager.pop() } + + /// Removes all views up to the selected view in the stack. The view from the argument will be the new active view + func pop(to view: N.Type) { NavigationManager.pop(to: view) } + + /// Removes all views from the stack. Root view will be the new active view + func popToRoot() { NavigationManager.popToRoot() } +} + +// MARK: - Configurable +public extension NavigatableView { + /// OPTIONAL: Changes the background colour of the selected view + var backgroundColour: Color? { nil } +} diff --git a/Sources/Navigattie/Extensions/Views/NavigatableView++.swift b/Sources/Navigattie/Public/Public+NavigationConfig.swift similarity index 55% rename from Sources/Navigattie/Extensions/Views/NavigatableView++.swift rename to Sources/Navigattie/Public/Public+NavigationConfig.swift index 250dca5..42b5fde 100644 --- a/Sources/Navigattie/Extensions/Views/NavigatableView++.swift +++ b/Sources/Navigattie/Public/Public+NavigationConfig.swift @@ -1,5 +1,5 @@ // -// NavigatableView++.swift of Navigattie +// Public+NavigationConfig.swift of Navigattie // // Created by Tomasz Kurylik // - Twitter: https://twitter.com/tkurylik @@ -10,6 +10,8 @@ import SwiftUI -public extension NavigatableView { - func implementNavigationView() -> some View { NavigationView(rootView: self) } +public class NavigationConfig { + public var backgroundColour: Color = .clear + + public init() {} } diff --git a/Sources/Navigattie/Configurables/TransitionAnimation.swift b/Sources/Navigattie/Public/Public+TransitionAnimation.swift similarity index 82% rename from Sources/Navigattie/Configurables/TransitionAnimation.swift rename to Sources/Navigattie/Public/Public+TransitionAnimation.swift index f333e3f..cc69c52 100644 --- a/Sources/Navigattie/Configurables/TransitionAnimation.swift +++ b/Sources/Navigattie/Public/Public+TransitionAnimation.swift @@ -1,5 +1,5 @@ // -// TransitionAnimation.swift of Navigattie +// Public+TransitionAnimation.swift of Navigattie // // Created by Tomasz Kurylik // - Twitter: https://twitter.com/tkurylik @@ -8,8 +8,6 @@ // Copyright ©2023 Mijick. Licensed under MIT License. -import Foundation - public enum TransitionAnimation { case no case dissolve diff --git a/Sources/Navigattie/Type Erasers/AnyNavigatableView.swift b/Sources/Navigattie/Type Erasers/AnyNavigatableView.swift index e4fd1bb..7f6386b 100644 --- a/Sources/Navigattie/Type Erasers/AnyNavigatableView.swift +++ b/Sources/Navigattie/Type Erasers/AnyNavigatableView.swift @@ -13,7 +13,7 @@ import SwiftUI struct AnyNavigatableView: NavigatableView, Equatable { let id: String let animation: TransitionAnimation - let backgroundColour: Color + let backgroundColour: Color? private let _body: AnyView diff --git a/Sources/Navigattie/View Modifiers/AnimationCompletionModifier.swift b/Sources/Navigattie/View Modifiers/AnimationCompletionModifier.swift index 8da8557..df7c1a7 100644 --- a/Sources/Navigattie/View Modifiers/AnimationCompletionModifier.swift +++ b/Sources/Navigattie/View Modifiers/AnimationCompletionModifier.swift @@ -14,7 +14,6 @@ extension View { func onAnimationCompleted(for value: V, perform action: @escaping () -> ()) -> some View { modifier(Modifier(observedValue: value, completion: action)) } } - // MARK: - Implementation fileprivate struct Modifier: AnimatableModifier { var animatableData: V { didSet { notifyCompletionIfFinished() }} diff --git a/Sources/Navigattie/Views/NavigationStackView.swift b/Sources/Navigattie/Views/NavigationStackView.swift index 0bd4876..688be58 100644 --- a/Sources/Navigattie/Views/NavigationStackView.swift +++ b/Sources/Navigattie/Views/NavigationStackView.swift @@ -16,9 +16,10 @@ struct NavigationStackView: View { @State private var animatableOpacity: CGFloat = 1 @State private var animatableOffset: CGFloat = 0 @State private var animatableScale: CGFloat = 0 + private let config: NavigationConfig - init(namespace: Namespace.ID) { self._temporaryViews = .init(initialValue: NavigationManager.shared.views); NavigationManager.setNamespace(namespace) } + init(namespace: Namespace.ID, config: NavigationConfig) { self._temporaryViews = .init(initialValue: NavigationManager.shared.views); self.config = config; NavigationManager.setNamespace(namespace) } var body: some View { ZStack(content: createStack) .onChange(of: stack.views, perform: onViewsChanged) @@ -36,7 +37,7 @@ private extension NavigationStackView { func createItem(_ item: AnyNavigatableView) -> some View { item .scaleEffect(getScale(item)) - .background(item.backgroundColour) + .background(getBackground(item)) .transition(.identity) .opacity(getOpacity(item)) .offset(getOffset(item)) @@ -44,6 +45,11 @@ private extension NavigationStackView { } } +// MARK: - Getting Background +private extension NavigationStackView { + func getBackground(_ item: AnyNavigatableView) -> Color { item.backgroundColour ?? config.backgroundColour } +} + // MARK: - Calculating Opacity private extension NavigationStackView { func getOpacity(_ view: AnyNavigatableView) -> CGFloat { @@ -59,7 +65,7 @@ private extension NavigationStackView { } private extension NavigationStackView { func checkOpacityPrerequisites(_ view: AnyNavigatableView) throws { - if !view.isOne(of: stack.views.last, temporaryViews.last, temporaryViews.isNextToLast) { throw "Opacity can concern the last or next to last element of the stack" } + if !view.isOne(of: temporaryViews.last, temporaryViews.nextToLast) { throw "Opacity can concern the last or next to last element of the stack" } } func isLastView(_ view: AnyNavigatableView) -> Bool { let lastView = stack.transitionType == .push ? temporaryViews.last : stack.views.last @@ -93,7 +99,7 @@ private extension NavigationStackView { private extension NavigationStackView { func checkOffsetPrerequisites(_ view: AnyNavigatableView) throws { if !stack.transitionAnimation.isOne(of: .horizontalSlide, .verticalSlide) { throw "Offset cannot be set for a non-slide transition type" } - if !view.isOne(of: stack.views.last, temporaryViews.last, temporaryViews.isNextToLast) { throw "Offset can concern the last or next to last element of the stack" } + if !view.isOne(of: temporaryViews.last, temporaryViews.nextToLast) { throw "Offset can concern the last or next to last element of the stack" } } func calculateSlideOffsetValue(_ view: AnyNavigatableView) -> CGFloat { switch view == temporaryViews.last { @@ -126,7 +132,7 @@ private extension NavigationStackView { private extension NavigationStackView { func checkScalePrerequisites(_ view: AnyNavigatableView) throws { if !stack.transitionAnimation.isOne(of: .scale) { throw "Scale cannot be set for a non-scale transition type" } - if !view.isOne(of: temporaryViews.last, temporaryViews.isNextToLast) { throw "Scale can concern the last or next to last element of the stack" } + if !view.isOne(of: temporaryViews.last, temporaryViews.nextToLast) { throw "Scale can concern the last or next to last element of the stack" } } func calculateScaleValue(_ view: AnyNavigatableView) -> CGFloat { switch view == temporaryViews.last { diff --git a/Sources/Navigattie/Views/NavigationView.swift b/Sources/Navigattie/Views/NavigationView.swift index f0be1af..25b2243 100644 --- a/Sources/Navigattie/Views/NavigationView.swift +++ b/Sources/Navigattie/Views/NavigationView.swift @@ -12,7 +12,9 @@ import SwiftUI struct NavigationView: View { @Namespace var namespace + private let config: NavigationConfig - init(rootView: some NavigatableView) { NavigationManager.setRoot(rootView) } - var body: some View { NavigationStackView(namespace: namespace) } + + init(rootView: some NavigatableView, config: NavigationConfig?) { self.config = config ?? .init(); NavigationManager.setRoot(rootView) } + var body: some View { NavigationStackView(namespace: namespace, config: config) } }