美文网首页SwiftiOS && Androidswift 文章收集
iOS事件响应链中hitTest的应用示例

iOS事件响应链中hitTest的应用示例

作者: 船长_ | 来源:发表于2018-07-25 21:27 被阅读137次
// 作用: 去寻找最适合的View
// 什么时候调用: 当一个事件传递给当前View,就会调用.
// 返回值: 返回的是谁,谁就是最适合的View(就会调用最适合的View的touch方法)
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        return super.hitTest(point, with: event)
}

hitTest的底层实现:

  • 1.先看自己是否能接受触摸事件
  • 2.再看触摸点是否在自己身上
  • 3.从后往前遍历子控件,拿到子控件后,再次重复1,2步骤,要把父控件上的坐标点转换为子控件坐标系下的点,再次执行hitTest方法
  • 4.若是最后还没有找到合适的view,那么就return self,自己就是合适的view

备注:当控件接收到触摸事件的时候,不管能不能处理事件,都会调用hitTest方法

应用实例

1.扩大UIButton的响应热区

import UIKit

private var ts_touchAreaEdgeInsets: UIEdgeInsets = .zero

extension UIButton {
    /// Increase your button touch area.
    /// If your button frame is (0,0,40,40). Then call button.ts_touchInsets = UIEdgeInsetsMake(-30, -30, -30, -30), it will Increase the touch area
    public var ts_touchInsets: UIEdgeInsets {
        get {
            if let value = objc_getAssociatedObject(self, &ts_touchAreaEdgeInsets) as? NSValue {
                var edgeInsets: UIEdgeInsets = .zero
                value.getValue(&edgeInsets)
                return edgeInsets
            }else {
                return .zero
            }
        }
        set(newValue) {
            var newValueCopy = newValue
            let objCType = NSValue(uiEdgeInsets: .zero).objCType
            let value = NSValue(&newValueCopy, withObjCType: objCType)
            objc_setAssociatedObject(self, &ts_touchAreaEdgeInsets, value, .OBJC_ASSOCIATION_RETAIN)
        }
    }
    
    open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        if UIEdgeInsetsEqualToEdgeInsets(self.ts_touchInsets, .zero) || !self.isEnabled || self.isHidden {
            return super.point(inside: point, with: event)
        }
        
        let relativeFrame = self.bounds
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.ts_touchInsets)
        
        return hitFrame.contains(point)
    }
}

使用示例

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    areaBtn.ts_touchInsets = .init(top: -30, left: -30, bottom: -30, right: -30)
}

也可以自定义UIButton,在自定义的button里实现

import UIKit
class TouchIncreaseButton: UIButton {
    
    private let btnWidth : CGFloat = 44
    private let btnHeight : CGFloat = 44

    private func hitTestBounds(minimumHitTestWidth minWidth : CGFloat,minimumHitTestHeight minHeight : CGFloat) -> CGRect {
        var hitTestBounds = self.bounds
        if minWidth > bounds.size.width {
            hitTestBounds.size.width = minWidth
            hitTestBounds.origin.x -= (hitTestBounds.size.width - bounds.size.width)/2
        }
        if minHeight > bounds.size.height {
            hitTestBounds.size.height = minHeight
            hitTestBounds.origin.y -= (hitTestBounds.size.height - bounds.size.height)/2
        }
        
        return hitTestBounds
    }
    
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        
        let rect = hitTestBounds(minimumHitTestWidth: btnWidth, minimumHitTestHeight: btnHeight)
        return rect.contains(point)
    }
}

2.子view超出了父viewbounds响应事件

demo.png

重载父viewhitTest(_ point: CGPoint, with event: UIEvent?)方法

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    for subView in self.subviews {
        // 把父类point点转换成子类坐标系下的点
        let convertedPoint = subView.convert(point, from: self)
        
        // 注意点:hitTest方法内部会调用pointInside方法,询问触摸点是否在这个控件上
        // 根据point,找到适合响应事件的这个View
        let hitTestView = subView.hitTest(convertedPoint, with: event)
        if hitTestView != nil {
            return hitTestView
        }
    }
    return nil
}

3.使部分区域失去响应.

tableView.png

场景需求:如图,tableView占整个屏幕,tableView底下是一个半透明的HUD,点击下面没有内容区域,要让HUD去响应事件.

在自定义的tableView中重载hitTest方法

// tableView会拦截底部`backgroundView`事件的响应,
// 实现点击tableViewCell 之外的地方,让tableView底下的backgroundView响应tap事件
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    if let hitView = super.hitTest(point, with: event) , hitView.isKind(of: BTTableCellContentView.self) {
        return hitView
    }
    // 返回nil 那么事件就不由当前控件处理
    return nil;
}

4.让非scrollView区域响应scrollView拖拽事件

scrollView.png

如图,这是一个使用scrollView自定义实现的卡片式轮播器,如何实现拖拽scrollView两边的view区域,和拖拽中间scrollView一样的效果呢?只需要在scrollView的父类重载hitTest方法

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    if self.point(inside: point, with: event) == true {
        return scrollView
    }
    return nil
}

参考:iOS事件响应链中Hit-Test View的应用
ios开发事件处理之 四:hittest方法的底层实现与应用

相关文章

  • iOS事件响应链中hitTest的应用示例

    hitTest的底层实现: 1.先看自己是否能接受触摸事件 2.再看触摸点是否在自己身上 3.从后往前遍历子控件,...

  • 响应链

    iOS事件响应链中Hit-Test View的应用从iOS的事件响应链看TableView为什么不响应touche...

  • iOS随笔 hit-test

    应用接收到touch事件,会按照事件响应链的顺序执行hitTest方法去获取touch对应的视图。 UIAppli...

  • 响应链 和 处理

    响应链 寻找事件的最佳响应视图是通过对视图调用hitTest和pointInside完成的 hitTest的调用顺...

  • iOS事件传递和视图响应

    iOS事件响应机制的事件传递流程 - (UIView *)hitTest:(CGPoint)point withE...

  • view点击事件分发机制

    两步:1: 事件传递链: 从上往下2: 事件响应链: 从下往上 (UIView *)hitTest:(CGPoin...

  • iOS 响应链

    iOS开发 - 事件传递响应链iOS 响应者链,事件的传递事件传递之响应链Cocoa Touch事件处理流程--响...

  • iOS基础06—--事件响应链

    iOS基础06—--事件响应链 移动应用的最大特性就是响应用户交互操作,那么iOS系统是如何去响应一个简单的点击事...

  • 对iOS响应链和事件传递的总结

    简短的描述响应链和事件传递 自底向上查找第一响应者,通过-(UIView*)hittest:touchevent;...

  • 运用Runtime扩大UIButton点击区域

    了解事件响应链的同学应该知道hitTest和point方法,我们先来简单回顾一下 hitTest 内部实现 总结 ...

网友评论

本文标题:iOS事件响应链中hitTest的应用示例

本文链接:https://www.haomeiwen.com/subject/gxsmmftx.html