如何点击测试SubView的图层?

时间:2018-12-03 16:50:35

标签: ios12 swift4.2 xcode10.1

我的应用程序遇到了挑战。我希望能够点击一个子视图并确定点击了哪个子视图的图层。我(天真?)选择使用子视图的根层的hitTest(:CGPoint)方法。结果不是我预期的。我创建了一个游乐场来说明困难。

游乐场(以下代码):

  1. 创建视图控制器及其根视图
  2. 在根视图中添加一个子视图
  3. 在子视图的根层中添加一个子层

这是它的样子:

enter image description here

  1. 根视图的根层为黑色
  2. 子视图的根层为红色
  3. 子视图的子层是半透明的灰色
  4. 白线用于说明目的

在操场上运行时,我开始点击,从白线的顶部开始向下移动。这是轻击手势处理方法产生的打印语句:

  

点击根视图@(188.5,12.5)
  命中测试返回带有帧(0.0、0.0、375.0、668.0)的“根视图->根层”

     

点击根视图@(188.5,49.0)
  命中测试返回带有帧(0.0、0.0、375.0、668.0)的“根视图->根层”

     

点击根视图@(188.5,88.5)
  命中测试返回带有帧(0.0、0.0、375.0、668.0)的“根视图->根层”

     

点击子视图@(140.0,11.0)

     

点击子视图@(138.5,32.5)

     

点击子视图@(138.5,55.5)

     

点击子视图@(138.0,84.0)

     

点击子视图@(139.0,113.0)
  命中测试返回带有帧(50.0、100.0、275.0、468.0)的“子视图->根层”

     

点击子视图@(138.0,138.5)
  命中测试返回带有帧(50.0、100.0、275.0、468.0)的“子视图->根层”

     

点击子视图@(140.0,171.5)
  命中测试返回带有帧(50.0、100.0、275.0、468.0)的“子视图->根层”

     

点击子视图@(137.5,206.5)
  命中测试返回带有帧(50.0、100.0、175.0、268.0)的“子视图->子层”

     

点击子视图@(135.5,224.0)
  命中测试返回带有帧(50.0、100.0、175.0、268.0)的“子视图->子层”

您可以看到,当我在根视图中点击时,情况是“正常的”。进入子视图后,点击测试将不再找到图层,直到我在子视图顶部以下100多个点为止;这时会遇到子视图的根层。遇到子视图的根层的子层后的100分。

这是怎么回事?好吧,显然,当将子视图添加到根视图时,子视图的根层成为根视图的根层的子层,并且其框架已更改以反映其在根视图的根层而不是子视图中的位置。 / p>

所以,可能有以下问题:

  1. 我是否纠正了明显发生的事情?
  2. 这正常吗?
  3. 我该如何处理?

谢谢您抽出宝贵的时间来考虑我的问题。

这是游乐场代码:

//: [Previous](@previous)

import UIKit
import PlaygroundSupport

class ViewController: UIViewController {

    class View : UIView {
        override func draw(_ rect: CGRect) {
            let context = UIGraphicsGetCurrentContext()!
            context.setStrokeColor(UIColor.white.cgColor)
            context.setLineWidth(2.0)
            context.move(to: CGPoint(x: rect.midX, y: 0))
            context.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
            context.strokePath()
        }
    }

    private var subview: View!

    override func loadView() {
        let rootView = View(frame: CGRect.zero)
        rootView.backgroundColor = .black
        rootView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:))))
        rootView.layer.name = "Root View -> Root Layer"
        self.view = rootView
    }

    override func viewDidLayoutSubviews() {
        subview = View(frame: view.frame.insetBy(dx: 50.0, dy: 100.0))
        subview.backgroundColor = .red
        subview.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:))))
        subview.layer.name = "Sub View -> Root Layer"

        let sublayer = CALayer()
        sublayer.bounds = subview.layer.bounds.insetBy(dx: 50, dy: 100)
        sublayer.position = CGPoint(x: subview.layer.bounds.midX, y: subview.layer.bounds.midY)
        sublayer.backgroundColor = UIColor.gray.cgColor.copy(alpha: 0.5)
        sublayer.name = "Sub View -> Sub Layer"
        subview.layer.addSublayer(sublayer)

        view.addSubview(subview)
    }

    @objc func tapGestureRecognized(_ sender: UITapGestureRecognizer) {
        guard sender.state == .ended, let tappedView = sender.view else { return }

        var viewName: String
        switch tappedView {
        case view: viewName = "Root"
        case subview: viewName = "Sub"
        default: return
        }

        let location = sender.location(in: tappedView)

        print("\n\(viewName) view tapped @\(location)")

        if let layer = tappedView.layer.hitTest(location) {
            print("Hit test returned <\(layer.name ?? "unknown")> with frame \(layer.frame)")
        }
    }
}

