From d863edbb27277a0d05f32307557e30ce2395da78 Mon Sep 17 00:00:00 2001 From: maximkrouk Date: Tue, 4 Mar 2025 21:14:39 +0100 Subject: [PATCH 1/5] Fix .speed modifier for CocoaAnimations - Align with SwiftUI animations - Add issue reporting --- .../AppKitNavigation/AppKitAnimation.swift | 33 ++++++++++++++++++- Sources/UIKitNavigation/UIKitAnimation.swift | 25 ++++++++++---- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/Sources/AppKitNavigation/AppKitAnimation.swift b/Sources/AppKitNavigation/AppKitAnimation.swift index 096761ab3..6ec4b43b0 100644 --- a/Sources/AppKitNavigation/AppKitAnimation.swift +++ b/Sources/AppKitNavigation/AppKitAnimation.swift @@ -1,6 +1,7 @@ #if canImport(AppKit) && !targetEnvironment(macCatalyst) import AppKit import SwiftNavigation + import IssueReporting #if canImport(SwiftUI) import SwiftUI @@ -43,7 +44,7 @@ var result: Swift.Result? NSAnimationContext.runAnimationGroup { context in context.allowsImplicitAnimation = true - context.duration = animation.duration + context.duration = animation.duration / animation.speed context.timingFunction = animation.timingFunction result = Swift.Result(catching: body) } completionHandler: { @@ -74,6 +75,7 @@ fileprivate struct AppKit: Hashable, @unchecked Sendable { fileprivate var duration: TimeInterval + fileprivate var speed: TimeInterval fileprivate var timingFunction: CAMediaTimingFunction? func hash(into hasher: inout Hasher) { @@ -84,6 +86,35 @@ } extension AppKitAnimation { + /// Changes the duration of an animation by adjusting its speed. + /// + /// - Parameter speed: The speed at which SwiftUI performs the animation. + /// - Returns: An animation with the adjusted speed. + public func speed( + _ speed: Double + ) -> Self { + switch framework { + case let .swiftUI(animation): + return AppKitAnimation( + framework: .swiftUI(animation.speed(speed)) + ) + case var .appKit(animation): + if speed != 0 { + animation.speed = speed + } else { + reportIssue( + """ + Setting animation speed to zero is not supported for AppKit animations. + Replacing with `.ulpOfOne` to avoid division by zero. + """ + ) + animation.speed = .ulpOfOne + } + return AppKitAnimation(framework: .appKit(animation)) + } + } + + /// Animates changes with the specified duration and timing function. /// /// A value description of `UIView.runAnimationGroup` that can be used with diff --git a/Sources/UIKitNavigation/UIKitAnimation.swift b/Sources/UIKitNavigation/UIKitAnimation.swift index f0fef59ea..4239fb2c5 100644 --- a/Sources/UIKitNavigation/UIKitAnimation.swift +++ b/Sources/UIKitNavigation/UIKitAnimation.swift @@ -1,5 +1,6 @@ #if canImport(UIKit) && !os(watchOS) import UIKit + import IssueReporting #if canImport(SwiftUI) import SwiftUI @@ -71,8 +72,8 @@ var result: Swift.Result? withoutActuallyEscaping(animations) { animations in UIView.animate( - withDuration: animation.duration * animation.speed, - delay: animation.delay * animation.speed, + withDuration: animation.duration / animation.speed, + delay: animation.delay / animation.speed, options: animation.options, animations: { result = Swift.Result(catching: animations) }, completion: completion @@ -84,8 +85,8 @@ var result: Swift.Result? withoutActuallyEscaping(animations) { animations in UIView.animate( - withDuration: animation.duration * animation.speed, - delay: animation.delay * animation.speed, + withDuration: animation.duration / animation.speed, + delay: animation.delay / animation.speed, usingSpringWithDamping: dampingRatio, initialSpringVelocity: velocity, options: animation.options, @@ -99,10 +100,10 @@ if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) { var result: Swift.Result? UIView.animate( - springDuration: animation.duration * animation.speed, + springDuration: animation.duration / animation.speed, bounce: bounce, initialSpringVelocity: initialSpringVelocity, - delay: animation.delay * animation.speed, + delay: animation.delay / animation.speed, options: animation.options, animations: { result = Swift.Result(catching: animations) }, completion: completion @@ -220,7 +221,17 @@ framework: .swiftUI(animation.speed(speed)) ) case var .uiKit(animation): - animation.speed = speed + if speed != 0 { + animation.speed = speed + } else { + reportIssue( + """ + Setting animation speed to zero is not supported for UIKit animations. + Replacing with `.ulpOfOne` to avoid division by zero. + """ + ) + animation.speed = .ulpOfOne + } return UIKitAnimation(framework: .uiKit(animation)) } } From 67e87195466211971acddda21cbbe663d414dd08 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 23 Sep 2025 11:26:53 -0700 Subject: [PATCH 2/5] Refactor AppKitAnimation extensions and structure Refactor AppKitAnimation by removing unnecessary closing brace and adjusting extensions. --- Sources/AppKitNavigation/AppKitAnimation.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Sources/AppKitNavigation/AppKitAnimation.swift b/Sources/AppKitNavigation/AppKitAnimation.swift index 6ec4b43b0..a2d2a8ba9 100644 --- a/Sources/AppKitNavigation/AppKitAnimation.swift +++ b/Sources/AppKitNavigation/AppKitAnimation.swift @@ -83,9 +83,7 @@ } } } - } - - extension AppKitAnimation { + /// Changes the duration of an animation by adjusting its speed. /// /// - Parameter speed: The speed at which SwiftUI performs the animation. @@ -113,8 +111,9 @@ return AppKitAnimation(framework: .appKit(animation)) } } + } - + extension AppKitAnimation { /// Animates changes with the specified duration and timing function. /// /// A value description of `UIView.runAnimationGroup` that can be used with From fa71c8db2012b321fde25c3ca074a52185026f6b Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 23 Sep 2025 11:28:26 -0700 Subject: [PATCH 3/5] Clarify warning for zero animation speed Updated warning message for animation speed adjustment. --- Sources/UIKitNavigation/UIKitAnimation.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/UIKitNavigation/UIKitAnimation.swift b/Sources/UIKitNavigation/UIKitAnimation.swift index 4239fb2c5..8c39662df 100644 --- a/Sources/UIKitNavigation/UIKitAnimation.swift +++ b/Sources/UIKitNavigation/UIKitAnimation.swift @@ -227,7 +227,8 @@ reportIssue( """ Setting animation speed to zero is not supported for UIKit animations. - Replacing with `.ulpOfOne` to avoid division by zero. + + Replace with '.ulpOfOne' to avoid division by zero. """ ) animation.speed = .ulpOfOne From 011ed67cf79079d465318ca81c0b767c93626081 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 23 Sep 2025 11:28:57 -0700 Subject: [PATCH 4/5] Fix warning message for zero animation speed --- Sources/AppKitNavigation/AppKitAnimation.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/AppKitNavigation/AppKitAnimation.swift b/Sources/AppKitNavigation/AppKitAnimation.swift index a2d2a8ba9..7fa5cfff4 100644 --- a/Sources/AppKitNavigation/AppKitAnimation.swift +++ b/Sources/AppKitNavigation/AppKitAnimation.swift @@ -102,8 +102,8 @@ } else { reportIssue( """ - Setting animation speed to zero is not supported for AppKit animations. - Replacing with `.ulpOfOne` to avoid division by zero. + Setting animation speed to zero is not supported for AppKit animations. \ + Replace with '.ulpOfOne' to avoid division by zero. """ ) animation.speed = .ulpOfOne From 52372868fce2aa64b9b8a8d1faa38a717e9d96e3 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 23 Sep 2025 12:17:57 -0700 Subject: [PATCH 5/5] Set default speed to 1 in AppKitAnimation.swift --- Sources/AppKitNavigation/AppKitAnimation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AppKitNavigation/AppKitAnimation.swift b/Sources/AppKitNavigation/AppKitAnimation.swift index 7fa5cfff4..3ca28a144 100644 --- a/Sources/AppKitNavigation/AppKitAnimation.swift +++ b/Sources/AppKitNavigation/AppKitAnimation.swift @@ -75,7 +75,7 @@ fileprivate struct AppKit: Hashable, @unchecked Sendable { fileprivate var duration: TimeInterval - fileprivate var speed: TimeInterval + fileprivate var speed: TimeInterval = 1 fileprivate var timingFunction: CAMediaTimingFunction? func hash(into hasher: inout Hasher) {