【问题标题】:Processing touches on moving/ animating UiViews处理涉及移动/动画 UiViews
【发布时间】:2021-10-07 05:19:12
【问题描述】:

我目前遇到的问题是无法始终正确识别触摸, 我的目标是有3个手势,这3个手势是

  1. 用户可以点击视图并且点击被识别,
  2. 用户可以双击视图,并且双击被识别,
  3. 用户可以在屏幕上移动他们的手指,如果屏幕下方有视图 一个标签被识别出来。

但是,我有多个视图都在不断地制作动画,并且它们可能会重叠, 目前,我按大小对视图进行排序,并在较大的视图之上拥有最小的视图。 而且我通常会遇到一个问题,即在点击 UIViews 时无法识别它们。特别是双击,滑动似乎在大多数情况下都可以正常工作,但整个体验非常不一致。

我用来解决问题的当前代码是:

            class FinderrBoxView: UIView {

            private var lastBox: String?

            private var throttleDelay = 0.01

            private var processQueue = DispatchQueue(label: "com.finderr.FinderrBoxView")


            public var predictedObjects: [FinderrItem] = [] {
                didSet {
                    predictedObjects.forEach { self.checkIfBoxIntersectCentre(prediction: $0) }
                    drawBoxs(with: FinderrBoxView.sortBoxByeSize(predictedObjects))
                    setNeedsDisplay()
                }
            }

            func drawBoxs(with predictions: [FinderrItem]) {
                var newBoxes = Set(predictions)
                var views = subviews.compactMap { $0 as? BoxView }
                views = views.filter { view in

                    guard let closest = newBoxes.sorted(by: { x, y in
                        let xd = FinderrBoxView.distanceBetweenBoxes(view.frame, x.box)
                        let yd = FinderrBoxView.distanceBetweenBoxes(view.frame, y.box)

                        return xd < yd
                    }).first else { return false }

                    if FinderrBoxView.updateOrCreateNewBox(view.frame, closest.box)
                    {
                        newBoxes.remove(closest)
                        UIView.animate(withDuration: self.throttleDelay, delay: 0, options: .curveLinear, animations: {
                            view.frame = closest.box
                        }, completion: nil)

                        return false
                    } else {
                        return true
                    }
                }

                views.forEach { $0.removeFromSuperview() }
                newBoxes.forEach { self.createLabelAndBox(prediction: $0) }
                accessibilityElements = subviews
            }

            func update(with predictions: [FinderrItem]) {
                var newBoxes = Set(predictions)
                var viewsToRemove = [UIView]()
                for view in subviews {
                    var shouldRemoveView = true
                    for box in predictions {
                        if FinderrBoxView.updateOrCreateNewBox(view.frame, box.box)
                        {
                            UIView.animate(withDuration: throttleDelay, delay: 0, options: .curveLinear, animations: {
                                view.frame = box.box
                            }, completion: nil)
                            shouldRemoveView = false
                            newBoxes.remove(box)
                        }
                    }
                    if shouldRemoveView {
                        viewsToRemove.append(view)
                    }
                }
                viewsToRemove.forEach { $0.removeFromSuperview() }

                for prediction in newBoxes {
                    createLabelAndBox(prediction: prediction)
                }
                accessibilityElements = subviews
            }

            func checkIfBoxIntersectCentre(prediction: FinderrItem) {
                let centreX = center.x
                let centreY = center.y
                let maxX = prediction.box.maxX
                let minX = prediction.box.midX
                let maxY = prediction.box.maxY
                let minY = prediction.box.minY
                if centreX >= minX, centreX <= maxX, centreY >= minY, centreY <= maxY {
            //        NotificationCenter.default.post(name: .centreIntersectsWithBox, object: prediction.name)
                }
            }

            func removeAllSubviews() {
                UIView.animate(withDuration: throttleDelay, delay: 0, options: .curveLinear) {
                    for i in self.subviews {
                        i.frame = CGRect(x: i.frame.midX, y: i.frame.midY, width: 0, height: 0)
                    }
                } completion: { _ in
                    self.subviews.forEach { $0.removeFromSuperview() }
                }
            }

            static func getDistanceFromCloseBbox(touchAt p1: CGPoint, items: [FinderrItem]) -> Float {
                var boxCenters = [Float]()
                for i in items {
                    let distance = Float(sqrt(pow(i.box.midX - p1.x, 2) + pow(i.box.midY - p1.y, 2)))
                    boxCenters.append(distance)
                }
                boxCenters = boxCenters.sorted { $0 < $1 }
                return boxCenters.first ?? 0.0
            }

            static func sortBoxByeSize(_ items: [FinderrItem]) -> [FinderrItem] {
                return items.sorted { i, j -> Bool in
                    let iC = sqrt(pow(i.box.height, 2) + pow(i.box.width, 2))
                    let jC = sqrt(pow(j.box.height, 2) + pow(j.box.width, 2))
                    return iC > jC
                }
            }

            static func updateOrCreateNewBox(_ box1: CGRect, _ box2: CGRect) -> Bool {
                let distance = sqrt(pow(box1.midX - box2.midX, 2) + pow(box1.midY - box2.midY, 2))
                print(distance)
                return distance < 50
            }

            static func distanceBetweenBoxes(_ box1: CGRect, _ box2: CGRect) -> Float {
                return Float(sqrt(pow(box1.midX - box2.midX, 2) + pow(box1.midY - box2.midY, 2)))
            }

            func createLabelAndBox(prediction: FinderrItem) {
                let bgRect = prediction.box
                let boxView = BoxView(frame: bgRect ,itemName: "box")
                addSubview(boxView)
            }

            @objc func handleTap(_ sender: UITapGestureRecognizer) {
                // handling code
            //    NotificationCenter.default.post(name: .didDoubleTapOnObject, object: itemName)
            }

            override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
                processTouches(touches, with: event)
            }


            override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
                processTouches(touches, with: event)
            }

            func processTouches(_ touches: Set<UITouch>, with event: UIEvent?) {
                if UIAccessibility.isVoiceOverRunning { return }
                if predictedObjects.count == 0 { return }
                if let touch = touches.first {
                    let hitView = hitTest(touch.location(in: self), with: event)

                    if hitView?.accessibilityLabel == lastBox { return }
                    lastBox = hitView?.accessibilityLabel
                    
                    guard let boxView = hitView as? BoxView else {
                        return
                    }

                    UIView.animate(withDuration: 0.1, delay: 0, options: .curveLinear) {
                        boxView.backgroundColor = UIColor.yellow.withAlphaComponent(0.5)

                    } completion: { _ in
                        UIView.animate(withDuration: 0.1, delay: 0, options: .curveLinear, animations: {
                            boxView.backgroundColor = UIColor.clear
                        }, completion: nil)
                    }

                }
            }

            }

            class BoxView: UIView {
                let id = UUID()
                var itemName: String

                init(frame: CGRect, itemName: String) {
                self.itemName = itemName
                super.init(frame: frame)
                if !UIAccessibility.isVoiceOverRunning {
                    let singleDoubleTapRecognizer = SingleDoubleTapGestureRecognizer(
                        target: self,
                        singleAction: #selector(handleDoubleTapGesture),
                        doubleAction: #selector(handleDoubleTapGesture)
                    )
                    addGestureRecognizer(singleDoubleTapRecognizer)
                }

                }

            @objc func navigateAction() -> Bool {
            //    NotificationCenter.default.post(name: .didDoubleTapOnObject, object: itemName)
                return true
            }

            required init?(coder aDecoder: NSCoder) {
                itemName = "error aDecoder"
                super.init(coder: aDecoder)
            }

            @objc func handleDoubleTapGesture(_: UITapGestureRecognizer) {
                // handling code
            //    NotificationCenter.default.post(name: .didDoubleTapOnObject, object: itemName)
            }

            }

            public class SingleDoubleTapGestureRecognizer: UITapGestureRecognizer {
                var targetDelegate: SingleDoubleTapGestureRecognizerDelegate
                public var timeout: TimeInterval = 0.5 {
                    didSet {
                        targetDelegate.timeout = timeout
                    }
                }

                public init(target: AnyObject, singleAction: Selector, doubleAction: Selector) {
                    targetDelegate = SingleDoubleTapGestureRecognizerDelegate(target: target, singleAction: singleAction, doubleAction: doubleAction)
                    super.init(target: targetDelegate, action: #selector(targetDelegate.recognizerAction(recognizer:)))
                }
            }

            class SingleDoubleTapGestureRecognizerDelegate: NSObject {
                weak var target: AnyObject?
                var singleAction: Selector
                var doubleAction: Selector
                var timeout: TimeInterval = 0.5
                var tapCount = 0
                var workItem: DispatchWorkItem?

                init(target: AnyObject, singleAction: Selector, doubleAction: Selector) {
                    self.target = target
                    self.singleAction = singleAction
                    self.doubleAction = doubleAction
                }

                @objc func recognizerAction(recognizer: UITapGestureRecognizer) {
                    tapCount += 1
                    if tapCount == 1 {
                        workItem = DispatchWorkItem { [weak self] in
                            guard let weakSelf = self else { return }
                            weakSelf.target?.performSelector(onMainThread: weakSelf.singleAction, with: recognizer, waitUntilDone: false)
                            weakSelf.tapCount = 0
                        }
                        DispatchQueue.main.asyncAfter(
                            deadline: .now() + timeout,
                            execute: workItem!
                        )
                    } else {
                        workItem?.cancel()
                        DispatchQueue.main.async { [weak self] in
                            guard let weakSelf = self else { return }
                            weakSelf.target?.performSelector(onMainThread: weakSelf.doubleAction, with: recognizer, waitUntilDone: false)
                            weakSelf.tapCount = 0
                        }
                    }
                }
            }

            class FinderrItem: Equatable, Hashable {
                var box: CGRect
                init(
                     box: CGRect)
                {
                    self.box = box
                }

                func hash(into hasher: inout Hasher) {
                    hasher.combine(Float(box.origin.x))
                    hasher.combine(Float(box.origin.y))
                    hasher.combine(Float(box.width))
                    hasher.combine(Float(box.height))
                    hasher.combine(Float(box.minX))
                    hasher.combine(Float(box.maxY))
                }

                static func == (lhs: FinderrItem, rhs: FinderrItem) -> Bool {
                    return lhs.box == rhs.box
                }
            }

【问题讨论】:

  • 我建议简化您的代码,以便我们可以对其进行编译。这都是什么:SingleDoubleTapGestureRecognizer、FinderrItem、UserSettings。另外,我建议不要使用 NotificationCenter,而是使用闭包作为回调。
  • 我已经简化了代码,现在应该可以编译了

标签: swift uikit


【解决方案1】:

默认情况下,视图对象会在动画“进行中”时阻止用户交互。您需要使用“长格式”动画方法之一,并传入选项 .allowUserInteraction。像这样的:

UIView.animate(withDuration: 0.5,
  delay: 0.0,
  options: .allowUserInteraction,
  animations: {
    myView.alpha = 0.5
})

【讨论】:

  • 我尝试使用手势识别它,但它们似乎在动画视图上效果不佳。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多