如何在不中断响应器链的情况下将 UIViewController 的视图嵌入到另一个视图中

How to embed a UIViewController's view into another view without breaking responder chain

提问人:Tristian 提问时间:11/23/2019 最后编辑:Tristian 更新时间:11/23/2019 访问量:305

问:

我正在尝试为视图控制器实现一个基类,该基类将添加一个顶部横幅,该横幅根据特定状态显示和隐藏。因为我扩展了这个基类,所以在我必须将原始属性嵌入到以编程方式创建的新属性中时,该属性已经加载。UIViewControllerviewviewview

嵌入工作正常(UI 方面),视图会适当地自行调整,但是我看到的问题是,在嵌入它之后,嵌入的视图不再响应触摸事件,在我最初嵌入的控制器中,我有一个包含 16 行和一个按钮的表视图,在嵌入它之前,它们都正确响应点击和滚动事件。

我只能在IB中使用分屏视图控制器来实现双视图分屏。

这是我目前的实现,似乎无法弄清楚我错过了什么来传播事件,我尝试使用自定义视图,该视图覆盖了两者和变量都无济于事。hitTest()newRootViewcontentView

非常感谢任何帮助或见解,谢谢!

class BaseViewController: UIViewController {
    var isInOfflineMode: Bool {
       didSet { if isInOfflineMode { embedControllerView() }}
    }

    var offlineBanner: UIView!
    var contentView: UIView!

    private func embedControllerView() {
        guard let currentRoot = viewIfLoaded else { return }

        currentRoot.translatesAutoresizingMaskIntoConstraints = false

        let newRootView = UIView(frame: UIScreen.main.bounds)
        newRootView.backgroundColor = .yellow // debugging
        newRootView.isUserInteractionEnabled = true
        newRootView.clipsToBounds = true
        view = newRootView

        offlineBanner = createOfflineBanner() // returns a button that I've verified to be tapable.

        view.addSubview(offlineBanner)

        contentView = UIView(frame: .zero)
        contentView.backgroundColor = .cyan // debugging
        contentView.translatesAutoresizingMaskIntoConstraints = false
        contentView.isUserInteractionEnabled = true
        view.addSubview(contentView)

        contentView.addSubview(currentRoot)

        let bannerHeight: CGFloat = 40.00
        var topAnchor: NSLayoutYAxisAnchor
        var bottomAnchor: NSLayoutYAxisAnchor
        var trailingAnchor: NSLayoutXAxisAnchor
        var leadingAnchor: NSLayoutXAxisAnchor

        if #available(iOS 11.0, *) {
            topAnchor = view.safeAreaLayoutGuide.topAnchor
            bottomAnchor = view.safeAreaLayoutGuide.bottomAnchor
            leadingAnchor = view.safeAreaLayoutGuide.leadingAnchor
            trailingAnchor = view.safeAreaLayoutGuide.trailingAnchor
        } else {
            topAnchor = view.topAnchor
            bottomAnchor = view.bottomAnchor
            leadingAnchor = view.leadingAnchor
            trailingAnchor = view.trailingAnchor
        }

        NSLayoutConstraint.activate([
            offlineBanner.heightAnchor.constraint(equalToConstant: bannerHeight),
            offlineBanner.topAnchor.constraint(equalTo: topAnchor),
            offlineBanner.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0),
            offlineBanner.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
        ])

        NSLayoutConstraint.activate([
            contentView.topAnchor.constraint(equalTo: topAnchor, constant: bannerHeight),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0),
            contentView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0),
            contentView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
        ])

        OfflineViewController.migrateViewContraints(from: currentRoot, to: contentView)

        view.setNeedsUpdateConstraints()
        offlineBanner.setNeedsUpdateConstraints()
        currentRoot.setNeedsUpdateConstraints()
    }

    private func unembedControllerView() {
        let v = contentView.subviews[0]
        v.removeFromSuperview()
        view = v
        OfflineViewController.migrateViewContraints(from: contentView, to: v)
    }

    /**
     Replaces any constraints associated with the current root's safe area`UILayoutGuide` or with the actual
     current root view.
     */
    private static func migrateViewContraints(from currentRoot: UIView, to newRoot: UIView) {
        for ct in currentRoot.constraints {
            var firstItem: Any? = ct.firstItem
            var secondItem: Any? = ct.secondItem

            if #available(iOS 11.0, *) {
                if firstItem as? UILayoutGuide == currentRoot.safeAreaLayoutGuide {
                    debugPrint("Migrating firstItem is currentLayoutGuide")
                    firstItem = newRoot.safeAreaLayoutGuide
                }

                if secondItem as? UILayoutGuide == currentRoot.safeAreaLayoutGuide {
                    debugPrint("Migrating secondItem is currentLayoutGuide")
                    secondItem = newRoot.safeAreaLayoutGuide
                }
            }

            if firstItem as? UIView == currentRoot {
                debugPrint("Migrating firstItem is currentRoot")
                firstItem = newRoot
            }

            if secondItem as? UIView == currentRoot {
                debugPrint("Migrating secondItem is currentRoot")
                secondItem = newRoot
            }

            NSLayoutConstraint.deactivate([ct])
            NSLayoutConstraint.activate([
                NSLayoutConstraint(item: firstItem as Any,
                                   attribute: ct.firstAttribute,
                                   relatedBy: ct.relation,
                                   toItem: secondItem,
                                   attribute: ct.secondAttribute,
                                   multiplier: ct.multiplier,
                                   constant: ct.constant)
            ])
        }
    }
}

在这个特定视图中,绿色按钮确实会获取事件,这是我以编程方式创建的按钮:

Embed Working

下面是一个不响应事件的视图,一个带有按钮的表视图:

Embed not working

iOS的 iPhone UIVieview控制器 XCode11 SWIFT4.2

评论

0赞 Mojtaba Hosseini 11/23/2019
您可以在任何您不喜欢的视图上禁用。userInteraction
0赞 Tristian 11/23/2019
我不想禁用它,我想为嵌入式视图启用它;默认情况下,他们嵌入的视图启用了交互

答:

0赞 Tristian 11/23/2019 #1

我已经弄清楚问题出在哪里了;我缺少(新的根视图)和原始视图之间的限制。这些约束实质上使原始视图占据了以下全部空间:contentViewself.viewcurrentRootcontentView

NSLayoutConstraint.activate([
  currentRoot.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0),
  currentRoot.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0),
  currentRoot.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0),
  currentRoot.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0),
])