Force Touch/ 3D Touch in XCUITest
Asked Answered
B

2

8

I am trying to add a force touch automation for an iOS App. I looked into the Apple docs for the same problem but cannot find anything useful. We can do the force touch from the assistive touch but I am looking for something as simple as the tap() action. Is there anything that we can use for forceTouch?

Any help will be greatly appreciated. Thanks!

Brython answered 21/3, 2018 at 18:29 Comment(2)
developer.apple.com/library/content/documentation/…Palpitant
@slickdaddy I have implemented it in the development code. I want to run UITest Automation using xctest framework.Brython
D
4

The raw force value associated with a touch is available in the force property of the UITouch object. You can compare that value against the value in the maximumPossibleForce property to determine the relative amount of force.

//Without this import line, you'll get compiler errors when implementing your touch methods since they aren't part of the UIGestureRecognizer superclass
//Without this import line, you'll get compiler errors when implementing your touch methods since they aren't part of the UIGestureRecognizer superclass
import UIKit.UIGestureRecognizerSubclass

//Since 3D Touch isn't available before iOS 9, we can use the availability APIs to ensure no one uses this class for earlier versions of the OS.
@available(iOS 9.0, *)
public class ForceTouchGestureRecognizer: UIGestureRecognizer {
  //Because we don't know what the maximum force will always be for a UITouch, the force property here will be normalized to a value between 0.0 and 1.0.
  public private(set) var force: CGFloat = 0.0
  public var maximumForce: CGFloat = 4.0

  convenience init() {
    self.init(target: nil, action: nil)
  }

  //We override the initializer because UIGestureRecognizer's cancelsTouchesInView property is true by default. If you were to, say, add this recognizer to a tableView's cell, it would prevent didSelectRowAtIndexPath from getting called. Thanks for finding this bug, Jordan Hipwell!
  public override init(target: Any?, action: Selector?) {
    super.init(target: target, action: action)
    cancelsTouchesInView = false
  }

  public override func touchesBegan(_ touches: Set, with event: UIEvent) {
    super.touchesBegan(touches, with: event)
    normalizeForceAndFireEvent(.began, touches: touches)
  }

  public override func touchesMoved(_ touches: Set, with event: UIEvent) {
    super.touchesMoved(touches, with: event)
    normalizeForceAndFireEvent(.changed, touches: touches)
  }


  public override func touchesEnded(_ touches: Set, with event: UIEvent) {
    super.touchesEnded(touches, with: event)
    normalizeForceAndFireEvent(.ended, touches: touches)
  }

  public override func touchesCancelled(_ touches: Set, with event: UIEvent) {
    super.touchesCancelled(touches, with: event)
    normalizeForceAndFireEvent(.cancelled, touches: touches)
  }

  private func normalizeForceAndFireEvent(_ state: UIGestureRecognizerState, touches: Set) {
    //Putting a guard statement here to make sure we don't fire off our target's selector event if a touch doesn't exist to begin with.
    guard let firstTouch = touches.first else { return }

    //Just in case the developer set a maximumForce that is higher than the touch's maximumPossibleForce, I'm setting the maximumForce to the lower of the two values.
    maximumForce = min(firstTouch.maximumPossibleForce, maximumForce)

    //Now that I have a proper maximumForce, I'm going to use that and normalize it so the developer can use a value between 0.0 and 1.0.
    force = firstTouch.force / maximumForce

    //Our properties are now ready for inspection by the developer. By setting the UIGestureRecognizer's state property, the system will automatically send the target the selector message that this recognizer was initialized with.
    self.state = state
  }

  //This function is called automatically by UIGestureRecognizer when our state is set to .Ended. We want to use this function to reset our internal state.
  public override func reset() {
    super.reset()
    force = 0.0
  }
}
Discursive answered 17/4, 2018 at 22:17 Comment(0)
T
0

how about using press(forDuration)?

press(forDuration:)

Sends a long press gesture to a hittable point computed for the element, holding for the specified duration.

https://developer.apple.com/documentation/xctest/xcuielement/1618663-press

Tentation answered 10/4, 2018 at 22:7 Comment(3)
I have tried this out. But it didn't work. It acts like a normal tap on the element.Brython
Is your gesture recognizer configured to accept a force touch? To trigger a force touch on a text field I usually use a 2 second duration.Tentation
Yes it is configured. I am able to force touch in a simulator by using my mac's trackpad. While I am running UIAutomation tests, I want to perform force touch. I tried press(forDuration) but didn't work.Brython

© 2022 - 2024 — McMap. All rights reserved.