diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index b651419c..fcb15f84 100644 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -167,6 +167,8 @@ Base, ); mainGroup = D5535812290E9691009E5D72; + packageReferences = ( + ); productRefGroup = D553581C290E9691009E5D72 /* Products */; projectDirPath = ""; projectRoot = ""; diff --git a/Demo/Demo/AppState.swift b/Demo/Demo/AppState.swift index 7dfa73ee..c339675f 100644 --- a/Demo/Demo/AppState.swift +++ b/Demo/Demo/AppState.swift @@ -70,63 +70,128 @@ final class AppState: ObservableObject { } } - struct Animation { - enum Curve: CaseIterable, CustomStringConvertible, Hashable { - case linear - case easeInOut - case spring - - var description: String { - switch self { - case .linear: - return "Linear" - case .easeInOut: - return "Ease In Out" - case .spring: - return "Spring" - } - } - } - - enum Duration: CaseIterable, CustomStringConvertible, Hashable { - case slow - case medium - case fast - - var description: String { - switch self { - case .slow: - return "Slow" - case .medium: - return "Medium" - case .fast: - return "Fast" - } - } + enum Animation: CaseIterable, CustomStringConvertible, Hashable { + case none + case linear + case easeInOut + case spring - func callAsFunction() -> Double { - switch self { - case .slow: - return 1 - case .medium: - return 0.6 - case .fast: - return 0.35 - } + var description: String { + switch self { + case .none: + return "None" + case .linear: + return "Linear" + case .easeInOut: + return "Ease In Out" + case .spring: + return "Spring" } } - var curve: Curve - var duration: Duration - - func callAsFunction() -> AnyNavigationTransition.Animation { - switch curve { + func callAsFunction( + duration: Duration, + stiffness: Stiffness, + damping: Damping + ) -> AnyNavigationTransition.Animation? { + switch self { + case .none: + return .none case .linear: return .linear(duration: duration()) case .easeInOut: return .easeInOut(duration: duration()) case .spring: - return .interpolatingSpring(stiffness: 120, damping: 50) + return .interpolatingSpring(stiffness: stiffness(), damping: damping()) + } + } + } + + enum Duration: CaseIterable, CustomStringConvertible, Hashable { + case slow + case medium + case fast + + var description: String { + switch self { + case .slow: + return "Slow" + case .medium: + return "Medium" + case .fast: + return "Fast" + } + } + + func callAsFunction() -> Double { + switch self { + case .slow: + return 1 + case .medium: + return 0.6 + case .fast: + return 0.35 + } + } + } + + enum Stiffness: CaseIterable, CustomStringConvertible, Hashable { + case low + case medium + case high + + var description: String { + switch self { + case .low: + return "Low" + case .medium: + return "Medium" + case .high: + return "High" + } + } + + func callAsFunction() -> Double { + switch self { + case .low: + return 300 + case .medium: + return 120 + case .high: + return 50 + } + } + } + + enum Damping: CaseIterable, CustomStringConvertible, Hashable { + case low + case medium + case high + case veryHigh + + var description: String { + switch self { + case .low: + return "Low" + case .medium: + return "Medium" + case .high: + return "High" + case .veryHigh: + return "Very High" + } + } + + func callAsFunction() -> Double { + switch self { + case .low: + return 20 + case .medium: + return 25 + case .high: + return 30 + case .veryHigh: + return 50 } } } @@ -160,7 +225,12 @@ final class AppState: ObservableObject { } @Published var transition: Transition = .slide - @Published var animation: Animation = .init(curve: .easeInOut, duration: .fast) + + @Published var animation: Animation = .spring + @Published var duration: Duration = .fast + @Published var stiffness: Stiffness = .low + @Published var damping: Damping = .high + @Published var interactivity: Interactivity = .edgePan @Published var isPresentingSettings: Bool = false diff --git a/Demo/Demo/RootView.swift b/Demo/Demo/RootView.swift index 84e69b8d..8e64a6cd 100644 --- a/Demo/Demo/RootView.swift +++ b/Demo/Demo/RootView.swift @@ -17,12 +17,25 @@ struct RootView: View { .navigationViewStyle(.stack) } } - .navigationTransition( - appState.transition().animation(appState.animation()), - interactivity: appState.interactivity() - ) + .navigationTransition(transition.animation(animation), interactivity: interactivity) .sheet(isPresented: $appState.isPresentingSettings) { SettingsView().environmentObject(appState) } } + + var transition: AnyNavigationTransition { + appState.transition() + } + + var animation: AnyNavigationTransition.Animation? { + appState.animation( + duration: appState.duration, + stiffness: appState.stiffness, + damping: appState.damping + ) + } + + var interactivity: AnyNavigationTransition.Interactivity { + appState.interactivity() + } } diff --git a/Demo/Demo/SettingsView.swift b/Demo/Demo/SettingsView.swift index 87deb38a..c68c39f4 100644 --- a/Demo/Demo/SettingsView.swift +++ b/Demo/Demo/SettingsView.swift @@ -7,13 +7,21 @@ struct SettingsView: View { var body: some View { NavigationView { Form { - Section(header: Text("Transition"), footer: transitionFooter) { + Section(header: Text("Transition")) { picker("Transition", $appState.transition) } - Section(header: Text("Animation"), footer: animationFooter) { - picker("Curve", $appState.animation.curve) - picker("Duration", $appState.animation.duration) + Section(header: Text("Animation")) { + picker("Animation", $appState.animation) + switch appState.animation { + case .none: + EmptyView() + case .linear, .easeInOut: + picker("Duration", $appState.duration) + case .spring: + picker("Stiffness", $appState.stiffness) + picker("Damping", $appState.damping) + } } Section(header: Text("Interactivity"), footer: interactivityFooter) { @@ -31,22 +39,6 @@ struct SettingsView: View { .navigationViewStyle(.stack) } - var transitionFooter: some View { - Text( - """ - "Swing" is a custom transition exclusive to this demo (only 12 lines of code!). - """ - ) - } - - var animationFooter: some View { - Text( - """ - Note: Duration is ignored when the Spring curve is selected. - """ - ) - } - var interactivityFooter: some View { Text( """ @@ -76,8 +68,12 @@ struct SettingsView: View { func shuffle() { appState.transition = .allCases.randomElement()! - appState.animation.curve = .allCases.randomElement()! - appState.animation.duration = .allCases.randomElement()! + + appState.animation = .allCases.randomElement()! + appState.duration = .allCases.randomElement()! + appState.stiffness = .allCases.randomElement()! + appState.damping = .allCases.randomElement()! + appState.interactivity = .allCases.randomElement()! } diff --git a/Sources/NavigationTransition/AnyNavigationTransition.swift b/Sources/NavigationTransition/AnyNavigationTransition.swift index ec97ac0d..5444a836 100644 --- a/Sources/NavigationTransition/AnyNavigationTransition.swift +++ b/Sources/NavigationTransition/AnyNavigationTransition.swift @@ -22,7 +22,7 @@ public struct AnyNavigationTransition { @_spi(package)public let isDefault: Bool @_spi(package)public let handler: Handler - @_spi(package)public var animation: Animation = .default + @_spi(package)public var animation: Animation? = .default public init(_ transition: T) { self.isDefault = false @@ -42,7 +42,7 @@ extension AnyNavigationTransition { public typealias Animation = _Animation /// Attaches an animation to this transition. - public func animation(_ animation: Animation) -> Self { + public func animation(_ animation: Animation?) -> Self { var copy = self copy.animation = animation return copy diff --git a/Sources/NavigationTransitions/NavigationTransitionDelegate.swift b/Sources/NavigationTransitions/NavigationTransitionDelegate.swift index e8fb123d..93c4e518 100644 --- a/Sources/NavigationTransitions/NavigationTransitionDelegate.swift +++ b/Sources/NavigationTransitions/NavigationTransitionDelegate.swift @@ -5,8 +5,9 @@ import UIKit final class NavigationTransitionDelegate: NSObject, UINavigationControllerDelegate { let transition: AnyNavigationTransition - weak var baseDelegate: UINavigationControllerDelegate? + private weak var baseDelegate: UINavigationControllerDelegate? var interactionController: UIPercentDrivenInteractiveTransition? + private var initialAreAnimationsEnabled = UIView.areAnimationsEnabled init(transition: AnyNavigationTransition, baseDelegate: UINavigationControllerDelegate?) { self.transition = transition @@ -14,11 +15,14 @@ final class NavigationTransitionDelegate: NSObject, UINavigationControllerDelega } func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { + initialAreAnimationsEnabled = UIView.areAnimationsEnabled + UIView.setAnimationsEnabled(transition.animation != nil) baseDelegate?.navigationController?(navigationController, willShow: viewController, animated: animated) } func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { baseDelegate?.navigationController?(navigationController, didShow: viewController, animated: animated) + UIView.setAnimationsEnabled(initialAreAnimationsEnabled) } func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { @@ -26,8 +30,15 @@ final class NavigationTransitionDelegate: NSObject, UINavigationControllerDelega } func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { - if let operation = NavigationTransitionOperation(operation) { - return NavigationTransitionAnimatorProvider(transition: transition, operation: operation) + if + let animation = transition.animation, + let operation = NavigationTransitionOperation(operation) + { + return NavigationTransitionAnimatorProvider( + transition: transition, + animation: animation, + operation: operation + ) } else { return nil } @@ -36,15 +47,17 @@ final class NavigationTransitionDelegate: NSObject, UINavigationControllerDelega final class NavigationTransitionAnimatorProvider: NSObject, UIViewControllerAnimatedTransitioning { let transition: AnyNavigationTransition + let animation: Animation let operation: NavigationTransitionOperation - init(transition: AnyNavigationTransition, operation: NavigationTransitionOperation) { + init(transition: AnyNavigationTransition, animation: Animation, operation: NavigationTransitionOperation) { self.transition = transition + self.animation = animation self.operation = operation } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - transition.animation.duration + animation.duration } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { @@ -67,7 +80,7 @@ final class NavigationTransitionAnimatorProvider: NSObject, UIViewControllerAnim } let animator = UIViewPropertyAnimator( duration: transitionDuration(using: transitionContext), - timingParameters: transition.animation.timingParameters + timingParameters: animation.timingParameters ) cachedAnimators[ObjectIdentifier(transitionContext)] = animator