title: Processing touches on moving/ animating UiViews
body: <p>I currently have the problem that touches are not always identified correctly,
  My goal is to have 3 gestures,The 3 gestures are</p>
  <ol>
  <li>A user can tap on a view and the tap gets recognised,</li>
  <li>A user can double tap on a view and the double tap is recognised,</li>
  <li>A user can move their finger on the screen and if a view is below it
  a tab is recognised.</li>
  </ol>
  <p>However I have multiple views all animating constantly and they may overlap,
  Currently I sort views by size and have the smallest views on top of larger views.
  And I typically get an issue that UIViews are not recognised when tapping on them. In particular double taps, swiping seems to work fine most of the time however the whole experience is very inconsistent.</p>
  <p>The current code I'm using to solve the problem is:</p>
  <pre><code>            class FinderrBoxView: UIView {
  
              private var lastBox: String?
  
              private var throttleDelay = 0.01
  
              private var processQueue = DispatchQueue(label: &quot;com.finderr.FinderrBoxView&quot;)
  
  
              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 &lt; 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 &gt;= minX, centreX &lt;= maxX, centreY &gt;= minY, centreY &lt;= 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]) -&gt; 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 &lt; $1 }
                  return boxCenters.first ?? 0.0
              }
  
              static func sortBoxByeSize(_ items: [FinderrItem]) -&gt; [FinderrItem] {
                  return items.sorted { i, j -&gt; 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 &gt; jC
                  }
              }
  
              static func updateOrCreateNewBox(_ box1: CGRect, _ box2: CGRect) -&gt; Bool {
                  let distance = sqrt(pow(box1.midX - box2.midX, 2) + pow(box1.midY - box2.midY, 2))
                  print(distance)
                  return distance &lt; 50
              }
  
              static func distanceBetweenBoxes(_ box1: CGRect, _ box2: CGRect) -&gt; 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: &quot;box&quot;)
                  addSubview(boxView)
              }
  
              @objc func handleTap(_ sender: UITapGestureRecognizer) {
                  // handling code
              //    NotificationCenter.default.post(name: .didDoubleTapOnObject, object: itemName)
              }
  
              override func touchesBegan(_ touches: Set&lt;UITouch&gt;, with event: UIEvent?) {
                  processTouches(touches, with: event)
              }
  
  
              override func touchesMoved(_ touches: Set&lt;UITouch&gt;, with event: UIEvent?) {
                  processTouches(touches, with: event)
              }
  
              func processTouches(_ touches: Set&lt;UITouch&gt;, 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() -&gt; Bool {
              //    NotificationCenter.default.post(name: .didDoubleTapOnObject, object: itemName)
                  return true
              }
  
              required init?(coder aDecoder: NSCoder) {
                  itemName = &quot;error aDecoder&quot;
                  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) -&gt; Bool {
                      return lhs.box == rhs.box
                  }
              }
  </code></pre>