iOS 键盘扩展中屏幕左侧的动画延迟

Animation delay on left side of screen in iOS keyboard extension

我正在为 iOS 开发键盘扩展。但是,我遇到了一些奇怪的问题,动画/层没有立即出现在屏幕的最左侧。当用户按下某个键时,我使用图层/动画来显示 "tool tip"。对于除 A 和 Q 之外的所有键,工具提示都会立即显示,但是对于这两个键,在图层和动画出现之前似乎有轻微的延迟。这只发生在触地时,如果我滑入 Q 或 A 命中区域,工具提示会立即呈现。我的调试显示代码对所有键的执行完全相同,但是对于这两个键它没有立即生效。

关于屏幕左边缘是否有任何可能导致此行为的特殊情况,您有什么想法吗?还是我做了一些愚蠢的事情,这可能是造成这种情况的原因?

这是触发工具提示呈现的触摸处理代码的一部分:

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    if(!shouldIgnoreTouches()) {
        for touch in touches {
            let location = (touch ).locationInView(self.inputView)

            // pass coordinates to offset service to find candidate keys
            let keyArray = keyOffsetService.getKeys(_keyboardLayout!, location: location)
            let primaryKey = keyArray[0]

            if primaryKey.alphaNumericKey != nil {
                let layers = findLayers(touch )

                if layers.keyLayer != nil {
                    graphicsService.animateKeyDown(layers.keyLayer as! CATextLayer, shieldLayer: layers.shieldLayer)
                    _shieldsUp.append((textLayer:layers.keyLayer, shieldLayer:layers.shieldLayer))
                }
            }
         }
    }
}

动画代码:

func animateKeyDown(layer:CATextLayer, shieldLayer:CALayer?) {
    if let sLayer = shieldLayer {
        keyDownShields(layer, shieldLayer: sLayer)
        CATransaction.begin()
        CATransaction.setDisableActions(true)

        let fontSizeAnim = CABasicAnimation(keyPath: "fontSize")
        fontSizeAnim.removedOnCompletion = true
        fontSizeAnim.fromValue = layer.fontSize
        fontSizeAnim.toValue = layer.fontSize * 0.9
        layer.fontSize = layer.fontSize * 0.9

        let animation  = CABasicAnimation(keyPath: "opacity")
        animation.removedOnCompletion = true
        animation.fromValue = layer.opacity
        animation.toValue = 0.3
        layer.opacity = 0.3

        let animGroup = CAAnimationGroup()
        animGroup.animations = [fontSizeAnim, animation]
        animGroup.duration = 0.01
        layer.addAnimation(animGroup, forKey: "down")

        CATransaction.commit()
    }
}

取消隐藏工具提示层:

private func keyDownShields(layer:CATextLayer, shieldLayer:CALayer) {
    shieldLayer.hidden = false
    shieldLayer.setValue(true, forKey: "isUp")
    shieldLayer.zPosition = 1
    shieldLayer.removeAllAnimations()
    layer.setValue(true, forKey: "isUp")
}

这是由 iOS9 中的一项功能引起的,该功能允许用户在向右滑动的同时用力按下屏幕的左边缘来切换应用程序。

您可以通过禁用 3D 触摸来关闭此功能,但这不是解决方案。

我不知道有任何 API 允许您覆盖此行为。

官方解决方案覆盖了您 UIInputViewControllerpreferredScreenEdgesDeferringSystemGestures

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

但是,它似乎至少在 iOS 13 上不起作用。据我了解,发生这种情况是因为 preferredScreenEdgesDeferringSystemGesturesUIInputViewController 内被覆盖时无法正常工作,至少在 iOS 13.

当您在常规视图控制器中覆盖此 属性 时,它会按预期工作:

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

不过 UIInputViewController 并非如此。

UPD: 看起来,手势识别器仍然会得到 .began 状态更新,没有延迟。因此,您可以添加一个自定义手势识别器来处理触摸事件,而不是遵循下面相当混乱的解决方案。

您可以快速测试将 UILongPressGestureRecognizerminimumPressDuration = 0 添加到您的控制视图。

另一种解决方案:

我最初的解决方法是在 hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? 中调用 touch down 效果,即使在视图的触摸被延迟时也会调用它。

您必须忽略 "real" 触地事件,当它在大约 0.4 秒后触发或与 touch up inside 事件同时触发时。此外,最好仅在测试点位于约 20pt 横向边距内时才应用此 hack。

因此,例如,对于与屏幕宽度相等的视图,实现可能如下所示:

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
}

...其中 triggerFocus() 是您在 touch down 事件中调用的方法。或者,您可以覆盖 touchesBegan(_:with:)