【发布时间】:2021-10-07 05:19:12
【问题描述】:
我目前遇到的问题是无法始终正确识别触摸, 我的目标是有3个手势,这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,而是使用闭包作为回调。
-
我已经简化了代码,现在应该可以编译了