如何使视图在其父视图范围之外接收触摸事件?

5

我有一个容器,clipToBounds设置为false,并且有一个超出其边界的视图。超出边界的视图无法识别触摸事件。

3个回答

6
只需将此类添加到您的视图中。
class MyView: UIView {

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        for subview in subviews as [UIView] {
            if !subview.isHidden 
               && subview.alpha > 0 
               && subview.isUserInteractionEnabled 
               && subview.point(inside: convert(point, to: subview), with: event) {
                 return true
            }
        }
        return false
    }
}

1
谢谢,但我在寻找不涉及子类化的解决方案,因为我有一个可重用的视图,其中包含越界组件。对于每个想要添加包含越界组件的视图控制器来说,子类化每个视图都是一种错误的方法。 - Adrian
Adrian,你错了。这是正确的方法。在UI方面,你不能简单地进行组合 - 你只能通过子类化来添加所需的概念,这是非常普遍的做法。 - Fattie

0

这里我修复了先前答案的格式和方法。

extension UIView {

    private enum ExtendedTouchAssociatedKey {
        static var outsideOfBounds = "viewExtensionAllowTouchesOutsideOfBounds"
    }

    /// This propery is set on the parent of the view that first clips the content you want to be touchable outside of the bounds
    var allowTouchesOfViewsOutsideBounds: Bool {
        get {
            objc_getAssociatedObject(self, &ExtendedTouchAssociatedKey.outsideOfBounds) as? Bool ?? false
        }
        set {
            UIView.swizzlePointInsideIfNeeded()
            subviews.forEach {
                $0.allowTouchesOfViewsOutsideBounds = newValue
            }
            objc_setAssociatedObject(self, &ExtendedTouchAssociatedKey.outsideOfBounds, newValue, .OBJC_ASSOCIATION_RETAIN)
        }
    }

    func hasSubview(at point: CGPoint) -> Bool {
        if subviews.isEmpty {
            return bounds.contains(point)
        }
        return subviews.contains { subview in
            let converted = self.convert(point, to: subview)
            return subview.hasSubview(at: converted)
        }
    }

    private static var swizzledMethods = false

    @objc
    func _point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        if allowTouchesOfViewsOutsideBounds {
            return originalPoint(inside: point, with: event) || hasSubview(at: point)
        }
        return originalPoint(inside: point, with: event)
    }

    @objc
    func originalPoint(inside point: CGPoint, with event: UIEvent?) -> Bool {
        fatalError("this should be swizzled")
    }

    private static func swizzlePointInsideIfNeeded() {
        if swizzledMethods {
            return
        }
        
        swizzledMethods = true
        
        let aClass = UIView.self
        
        let originalSelector = class_getInstanceMethod(
            aClass,
            #selector(point(inside:with:))
        )!
        
        let swizzledSelector = class_getInstanceMethod(
            aClass,
            #selector(_point(inside:with:))
        )!
        
        let backupSelector = class_getInstanceMethod(
            aClass,
            #selector(originalPoint(inside:with:))
        )!
        
        let nativeImplementation = method_getImplementation(originalSelector)
        let customImplementation = method_getImplementation(swizzledSelector)
        
        method_setImplementation(originalSelector, customImplementation)
        method_setImplementation(backupSelector, nativeImplementation)
    }
}

-1

这里有一个扩展,可以让您允许在容器中触摸被裁剪的子视图。将此文件粘贴到您的项目中,并设置containerView.allowTouchesOfViewsOutsideBounds = true

public extension UIView {

    private struct ExtendedTouchAssociatedKey {
        static var outsideOfBounds = "viewExtensionAllowTouchesOutsideOfBounds"
    }

    /// This propery is set on the parent of the view that first clips the content you want to be touchable
    /// outside of the bounds
    var allowTouchesOfViewsOutsideBounds:Bool {
        get {
            return objc_getAssociatedObject(self, &ExtendedTouchAssociatedKey.outsideOfBounds) as? Bool ?? false
        }
        set {
            UIView.swizzlePointInsideIfNeeded()
            subviews.forEach({$0.allowTouchesOfViewsOutsideBounds = newValue})
            objc_setAssociatedObject(self, &ExtendedTouchAssociatedKey.outsideOfBounds, newValue, .OBJC_ASSOCIATION_RETAIN)
        }
    }

    func hasSubview(at point:CGPoint) -> Bool {

        if subviews.count == 0 {
            return self.bounds.contains(point)
        }
        return subviews.contains(where: { (subview) -> Bool in
            let converted = self.convert(point, to: subview)
            return subview.hasSubview(at: converted)
        })

    }

    static private var swizzledMethods:Bool = false

    @objc func _point(inside point: CGPoint, with event: UIEvent?) -> Bool {

        if allowTouchesOfViewsOutsideBounds {
            return  _point(inside:point,with:event) || hasSubview(at: point)
        }
        return _point(inside:point,with:event)
    }

    static private func swizzlePointInsideIfNeeded() {
        if swizzledMethods {
            return
        }
        swizzledMethods = true
        let aClass: AnyClass! = UIView.self
        let originalSelector = #selector(point(inside:with:))
        let swizzledSelector = #selector(_point(inside:with:))
        swizzle(forClass: aClass, originalSelector: originalSelector, swizzledSelector: swizzledSelector)
    }
}

1
swizzle(forClass: aClass, originalSelector: originalSelector, swizzledSelector: swizzledSelector) 这行代码不起作用。 - Kishan Lal
无法编译。什么是“swizzle”? - Jameson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接