【问题标题】:Recognizing touch events only inside the masked view仅在蒙版视图内识别触摸事件
【发布时间】:2015-10-23 18:50:33
【问题描述】:

我正在通过掩码创建这个结构化的:

每个六边形都应该是可点击的。这是我使用的代码:

    // 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!")
    }

问题是点击区域大于遮罩,会造成区域相互重叠的不便。像这样的:

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

我是初学者,可能是个小问题,但你能帮我解决吗?谢谢。

【问题讨论】:

标签: ios swift quartz-core


【解决方案1】:

如果您想完美地做到这一点,请使用UIGestureRecognizerDelegate 方法gestureRecognizer(gesture, shouldReceiveTouch: touch) -> Bool。您需要将给定的手势识别器映射到特定的六边形,然后对该六边形的图像进行像素精确的命中测试。后一部分是通过将遮罩图像渲染到图形上下文并在与触摸位置对应的点处找到像素来实现的。

但是,这可能是矫枉过正。您可以通过将每个形状作为圆形而不是六边形进行命中测试来简化问题。圆形大致近似于六边形,因此它对用户的工作方式几乎相同,并且避免了混乱的像素级 alpha 相等性。触摸输入的不准确会掩盖不准确的区域。

另一种选择是根据CAShapeLayer 掩码修改您的视图。 CAShapeLayer 包括 path 属性。 UIKit 中的 Bezier 路径包含它们自己的滚动版本的 path-contains-point 方法,因此您可以将其用于此目的。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      @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() {
          ...
          }
      

      结果:

      【讨论】:

        【解决方案4】:

        这是一个 Swift 3 HexagonImageView,可在六边形内点击:

        首先创建一个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()
        }
        

        }

        然后创建一个Hexagon 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")
        }
        

        【讨论】:

          【解决方案5】:

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

          【讨论】:

          • touchesBegan(_:withEvent:) 函数用于在UIResponder 子类中实现手势处理。看起来 OP 正在使用UIGestureRecognizer
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-11-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多