From d863edbb27277a0d05f32307557e30ce2395da78 Mon Sep 17 00:00:00 2001 From: maximkrouk Date: Tue, 4 Mar 2025 21:14:39 +0100 Subject: [PATCH] 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)) } }