0

I have the following hierarchy, the root view of the controller. Added two subviews, a standard UIView and a UIScrollView. The UIView is on top.

What I want to do is to receive touches on that UIView, meaning touchesBegan, touchesMoved... like this:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    print("Began")
    super.touchesBegan(touches, with: event)
    next?.touchesBegan(touches, with: event)
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    print("Moved")
    super.touchesMoved(touches, with: event)
    next?.touchesMoved(touches, with: event)
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    print("Ended")
    super.touchesEnded(touches, with: event)
    next?.touchesEnded(touches, with: event)
}

But at the same time I want the scroll to work. The UIScrollView that is below, I want that to keep scrolling as usual.

This seems impossible to do. If my view handles touches, then I can't forward them to the UIScrollView. But I want both things: see the raw touches on the top view while the scrollview to work as usual.

How can I accomplish this?

The opposite would also work. Meaning, a scrollview on top that scrolls as usual but I also receive the raw touches (touchesBegan, touchesMoved...) on the view that is underneath.

Vladislav
  • 1,392
  • 1
  • 12
  • 21

1 Answers1

3

Following this answer worked for me, though with some slight changes. Do note that for this solution, the UIScrollView is on top of the UIView. Firstly, you need to create a subclass of UIScrollView and override the touch methods.

protocol CustomScrollViewDelegate {
    func scrollViewTouchBegan(_ touches: Set<UITouch>, with event: UIEvent?)
    func scrollViewTouchMoved(_ touches: Set<UITouch>, with event: UIEvent?)
    func scrollViewTouchEnded(_ touches: Set<UITouch>, with event: UIEvent?)
}

class CustomScrollView: UIScrollView {
    
    var customDelegate: CustomScrollViewDelegate?
    
    override func awakeFromNib() {
        super.awakeFromNib()
        
        for gesture in self.gestureRecognizers ?? [] {
            gesture.cancelsTouchesInView = false
            gesture.delaysTouchesBegan = false
            gesture.delaysTouchesEnded = false
        }
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        customDelegate?.scrollViewTouchBegan(touches, with: event)
        super.touchesBegan(touches, with: event)
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        customDelegate?.scrollViewTouchMoved(touches, with: event)
        super.touchesMoved(touches, with: event)
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        customDelegate?.scrollViewTouchEnded(touches, with: event)
        super.touchesEnded(touches, with: event)
    }
}

Take note of the code in awakeFromNib(). UIScrollView has its own set of gestures. So for each gesture, delaysTouchesBegan and delaysTouchesEnded needs to be false to prevent delays for the touch events.

Finally, just assign the delegate to your ViewController and implement the methods like so.

class ViewController: UIViewController {
    
    @IBOutlet weak var scrollView: CustomScrollView!
    @IBOutlet weak var touchView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        scrollView.customDelegate = self
    }
}

extension ViewController: CustomScrollViewDelegate {
    func scrollViewTouchBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Check if touch location is within the bounds of the UIView
        if let touch = touches.first {
            let position = touch.location(in: view)
            if touchView.bounds.contains(position) {
                print("Began")
            }
        }
    }
    
    func scrollViewTouchMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Check if touch location is within the bounds of the UIView
        if let touch = touches.first {
            let position = touch.location(in: view)
            if touchView.bounds.contains(position) {
                print("Moved")
            }
        }
    }
    
    func scrollViewTouchEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Check if touch location is within the bounds of the UIView
        if let touch = touches.first {
            let position = touch.location(in: view)
            if touchView.bounds.contains(position) {
                print("Ended")
            }
        }
    }
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
yjoon
  • 106
  • 1
  • 5