代码之家  ›  专栏  ›  技术社区  ›  clearlight anky_believeMe

挂在超级视图边缘的UIView将触摸手势事件发送到不相关的底层视图

  •  0
  • clearlight anky_believeMe  · 技术社区  · 4 年前

    我创建了一个UIView(工具提示框),并将其添加为另一个视图的子视图。

    使用锚点约束,我将它从超视图的边缘浮动,它悬停在一个不相关的UIView(恰好是UITextView)之上

    在侵入工具提示视图中,我添加了一个UIButton和一个UISegmentedControl

    当我点击按钮或分段控件时,不拥有工具提示视图的文本视图将获取事件, 不 按钮或分段控件。

    我的问题是,如何让UIButton或UISegmentedControl获得它们的事件,而不是文本视图?


    更新1: (一种理论)

    我认为发生的事情与 UIView 是 UIResponder ,但是 UITextView 是当前 第一响应者 创建工具提示视图的视图控制器承载键盘附件视图,其中键盘(以及因此附件视图控制器及其附件视图)出现 因为 文本视图成为第一响应者。

    我需要使工具提示视图成为第一响应程序吗?这将是一场混乱,尤其是因为辅助键盘(工具提示是其子视图)将消失。此外,当文本视图不再是第一响应者时,文本视图约束会发生变化,因此当文本视图失去第一响应者状态时,它会向下滑动,其他视图会移入。


    更新2: (苹果公司说我是个坏孩子,事实证明,我是,我可以证明这一点)

    在检查与重新排序响应程序链相关的苹果文档时,我发现了以下明确的注释(将其视为警告):

    注:

    如果触摸位置在视图边界之外,hitTest(:with:)方法将忽略该视图及其所有子视图。因此,当视图clipseToBounds属性为false时,即使视图边界之外的子视图恰好包含触摸,也不会返回。有关hit测试行为的更多信息,请参阅UIView中对hitTest(_:with:)方法的讨论。

    所以我测试了一下:

    我创建了UIView的一个子类,并用 print(#function) 。

    我观察到两件事:

    1. 当工具提示UIView离开键盘附件UIView的边缘时:

      A.按钮没有响应

      B.没有调用工具提示视图的重写hitest()!

    2. 当我将约束更改为将工具提示放置在键盘附件视图的顶部时,对于与附件视图(例如,第一响应者/超级视图)边界重叠的按钮,将调用hittest(),按钮将按预期工作。工具提示视图中仍挂在键盘附件边缘的按钮感觉不到触摸,hitest()也不被调用。。底层文本视图获取它们。

    结论:苹果公司的明确说明描述了我的问题。


    更新3 (手势识别器能工作吗?)

    我不希望我的交互式工具提示位于键盘附件上方。

    基于手势识别器在调用UIView事件处理之前进行处理的事实,我想知道解决方案是否是在UIControls(按钮和分段控件)中添加(否则是多余的)敲击手势识别器,因为知道它们的父视图超出了响应器链的范围?

    因此,我的新问题是:如何使UIView位于非剪切接收器的边缘?UIView的按钮可以工作?

    0 回复  |  直到 4 年前
        1
  •  1
  •   DonMag    4 年前

    我们可以实施 hitTest() 以允许使用视图边界之外的触摸。

    但是,我们需要 检查 对于被触摸的景色。

    所以,你说你有一个子视图(我称之为 tipView )包含一个按钮和一个分段控件作为子视图,并且您正在添加 tipView 作为的子视图 UITextView 已定位 外部 文本视图的边界。

    例如:

    class ToolTipTextView: UITextView {
    
        var tipView: UIView!
        var btn: UIButton!
        var seg: UISegmentedControl!
    
        override init(frame: CGRect, textContainer: NSTextContainer?) {
            super.init(frame: frame, textContainer: textContainer)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        
        func commonInit(){
            
            // we need to see the view outside our bounds
            self.clipsToBounds = false
            
            tipView = UIView()
            tipView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
    
            btn = UIButton()
            btn.setTitle("Tap Me", for: [])
            btn.setTitleColor(.white, for: .normal)
            btn.setTitleColor(.lightGray, for: .highlighted)
            btn.backgroundColor = .systemBlue
            
            seg = UISegmentedControl(items: ["One", "Two", "Three"])
            
            [tipView, btn, seg].forEach { v in
                v.translatesAutoresizingMaskIntoConstraints = false
            }
    
            tipView.addSubview(btn)
            tipView.addSubview(seg)
            self.addSubview(tipView)
    
            NSLayoutConstraint.activate([
                // button at top of tipView, centerX
                btn.topAnchor.constraint(equalTo: tipView.topAnchor, constant: 4.0),
                btn.centerXAnchor.constraint(equalTo: tipView.centerXAnchor),
                
                // seg control below button
                seg.topAnchor.constraint(equalTo: btn.bottomAnchor, constant: 4.0),
                seg.bottomAnchor.constraint(equalTo: tipView.bottomAnchor, constant: -4.0),
                seg.leadingAnchor.constraint(equalTo: tipView.leadingAnchor, constant: 4.0),
                seg.trailingAnchor.constraint(equalTo: tipView.trailingAnchor, constant: -4.0),
    
                // tipView above self, centerX
                tipView.bottomAnchor.constraint(equalTo: self.topAnchor, constant: -4.0),
                tipView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            ])
            
            // targets for btn and seg
            btn.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
            seg.addTarget(self, action: #selector(segSelected(_:)), for: .valueChanged)
            
        }
        
        @objc func btnTapped(_ sender: Any?) {
            let str = self.text ?? ""
            self.text = str + " Button Tapped! "
        }
        @objc func segSelected(_ sender: Any?) {
            guard let ctrl = sender as? UISegmentedControl,
                  let t = ctrl.titleForSegment(at: ctrl.selectedSegmentIndex)
            else { return }
            let str = self.text ?? ""
            self.text = str + " Segment \(t) Selected! "
        }
        
    }
    
    class TipTestVC: UIViewController {
        
        let myView = ToolTipTextView()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            myView.translatesAutoresizingMaskIntoConstraints = false
            
            view.addSubview(myView)
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                myView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                myView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                myView.heightAnchor.constraint(equalToConstant: 160.0),
                myView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            ])
            
            myView.font = .systemFont(ofSize: 20.0, weight: .light)
            myView.text = "This is text in the text view."
    
            // so we can see the text view frame
            myView.backgroundColor = .green
            
        }
        
    }
    

    并且在运行时会是这样的:

    enter image description here

    假设这接近你想要的。。。

    正如您所看到的,您无法点击按钮或分段控件。

    因此,我们需要添加 hitTest() 按照我们的习惯 ToolTipTextView 类别:

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    
        // convert the touch to the tipView coordinate space
        let cvtPoint = self.convert(point, to: tipView)
    
        if btn.frame.contains(cvtPoint) {
            return btn
        }
        
        if seg.frame.contains(cvtPoint) {
            return seg
        }
    
        return super.hitTest(point, with: event)
    
    }
    

    因为视图、按钮和seg控件 不 在视图的边界内,点击按钮将产生一个触摸点 相对于文本视图 --点击按钮可能会告诉你这一点: (167.0, -59.5) 。

    因此,我们将该点转换为 tipView 坐标空间,并查看它是否包含在按钮或seg控件的框架中。如果是这样的话,我们就返回这一观点。

    如果没有,我们打电话 super 让UIKit处理触摸。。。如果触摸在内部则激活文本视图,或者允许触摸到达屏幕上的某个其他UI元素。

    推荐文章