UIScrollView delaysContentTouches & touchesShouldBegin

本文深入探讨了UIScrollView如何处理触摸事件,包括默认触摸事件分发机制、delaysContentTouches属性的作用及touchesShouldBegin方法的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

默认情况下的触摸事件分发

当用户手指触摸UIScrollView及其子类时,其属性 isTracking 设置为YES,同时内部会开启一个NSTimer, 在timer的一个极短的时间周期内,如果手指发生了较大距离的移动,UIScrollView接收这个事件开始滚动到相应的位置, isTracking 设置为NO。 如果手指没有发生较大距离的移动,而touch位置正好位于子视图上,并且其子视图接受touch事件,那么就触发子视图的touch事件。

Demo中创建一个tableView,为每一个cell 添加一个button

class ViewController: UIViewController {

    @IBOutlet weak var tableView: YMTableView!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }



}

extension ViewController:UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 30;
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        var cell = tableView.dequeueReusableCell(withIdentifier: "test");
        if (cell == nil) {

            cell = UITableViewCell(style: .default  , reuseIdentifier: "test")
            let button = UIButton(type:.custom)
            button.frame = CGRect(x: 40, y:0, width:100, height:44)
            button.backgroundColor = UIColor.red
            cell?.contentView .addSubview(button)
            button .addTarget(self, action: #selector(self.click), for: .touchUpInside)
        }

        cell?.textLabel?.text = "\(indexPath.row)"

        return cell!
    }

    func click() {
        print("被点击了");
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        print(indexPath.row)
    }

}

extension ViewController:UIScrollViewDelegate {


    func scrollViewDidScroll(_ scrollView: UIScrollView) {

        print("start to scrolling")
    }

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        print("Did end Decelerating")
    }
}

运行代码,正如上面所描述的那样,当手指滑动时,UIScrollView开始滑动,如果点击在button上并且没有移动,那么就会触发button的selector,如果点击在其部分则会调用UITableView的 didSelectRowAtIndex。

delaysContentTouches

var delaysContentTouches: Bool { get set }

If the value of this property is true , the scroll view delays handling the touch-down gesture until it can determine if scrolling is the intent. If the value is false, the scroll view immediately calls touchesShouldBegin(_:with:in:). The default value is true。
如果这个值为YES,scrollView会延迟处理手势事件直到它能决定是否要滚动。如果为No,那么它会直接调用 touchesShouldBegin(_:with:in:)来处理事件。

说白了这个值就是决定什么时候去调用touchesShouldBegin(:with:in:),如果值为YES,那么UIScrollView先去判断touch事件,如果手指有较大范围的移动,就让UIScrollView去滚动,如果没有就调用touchesShouldBegin(:with:in:)去处理, 而如果值为No,UIScrollView不会去判断touch事件,会直接调用touchesShouldBegin(_:with:in:),如果有滚动UIScrollView同时滚动。

新创建一个tableView的子类,YMTableView:

class YMTableView: UITableView {

    override func awakeFromNib() {

        self.delaysContentTouches = true   // 默认值,为了强调

    }

    override func touchesShouldBegin(_ touches: Set<UITouch>,
                            with event: UIEvent?,
                            in view: UIView) -> Bool{
        super.touchesShouldBegin(touches, with: event, in: view)
        print("call touchShould Begin")
        return true
    } 
}

当值为YES时,触摸在按钮上并且滑动tableView 输出如下:
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling

点击按钮时输出:
call touchShould Begin
被点击了

如果值设置为NO, 触摸在按钮上并且滑动,输出如下:

call touchShould Begin
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling
立即调用了touchesShouldBegin,并没有去延迟判断

点击按钮时输出:
call touchShould Begin
被点击了

touchesShouldBegin的返回值

func touchesShouldBegin(_ touches: Set<UITouch>, 
                   with event: UIEvent?, 
                     in view: UIView) -> Bool

Return false if you don’t want the scroll view to send event messages to view. If you want view to receive those messages, return true (the default).
如果返回YES(默认),UIScrollView会把当前事件分发到子view去处理,如果NO就不分发此事件。

如果把上面的返回值改为NO,那么Button的点击和didSelect都不会被触发.

canCancelContentTouches

A Boolean value that controls whether touches in the content view always lead to tracking。

一个BOOL值来控制发生在 content view上的触摸事件能否总是触发追踪(tracking)。如果值为YES,那么istracking 会被设置成YES,NSTimer检测是否有手指移动,如果有就滚动,并且结束 content view 的touch 事件。

新建一个YMButton类,把上面的UIButton改为YMButton

import UIKit

class YMButton: UIButton {



    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("YMButton touch begin");

        super.touchesBegan(touches, with: event);
    }


    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesCancelled(touches, with: event)
        print("YMButton touch touchesCancelled cancelled");

    }

}

YMTable 中添加如下代码:

 override func touchesShouldCancel(in view: UIView) -> Bool {

        super.touchesShouldCancel(in: view)
        print("YMTable touch should cancelled");

        return true

    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesCancelled(touches, with: event)
        print("YMTable touch touchesCancelled cancelled");

    }

点击在button上然后移动手指,输出如下

YMTable touchShould Begin
YMButton touch begin
YMTable touch should cancelled
YMButton touch touchesCancelled cancelled

正如上面的输出,虽然触发了button的触摸事件,但是推算出其是滚动,然后把touch时间取消掉,执行滚动操作。

如果canCancelContentTouches = false,执行同样的操作 输出如下

YMTable touchShould Begin
YMButton touch begin
被点击了
当触摸发后在contet view 也就是button上时,直接执行的button的点击事件,ScrollView 并没有发生滚动.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值