Skip to content

Commit 6796f9b

Browse files
committed
wait for any unfinished animations
1 parent 08802f0 commit 6796f9b

File tree

3 files changed

+68
-25
lines changed

3 files changed

+68
-25
lines changed

Sources/Hammer/EventGenerator/EventGenerator.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ public final class EventGenerator {
121121
}
122122
}
123123

124+
/// Waits until animations are finished.
125+
///
126+
/// - parameter timeout: The maximum time to wait for the window to be ready.
127+
public func waitUntilAnimationsAreFinished(timeout: TimeInterval) throws {
128+
try self.waitUntil(!self.hasRunningAnimations, timeout: timeout)
129+
}
130+
124131
/// Returns if the window is ready to receive user interaction events
125132
public var isWindowReady: Bool {
126133
guard !(UIApplication.shared as UIApplicationDeprecated).isIgnoringInteractionEvents
@@ -148,6 +155,32 @@ public final class EventGenerator {
148155
return true
149156
}
150157

158+
// Returns if the view or any of its subviews has running animations.
159+
public var hasRunningAnimations: Bool {
160+
// Recursive
161+
func hasRunningAnimations(currentView: UIView) -> Bool {
162+
// If the view is not visible, we do not need to consider it as running animation
163+
guard self.viewIsVisible(currentView) else {
164+
return false
165+
}
166+
167+
// If there are animations running on the layer, return true
168+
if currentView.layer.animationKeys()?.isEmpty == false {
169+
return true
170+
}
171+
172+
// Special case for parallax dimming view which happens during some animations
173+
if String(describing: type(of: currentView)) == "_UIParallaxDimmingView" {
174+
return true
175+
}
176+
177+
// Traverse subviews
178+
return currentView.subviews.contains { hasRunningAnimations(currentView: $0) }
179+
}
180+
181+
return hasRunningAnimations(currentView: self.window)
182+
}
183+
151184
/// Gets the next event ID to use. Event IDs are global and sequential.
152185
///
153186
/// - returns: The next event ID.

Sources/Hammer/Utilties/Subviews.swift

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -154,31 +154,7 @@ extension EventGenerator {
154154
///
155155
/// - returns: If the view is visible
156156
public func viewIsVisible(_ view: UIView, visibility: Visibility = .partial) -> Bool {
157-
guard view.isDescendant(of: self.window) else {
158-
return false
159-
}
160-
161-
// Recursive
162-
func viewIsVisible(currentView: UIView) -> Bool {
163-
guard !currentView.isHidden && currentView.alpha >= 0.01 else {
164-
return false
165-
}
166-
167-
guard let superview = currentView.superview else {
168-
return currentView == self.window
169-
}
170-
171-
if superview.clipsToBounds {
172-
let adjustedBounds = view.convert(view.bounds, to: superview)
173-
guard superview.bounds.isVisible(adjustedBounds, visibility: visibility) else {
174-
return false
175-
}
176-
}
177-
178-
return viewIsVisible(currentView: superview)
179-
}
180-
181-
return viewIsVisible(currentView: view)
157+
return view.isVisible(inWindow: self.window, visibility: visibility)
182158
}
183159

184160
/// Returns if the specified rect is visible.

Sources/Hammer/Utilties/UIKit+Extensions.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,38 @@ extension UIView {
7575
var topLevelView: UIView {
7676
return self.superview?.topLevelView ?? self
7777
}
78+
79+
/// Returns if the view is visible.
80+
///
81+
/// - parameter window: The window to check if the view is part of.
82+
/// - parameter visibility: How determine if the view is visible.
83+
///
84+
/// - returns: If the view is visible
85+
func isVisible(inWindow window: UIWindow, visibility: EventGenerator.Visibility = .partial) -> Bool {
86+
guard self.isDescendant(of: window) else {
87+
return false
88+
}
89+
90+
// Recursive
91+
func isVisible(currentView: UIView) -> Bool {
92+
guard !currentView.isHidden && currentView.alpha >= 0.01 else {
93+
return false
94+
}
95+
96+
guard let superview = currentView.superview else {
97+
return currentView == window
98+
}
99+
100+
if superview.clipsToBounds {
101+
let adjustedBounds = self.convert(self.bounds, to: superview)
102+
guard superview.bounds.isVisible(adjustedBounds, visibility: visibility) else {
103+
return false
104+
}
105+
}
106+
107+
return isVisible(currentView: superview)
108+
}
109+
110+
return isVisible(currentView: self)
111+
}
78112
}

0 commit comments

Comments
 (0)