简体   繁体   中英

Intercepting tap even on the top UIView when multiple UIView are overlapping on top of each other

I have a UIView that fill the entire screen, then I'm adding multiple small circle UIView within that container view, I want those small circle's UIView to be draggable using UIPanGestureRecognizer . But sometimes they happen to be on top of each other making the top UIView not clickable at all, it always select the bottom ones.

In the container UIView I implemented hitTest to be able to select only those child views.

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    for planet in self.myPlanets.values {
        if let presentation = planet.layer.presentation(), presentation.frame.contains(point) {
            return planet
        }
    }
    return nil
}

How can I make the top view receive the click even instead of the bottom view?

I handled draggable views by creating a UIView subclass, adding a UIPanGestureRecognizer and updating based on its inputs.

Using this method, whichever view is on top will receive the touch and you don't have to override hitTest on the superview.

I also added a delegate to update constraints if the view is constrained to the superview. By setting the delegate the UIView or ViewController (whichever is delegate) can update the constraints for the views you want to move.

Here's a simple implementation:

// Delegate protocol for managing constraint updates if needed
protocol DraggableDelegate: class {

    // tells the delegate to move
    func moveByTranslation(_ change: CGPoint)
}


class DraggableView: UIView {

    var dragDelegate: DraggableDelegate?

    init() {
        // frame is set later if needed by creator
        super.init(frame: CGRect.zero)
        configureGestureRecognizers()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // setup UIPanGestureRecognizer
    internal func configureGestureRecognizers() {
        let panGR = UIPanGestureRecognizer.init(target: self, action: #selector(didPan(_:)))
        addGestureRecognizer(panGR)
    }

    @objc func didPan(_ panGR: UIPanGestureRecognizer) {

        // get the translation
        let translation = panGR.translation(in: self).applying(transform)

        if let delegate = dragDelegate {

            // tell delegate to move
            delegate.moveByTranslation(translation)

        } else {

            // move self
            self.center.x += translation.x
            self.center.y += translation.y
        }

        // reset translation
        panGR.setTranslation(CGPoint.zero, in: self)
    }
}

Here's how I implemented the delegate callback in the view controller using this view, since my view used constraints:

/// Moves the tool tray when dragging is triggered via the pan gesture recognizer in the tool tray (an instance of DraggableView).
///
/// - Parameter change: The x/y change provided by the pan gesture recognizer.
func moveByTranslation(_ change: CGPoint) {

    // only interested in the y axis movements for this example
    // update the constraint that moves this view
    let newPos = constraint_tooltray_yAxis.constant + change.y

    // this function limited the movement of the view to keep it within bounds
    updateToolTrayPosition(newPos)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM