Touch events are delayed near left screen edge on iOS 9 only. How to fix it?
Asked Answered
T

3

6

I am developing a keybaord extension for iOS. On iOS 9 the keys react imediatelly except for keys along left edge of the keyboard. Those react with around 0.2 second delay. The reason is that the touches are simply delivered with this delay to the UIView that is root view of my keyboard. On iOS 8 there is no such delay.

My guess is that this delay is cause by some logic that is supposed to recognize gesture for opening "running apps screen". That is fine but the delay on a keyboard is unacceptable. Is there any way how to get those events without delay? Perhaps just setting delaysTouchesBegan to false on some UIGestureRecognizer?

Textile answered 9/11, 2015 at 22:34 Comment(4)
Did you find a solution for this? We're experiencing the same issue. Frustrating!Tiatiana
I'd also like to find a solution for this.Astute
Still no solution to this?Motto
@Textile I believe I've suggested a correct solution. Care to mark as solved?)Collywobbles
A
2

This is for anyone using later versions of iOS (this is working on iOS 9 and 10 for me). My issue was caused by the swipe to go back gesture interfering with my touchesBegan method by preventing it from firing on the very left edge of the screen until either the touch was ended, or the system recognised the movement to not be that of the swipe to go back gesture.

In your viewDidLoad function in your controller, simply put:

self.navigationController?.interactivePopGestureRecognizer?.delaysTouchesBegan = false
Ahumada answered 27/8, 2017 at 7:25 Comment(0)
C
2

The official solution since iOS11 is overriding preferredScreenEdgesDeferringSystemGestures of your UIInputViewController.

https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys

However, it doesn't seem to work on iOS 13 at least. As far as I understand, that happens due to preferredScreenEdgesDeferringSystemGestures not working properly when overridden inside UIInputViewController, at least on iOS 13.

When you override this property in a regular view controller, it works as expected:

override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
    return [.left, .bottom, .right]
}

That' not the case for UIInputViewController, though.

UPD: It appears, gesture recognizers will still get .began state update, without the delay. So, instead of following the rather messy solution below, you can add a custom gesture recognizer to handle touch events.

You can quickly test this adding UILongPressGestureRecognizer with minimumPressDuration = 0 to your control view.

Another solution:

My original workaround was calling touch down effects inside hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?, which is called even when the touches are delayed for the view.

You have to ignore the "real" touch down event, when it fires about 0.4s later or simultaneously with touch up inside event. Also, it's probably better to apply this hack only in case the tested point is inside ~20pt lateral margins.

So for example, for a view with equal to screen width, the implementation may look like:

let edgeProtectedZoneWidth: CGFloat = 20

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let result = super.hitTest(point, with: event)

    guard result == self else {
        return result
    }

    if point.x < edgeProtectedZoneWidth || point.x > bounds.width-edgeProtectedZoneWidth
    {
        if !alreadyTriggeredFocus {
            isHighlighted = true
        }
        triggerFocus()
    }

    return result
}

private var alreadyTriggeredFocus: Bool = false

@objc override func triggerFocus() {
    guard !alreadyTriggeredFocus else { return }
    super.triggerFocus()
    alreadyTriggeredFocus = true
}

override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesCancelled(touches, with: event)

    alreadyTriggeredFocus = false
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesEnded(touches, with: event)

    alreadyTriggeredFocus = false
}

...where triggerFocus() is the method you call on touch down event. Alternatively, you may override touchesBegan(_:with:).

Collywobbles answered 2/5, 2020 at 3:56 Comment(1)
perfect your IOS 13 solution did the job for me (on IOS 13)Fabien
M
1

If you have access to the view's window property, you can access these system gesture recognizers and set delaysTouchesBegan to false.

Here's a sample code in swift that does that

if let window = view.window,
   let recognizers = window.gestureRecognizers {
   recognizers.forEach { r in
        // add condition here to only affect recognizers that you need to
        r.delaysTouchesBegan = false
   }
}

Also relevant: UISystemGateGestureRecognizer and delayed taps near bottom of screen

Mayhap answered 5/4, 2017 at 9:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.