只在遮罩视图内识别触摸事件

4

我正在通过模板创建这个结构:

enter image description here

每个六边形都应该可点击。这是我使用的代码:
    // To create one masked hexagun

    let hex = UIImage(named: "hexum")
    let mask = CALayer()
    mask.contents = hexum!.CGImage
    mask.frame = CGRectMake(0, 0, hex!.size.width, hex!.size.height)

    let img = UIImageView(image: UIImage(named: "img"))
    img.layer.mask = mask
    img.layer.masksToBounds = true

    // Gesture Recognizer

    let singleTap = UITapGestureRecognizer(target: self, action: "tapDetected")
    singleTap.numberOfTapsRequired = 1
    img.addGestureRecognizer(singleTap)
    img.userInteractionEnabled = true

    func tapDetected() {
        print("Clicked!")
    }

问题在于点击区域比遮罩大,这会导致区域重叠的不便。就像这样:

Not what I want!

黄色边框显示可点击区域(实际上不可见)

我是一个初学者,这可能是一个琐碎的问题,但你能帮我解决吗?谢谢。


2
请查看类似的问题:https://dev59.com/0mYr5IYBdhLWcg3w7eTb - Ty Lertwichaiworawit
@tnylee 谢谢,我会看一下! - Damasio
5个回答

4
如果您想完美地完成此操作,请使用UIGestureRecognizerDelegate方法gestureRecognizer(gesture, shouldReceiveTouch: touch) -> Bool。您需要将给定的手势识别器映射到特定的六边形,然后对该六边形的图像进行像素级别的精确命中测试。通过将蒙版图像渲染到图形上下文中并找到与触摸位置相对应的点的像素来实现后一部分。
但是,这可能有些过度了。您可以将问题简化为将每个形状视为圆而不是六边形进行命中测试。圆形大致逼近六边形,因此对于用户来说几乎具有相同的效果,并避免了混乱的像素级别的alpha等值。触摸输入的不准确性将掩盖不准确的区域。
另一个选择是重新设计基于CAShapeLayer掩码的视图。 CAShapeLayer包括一个path属性。UIKit中的Bezier路径包含其自己的路径包含点方法的卷曲版本,因此您只需为此目的使用它即可。

2

您可以采用UIGestureRecognizerDelegate协议,并实现gestureRecognizer(_:shouldReceiveTouch:)方法,进一步限制手势是否应该触发。@tnylee建议的链接是一个好的起点,以了解如何进行此类命中测试。


1

@Benjamin Mayo提供了很好的解决方案。最终我选择了最简单但高效的方法:将每个形状视为一个圆进行命中测试。

我将这段代码分享出来,希望能帮助到其他人:

class hexum: UIImageView {

    var maskFrame: CGRect?

    convenience init(mask: String, inside: String) {

    // Mask things:

        let masked = CALayer()
        let img = UIImage(named: mask)
        masked.contents = img?.CGImage
        masked.frame = CGRectMake(x, y, img!.size.width, img!.size.height)

        self.init(image: UIImage(named: inside))
        self.layer.mask = masked
        self.layer.masksToBounds = true

        maskFrame = masked.frame

    }

    // The touch event things
    // Here, I got help from @Matt in (http://stackoverflow.com/a/21081518/3462518):

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        let p = UIBezierPath(ovalInRect: maskFrame!)
        return p.containsPoint(point)
    }
}

    let firstOne = hexum(mask: "img1", inside: "img2")
    let tap = UITapGestureRecognizer(target: self, action: "clicked")
    tap.numberOfTapsRequired = 1

    firstOne.userInteractionEnabled = true
    firstOne.addGestureRecognizer(tap)

    func clicked() {
    ...
    }

结果:

enter image description here


1

这里是一个Swift 3的六边形图像视图,只能在六边形内部进行点击:

首先创建一个UIBezier路径:

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

var sideLength: CGFloat = 100 {
    didSet {
        redrawPath()
    }
}

override init() {
    super.init()
    redrawPath()
}

private func redrawPath() {
    removeAllPoints()

    let yDrop = sideLength / 2

    move(to: CGPoint(x: sideLength, y: 0))

    addLine(to: CGPoint(x: sideLength * 2, y: yDrop))
    addLine(to: CGPoint(x: sideLength * 2, y: yDrop + sideLength))

    addLine(to: CGPoint(x: sideLength, y: (yDrop * 2) + sideLength))
    addLine(to: CGPoint(x: 0, y: yDrop + sideLength))

    addLine(to: CGPoint(x: 0, y: yDrop ))
    //addLine(to: CGPoint(x: sideLength, y: 0))
    close()
}

}

然后创建一个六边形的UIImageView:
class HexagonImageView: UIImageView {
let hexagonPath = HexagonPath()

var sideLength: CGFloat = 100 {
    didSet {
        hexagonPath.sideLength = sideLength
        initilize()
    }
}

init() {
    super.init(frame: CGRect())
    initilize()
}

override init(frame: CGRect) {
    super.init(frame: frame)
    initilize()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    initilize()
}

private func initilize() {
    self.frame.size.width = sideLength * 2
    self.frame.size.height = sideLength * 2

    contentMode = .scaleAspectFill
    mask(withPath: hexagonPath)
}

// MAKE THE TAP-HIT POINT JUST THE INNER PATH
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    return hexagonPath.contains(point)
}

}

使用此扩展程序:

extension UIView {
func mask(withRect rect: CGRect, inverse: Bool = false) {
    let path = UIBezierPath(rect: rect)
    let maskLayer = CAShapeLayer()

    if inverse {
        path.append(UIBezierPath(rect: self.bounds))
        maskLayer.fillRule = kCAFillRuleEvenOdd
    }

    maskLayer.path = path.cgPath

    self.layer.mask = maskLayer
}

func mask(withPath path: UIBezierPath, inverse: Bool = false) {
    let path = path
    let maskLayer = CAShapeLayer()

    if inverse {
        path.append(UIBezierPath(rect: self.bounds))
        maskLayer.fillRule = kCAFillRuleEvenOdd
    }

    maskLayer.path = path.cgPath

    self.layer.mask = maskLayer
}

}

最后,在 ViewController 的 ViewDidLoad 中可以这样使用它:

override func viewDidLoad() {
    super.viewDidLoad()

   let hexImageView = HexagonImageView()
    hexImageView.image = UIImage(named: "hotcube")
    hexImageView.sideLength = 100

    let tap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
    tap.numberOfTapsRequired = 1
    hexImageView.isUserInteractionEnabled = true
    hexImageView.addGestureRecognizer(tap)

    view.addSubview(hexImageView)
}

func imageTapped() {
    print("tapped")
}

0

我不确定这是最简单和正确的方法,但我会检查用户点击的位置并覆盖touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)


touchesBegan(_:withEvent:) 函数用于在 UIResponder 子类中实现手势处理。看起来 OP 正在使用 UIGestureRecognizer - Charles A.

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