Skip to content
Merged
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
2 changes: 2 additions & 0 deletions Demo/Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@
Base,
);
mainGroup = D5535812290E9691009E5D72;
packageReferences = (
);
productRefGroup = D553581C290E9691009E5D72 /* Products */;
projectDirPath = "";
projectRoot = "";
Expand Down
168 changes: 119 additions & 49 deletions Demo/Demo/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Expand Down Expand Up @@ -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
Expand Down
21 changes: 17 additions & 4 deletions Demo/Demo/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
40 changes: 18 additions & 22 deletions Demo/Demo/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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(
"""
Expand Down Expand Up @@ -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()!
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/NavigationTransition/AnyNavigationTransition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: NavigationTransition>(_ transition: T) {
self.isDefault = false
Expand All @@ -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
Expand Down
25 changes: 19 additions & 6 deletions Sources/NavigationTransitions/NavigationTransitionDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,40 @@ 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
self.baseDelegate = baseDelegate
}

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? {
return interactionController
}

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
}
Expand All @@ -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) {
Expand All @@ -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

Expand Down