PlaygroundPage.current.liveView = ViewController()
PlaygroundPage.current.needsIndefiniteExecution = true

//: [Next](@next)

更新:已解决问题

在始终学识渊博的Matt Nueburg(和他最出色的书)的指导下,我将轻击手势处理程序方法修改为以下内容:

@objc func tapGestureRecognized(_ sender: UITapGestureRecognizer) {
    guard sender.state == .ended, let tappedView = sender.view else { return }

    var viewName: String
    let hitTestLocation: CGPoint

    switch tappedView {
    case view:
        viewName = "Root"
        hitTestLocation = sender.location(in: tappedView)
    case subview:
        viewName = "Sub"
        hitTestLocation = sender.location(in: tappedView.superview!)
   default: return
    }

    print("\n\(viewName) view tapped @\(sender.location(in: tappedView))")

    if let layer = tappedView.layer.hitTest(hitTestLocation) {
        print("Hit test returned <\(layer.name ?? "unknown")> with frame \(layer.frame)")
    }
}

现在控制台输出看起来不错:

点击根视图@(186.0,19.0)
命中测试返回带有帧(0.0、0.0、375.0、668.0)的“根视图->根层”

点击根视图@(187.0,45.0)
命中测试返回带有帧(0.0、0.0、375.0、668.0)的“根视图->根层”

点击根视图@(187.0,84.5)
命中测试返回带有帧(0.0、0.0、375.0、668.0)的“根视图->根层”

点击子视图@(138.0,9.0)
命中测试返回带有帧(50.0、100.0、275.0、468.0)的“子视图->根层”

点击子视图@(137.5,43.5)
命中测试返回带有帧(50.0、100.0、275.0、468.0)的“子视图->根层”

点击子视图@(138.5,77.5)
命中测试返回带有帧(50.0、100.0、275.0、468.0)的“子视图->根层”

点击子视图@(138.0,111.0)
命中测试返回带有帧(50.0、100.0、175.0、268.0)的“ Sub View-> Sub Layer 1”

点击子视图@(138.5,140.5)
命中测试返回带有帧(50.0、100.0、175.0、268.0)的“ Sub View-> Sub Layer 1”

点击子视图@(139.5,174.5)
命中测试返回带有帧(50.0、100.0、175.0、268.0)的“ Sub View-> Sub Layer 1”

第二次更新-清除代码

我回想起我最终用来检测子层上的触摸的代码。我希望它比上面的操场代码更清楚。

private struct LayerTouched : CustomStringConvertible {

    let view: UIView // The touched view
    var layer: CALayer // The touched layer
    var location: CGPoint // The touch location expressed in the touched layer's coordinate system

    init(by recognizer: UIGestureRecognizer) {
        view = recognizer.view!
        let gestureLocation = recognizer.location(in: view)

        // AFAIK - Any touchable layer will have a super layer. Hence the forced unwrap is okay.
        let hitTestLocation = view.layer.superlayer!.convert(gestureLocation, from: view.layer)
        layer = view.layer.hitTest(hitTestLocation)!

        location = layer.convert(gestureLocation, from: view.layer)
    }

    var description: String {
        return """
        Touched \(layer)
        of \(view)
        at \(location).
        """
    }
}

1 个答案:

答案 0 :(得分:1)

  

子视图的根层的hitTest(:CGPoint)方法

但是您忘记了层级点击测试以一种特殊的方式工作。您提供的CGPoint必须位于要进行测试的图层的超层的坐标系中。