From f61b74c42534e7d294e71ee57655fde3e07da7f0 Mon Sep 17 00:00:00 2001 From: Gabriel Lanata Date: Fri, 25 Feb 2022 10:51:18 -0800 Subject: [PATCH] Improve view initialization (#42) Currently, when you initialize an event generator from a UIView, it adds that view directly to a UIWindow. This can be a destructive change because the view might already be a subview of another view (possibly even already added to a window) and it will get removed. This change detects if the view already has a window and uses that directly. If not, it will get the top level superview by transversing the view hierarchy and use that instead. Because we set the mainView to the view that was passed in the initializer, everything else remains the same and it should not be a breaking change. --- HammerTests.podspec | 2 +- Sources/Hammer/EventGenerator/EventGenerator.swift | 14 ++++++++++---- Sources/Hammer/Utilties/HammerError.swift | 6 +++--- Sources/Hammer/Utilties/UIKit+Extensions.swift | 7 +++++++ 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/HammerTests.podspec b/HammerTests.podspec index 1616c0d..b6caaa5 100644 --- a/HammerTests.podspec +++ b/HammerTests.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "HammerTests" - spec.version = "0.13.3" + spec.version = "0.14.0" spec.summary = "iOS touch and keyboard syntheis library for unit tests." spec.description = "Hammer is a touch and keyboard synthesis library for emulating user interaction events. It enables new ways of triggering UI actions in unit tests, replicating a real world environment as much as possible." spec.homepage = "https://github.com/lyft/Hammer" diff --git a/Sources/Hammer/EventGenerator/EventGenerator.swift b/Sources/Hammer/EventGenerator/EventGenerator.swift index 0e75fea..cbd2e24 100644 --- a/Sources/Hammer/EventGenerator/EventGenerator.swift +++ b/Sources/Hammer/EventGenerator/EventGenerator.swift @@ -59,11 +59,12 @@ public final class EventGenerator { /// Initialize an event generator for a specified UIViewController. /// - /// Event Generator will temporarily create a wrapper UIWindow to send touches. + /// If the view controller's view does not have a window, this will temporarily create a wrapper + /// UIWindow to send touches. /// /// - parameter viewController: The viewController to receive events. public convenience init(viewController: UIViewController) throws { - let window = UIWindow(wrapping: viewController) + let window = viewController.view.window ?? UIWindow(wrapping: viewController) if #available(iOS 13.0, *) { window.backgroundColor = .systemBackground @@ -81,12 +82,17 @@ public final class EventGenerator { /// Initialize an event generator for a specified UIView. /// - /// Event Generator will temporarily create a wrapper UIWindow to send touches. + /// If the view does not have a window, this will temporarily create a wrapper UIWindow to send touches. /// /// - parameter view: The view to receive events. /// - parameter alignment: The wrapping alignment to use. public convenience init(view: UIView, alignment: WrappingAlignment = .center) throws { - try self.init(viewController: UIViewController(wrapping: view, alignment: alignment)) + if let window = view.window { + try self.init(window: window) + } else { + try self.init(viewController: UIViewController(wrapping: view.topLevelView, alignment: alignment)) + } + self.mainView = view } diff --git a/Sources/Hammer/Utilties/HammerError.swift b/Sources/Hammer/Utilties/HammerError.swift index 9add251..64e8529 100644 --- a/Sources/Hammer/Utilties/HammerError.swift +++ b/Sources/Hammer/Utilties/HammerError.swift @@ -72,11 +72,11 @@ extension HammerError: CustomStringConvertible { case .viewIsNotInHierarchy(let view): return "View is not in hierarchy: \(view.shortDescription)" case .viewIsNotVisible(let view): - return "View is not in visible: \(view.shortDescription)" + return "View is not visible: \(view.shortDescription)" case .viewIsNotHittable(let view): - return "View is not in hittable: \(view.shortDescription)" + return "View is not hittable: \(view.shortDescription)" case .pointIsNotHittable(let point): - return "Point is not in hittable: \(point)" + return "Point is not hittable: \(point)" case .unableToFindView(let identifier): return "Unable to find view: \"\(identifier)\"" case .invalidViewType(let identifier, let type, let expected): diff --git a/Sources/Hammer/Utilties/UIKit+Extensions.swift b/Sources/Hammer/Utilties/UIKit+Extensions.swift index 20acdf3..2a1da24 100644 --- a/Sources/Hammer/Utilties/UIKit+Extensions.swift +++ b/Sources/Hammer/Utilties/UIKit+Extensions.swift @@ -76,3 +76,10 @@ extension UIViewController { } } } + +extension UIView { + /// Returns the view at the top level of the view hierarchy. Could be a UIWindow. + var topLevelView: UIView { + return self.superview?.topLevelView ?? self + } +}