Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve hit test methods #6

Merged
merged 4 commits into from
Jul 16, 2021
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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,22 @@ When running on a full screen app or testing navigation, specifying a CGPoint in

```swift
let myButton = try eventGenerator.viewWithIdentifier("my_button", ofType: UIButton.self)
try eventGenerator.fingerTap(at: myButton)
```

This method will throw an error if the view was not found in the hierarchy. If you're testing navigation or screen changes and you need to wait until the view appears, you can add a timeout. This will wait until the hierarchy has updated and return the view.

```swift
let myButton = try eventGenerator.viewWithIdentifier("my_button", ofType: UIButton.self, timeout: 1)
try eventGenerator.fingerTap(at: myButton)
```

You can also pass accessibility identifiers directly to the event methods.

```swift
try eventGenerator.fingerDown(at: "my_draggable_object")
try eventGenerator.fingerMove(to: "drop_target", duration: 0.5)
try eventGenerator.fingerUp()
```

### Waiting
Expand Down
12 changes: 8 additions & 4 deletions Sources/Hammer/AppleInternal/AppleInternal+UIKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ import UIKit
extension UIApplication {
typealias HIDEventCallback = (_ event: IOHIDEvent) -> Void

private static var hidEventCallbacks = [HIDEventCallback]()
private static var hidEventCallbacks = [ObjectIdentifier: HIDEventCallback]()

@objc
private func swizzledHandleHIDEvent(_ event: IOHIDEvent) {
// Calling this really calls the original un-swizzled method
self.swizzledHandleHIDEvent(event)

UIApplication.hidEventCallbacks.forEach { $0(event) }
UIApplication.hidEventCallbacks.values.forEach { $0(event) }
}

private static let runOnce: () = {
Expand All @@ -46,7 +46,11 @@ extension UIApplication {
self.runOnce
}

static func registerForHIDEvents(callback: @escaping HIDEventCallback) {
self.hidEventCallbacks.append(callback)
static func registerForHIDEvents(_ object: ObjectIdentifier, callback: @escaping HIDEventCallback) {
self.hidEventCallbacks.updateValue(callback, forKey: object)
}

static func unregisterForHIDEvents(_ object: ObjectIdentifier) {
self.hidEventCallbacks.removeValue(forKey: object)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ extension EventGenerator {
/// - parameter locations: The locations where to touch down.
public func fingerDown(_ indices: [FingerIndex?] = .automatic, at locations: [HammerLocatable]) throws {
let indices = try self.fillNextFingerIndices(indices, withExpected: locations.count)
let locations = try locations.map { try $0.hitPoint(for: self) }
let locations = try locations.map { try $0.windowHitPoint(for: self) }
try self.checkPointsAreHittable(locations)
try self.sendEvent(hand: HandInfo(fingers: zip(locations, indices).map { location, index in
FingerInfo(fingerIndex: index, location: location, phase: .began,
Expand All @@ -37,7 +37,7 @@ extension EventGenerator {
/// - parameter index: The finger index to touch down.
/// - parameter location: The location where to touch down. Nil to use the center.
public func fingerDown(_ index: FingerIndex? = .automatic, at location: HammerLocatable? = nil) throws {
try self.fingerDown([index], at: [location ?? self.defaultTouchLocation])
try self.fingerDown([index], at: [location ?? self.mainView])
}

/// Sends a finger up event.
Expand Down Expand Up @@ -119,7 +119,7 @@ extension EventGenerator {
/// - parameter locations: The new locations of the fingers.
public func fingerMove(_ indices: [FingerIndex?] = .automatic, to locations: [HammerLocatable]) throws {
let indices = try self.fillExistingFingerIndices(indices, withMinimum: locations.count)
let locations = try locations.map { try $0.hitPoint(for: self) }
let locations = try locations.map { try $0.windowHitPoint(for: self) }
let fingers = zip(locations, indices).map { location, index in
FingerInfo(fingerIndex: index, location: location, phase: .moved,
pressure: 0, twist: 0, majorRadius: kDefaultRadius, minorRadius: kDefaultRadius)
Expand Down Expand Up @@ -154,7 +154,7 @@ extension EventGenerator {
}

let indices = try self.fillExistingFingerIndices(indices, withMinimum: locations.count)
let locations = try locations.map { try $0.hitPoint(for: self) }
let locations = try locations.map { try $0.windowHitPoint(for: self) }
let startLocations = self.activeTouches.fingers(forIndices: indices).map(\.location)

let startTime = Date()
Expand Down Expand Up @@ -217,7 +217,7 @@ extension EventGenerator {
angle radians: CGFloat = 0) throws
{
let indices = try self.fillNextFingerIndices(indices, withExpected: 2)
let location = try location?.hitPoint(for: self) ?? self.defaultTouchLocation
let location = try (location ?? self.mainView).windowHitPoint(for: self)
try self.fingerDown(indices, at: location.twoWayOffset(distance, angle: radians))
}

Expand Down Expand Up @@ -245,7 +245,7 @@ extension EventGenerator {
angle radians: CGFloat = 0) throws
{
let indices = try self.fillExistingFingerIndices(indices, withMinimum: 2)
let location = try location.hitPoint(for: self)
let location = try location.windowHitPoint(for: self)
try self.fingerMove(indices, to: location.twoWayOffset(distance, angle: radians))
}

Expand All @@ -263,7 +263,7 @@ extension EventGenerator {
withDistance distance: CGFloat = EventGenerator.twoFingerDistance,
angle radians: CGFloat = 0, duration: TimeInterval) throws
{
let location = try location.hitPoint(for: self)
let location = try location.windowHitPoint(for: self)
try self.fingerMove(indices, to: location.twoWayOffset(distance, angle: radians), duration: duration)
}

Expand Down Expand Up @@ -300,7 +300,7 @@ extension EventGenerator {
angle radians: CGFloat = 0, duration: TimeInterval) throws
{
let indices = try self.fillNextFingerIndices(indices, withExpected: 2)
let location = try location?.hitPoint(for: self) ?? self.defaultTouchLocation
let location = try (location ?? self.mainView).windowHitPoint(for: self)
let startLocations = location.twoWayOffset(startDistance, angle: radians)
let endLocations = location.twoWayOffset(endDistance, angle: radians)
try self.fingerDown(indices, at: startLocations)
Expand Down Expand Up @@ -352,7 +352,7 @@ extension EventGenerator {
angle radians: CGFloat) throws
{
let indices = try self.fillExistingFingerIndices(indices, withMinimum: 1)
let anchor = try anchor.hitPoint(for: self)
let anchor = try anchor.windowHitPoint(for: self)
let locations = self.activeTouches.fingers(forIndices: indices).map(\.location)
try self.fingerMove(indices, to: locations.map { $0.pivot(anchor: anchor, angle: radians) })
}
Expand All @@ -376,7 +376,7 @@ extension EventGenerator {
}

let indices = try self.fillExistingFingerIndices(indices, withMinimum: 1)
let anchor = try anchor.hitPoint(for: self)
let anchor = try anchor.windowHitPoint(for: self)
let startLocations = self.activeTouches.fingers(forIndices: indices).map(\.location)

let startTime = Date()
Expand Down Expand Up @@ -413,7 +413,7 @@ extension EventGenerator {
duration: TimeInterval) throws
{
let indices = try self.fillNextFingerIndices(indices, withExpected: 2)
let location = try location?.hitPoint(for: self) ?? self.defaultTouchLocation
let location = try (location ?? self.mainView).windowHitPoint(for: self)
try self.fingerDown(indices, at: location.twoWayOffset(distance, angle: startRadians))
try self.fingerPivot(indices, aroundAnchor: location, byAngle: endRadians - startRadians,
duration: duration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ extension EventGenerator {
public func stylusDown(at location: HammerLocatable? = nil,
azimuth: CGFloat = 0, altitude: CGFloat = 0, pressure: CGFloat = 0) throws
{
let location = try location?.hitPoint(for: self) ?? self.defaultTouchLocation
let location = try (location ?? self.mainView).windowHitPoint(for: self)
try self.checkPointsAreHittable([location])
try self.sendEvent(stylus: StylusInfo(location: location, phase: .began,
pressure: pressure, twist: 0,
Expand Down Expand Up @@ -110,7 +110,7 @@ extension EventGenerator {
public func stylusMove(to location: HammerLocatable,
azimuth: CGFloat = 0, altitude: CGFloat = 0, pressure: CGFloat = 0) throws
{
let location = try location.hitPoint(for: self)
let location = try location.windowHitPoint(for: self)
try self.sendEvent(stylus: StylusInfo(location: location, phase: .moved, pressure: pressure, twist: 0,
altitude: altitude, azimuth: azimuth))
}
Expand All @@ -130,7 +130,7 @@ extension EventGenerator {
throw HammerError.touchForStylusDoesNotExist
}

let location = try location.hitPoint(for: self)
let location = try location.windowHitPoint(for: self)

let startLocation = existingStylus.location
let startAzimuth = existingStylus.azimuth
Expand Down
14 changes: 8 additions & 6 deletions Sources/Hammer/EventGenerator/EventGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public final class EventGenerator {
/// The window for the events
public let window: UIWindow

/// The view that was used to create the event generator
private(set) var mainView: UIView

var activeTouches = TouchStorage()
var debugWindow = DebugVisualizerWindow()
var eventCallbacks = [UInt32: CompletionHandler]()
Expand All @@ -29,21 +32,17 @@ public final class EventGenerator {
set { self.debugWindow.isHidden = !newValue }
}

/// The default location for touches when it's not specified.
var defaultTouchLocation: CGPoint {
self.window.bounds.center
}

/// Initialize an event generator for a specified UIWindow.
///
/// - parameter window: The window to receive events.
public init(window: UIWindow) throws {
self.window = window
self.window.layoutIfNeeded()
self.debugWindow.frame = self.window.frame
self.mainView = window

UIApplication.swizzle()
UIApplication.registerForHIDEvents { [weak self] event in
UIApplication.registerForHIDEvents(ObjectIdentifier(self)) { [weak self] event in
self?.markerEventReceived(event)
}

Expand All @@ -69,6 +68,7 @@ public final class EventGenerator {

try self.init(window: window)
self.isUsingCustomWindow = true
self.mainView = viewController.view
}

/// Initialize an event generator for a specified UIView.
Expand All @@ -78,9 +78,11 @@ public final class EventGenerator {
/// - parameter view: The view to receive events.
public convenience init(view: UIView) throws {
try self.init(viewController: UIViewController(wrapping: view))
self.mainView = view
}

deinit {
UIApplication.unregisterForHIDEvents(ObjectIdentifier(self))
if self.isUsingCustomWindow {
self.window.isHidden = true
self.window.rootViewController = nil
Expand Down
18 changes: 9 additions & 9 deletions Sources/Hammer/EventGenerator/HammerLocatable.swift
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
import UIKit

public protocol HammerLocatable {
func hitPoint(for eventGenerator: EventGenerator) throws -> CGPoint
func windowHitPoint(for eventGenerator: EventGenerator) throws -> CGPoint
}

extension CGPoint: HammerLocatable {
public func hitPoint(for eventGenerator: EventGenerator) throws -> CGPoint {
public func windowHitPoint(for eventGenerator: EventGenerator) throws -> CGPoint {
return self
}
}

extension CGRect: HammerLocatable {
public func hitPoint(for eventGenerator: EventGenerator) throws -> CGPoint {
public func windowHitPoint(for eventGenerator: EventGenerator) throws -> CGPoint {
return self.center
}
}

extension UIView: HammerLocatable {
public func hitPoint(for eventGenerator: EventGenerator) throws -> CGPoint {
return try eventGenerator.hitPoint(forView: self)
public func windowHitPoint(for eventGenerator: EventGenerator) throws -> CGPoint {
return try eventGenerator.windowHitPoint(forView: self)
}
}

extension UIViewController: HammerLocatable {
public func hitPoint(for eventGenerator: EventGenerator) throws -> CGPoint {
return try self.view.hitPoint(for: eventGenerator)
public func windowHitPoint(for eventGenerator: EventGenerator) throws -> CGPoint {
return try self.view.windowHitPoint(for: eventGenerator)
}
}

extension String: HammerLocatable {
public func hitPoint(for eventGenerator: EventGenerator) throws -> CGPoint {
return try eventGenerator.viewWithIdentifier(self).hitPoint(for: eventGenerator)
public func windowHitPoint(for eventGenerator: EventGenerator) throws -> CGPoint {
return try eventGenerator.viewWithIdentifier(self).windowHitPoint(for: eventGenerator)
}
}
Loading