如果部分离开屏幕,则子ViewController安全区域插图不会更新

Ben*_*Ben 6 ios childviewcontroller safearealayoutguide iphone-x

当在横向iPhone X上滑动儿童ViewController时,在安全区域方面遇到困难。

我有一个根ViewController,其视图之一是可移动的,并且包含一个嵌入式子ViewController。真正的应用程序是滑动的侧栏菜单UI。这里的示例代码是简化版本。

如果表格位于屏幕的左侧,则“安全区域”布局规则会将其单元格contentView插入项推到右侧以允许出现缺口。正确。但是,如果我将子级ViewController移离屏幕的左边缘,则子级的插图不会更新以重播内容。

我已经意识到,实际上,如果子ViewController在其最终位置完全显示在屏幕上,则一切正常。如果其中任何部分不在屏幕上,则不会发生“安全区域”更新。

这是显示此问题的示例代码。这适用于标准的Single View App Xcode模板项目:用显示的代码替换ViewController文件代码。运行时,向右滑动表格会将其从屏幕的左边缘移动到右边缘。

参见“ constraint(...,乘数:0.5)”行。这设置了相对于屏幕的可移动视图的宽度。值为0.5时,表格完全适合屏幕,并且安全区域会随着移动而更新。左停靠时,表格单元格遵循“安全区域”插图,右停靠时,表格单元格中没有正确的额外插图。

一旦乘数超过0.5(甚至在0.51时),表格的右滑动部分便不在屏幕上。在这种情况下,不会发生安全区域更新,因此表格单元格内容插入太大了-即使表格的左边缘现在不在安全区域附近,它仍然具有44像素的安全区域插入。

更令人困惑的是,如果UIViews不是UIViewController的视图,则布局似乎可以很好地工作。但是我需要它与UIViewControllers一起使用。

谁能解释如何让子级ViewController遵守正确的安全区域?谢谢。

该问题的屏幕截图

复制代码:

class ViewController: UIViewController {

    var leftEdgeConstraint : NSLayoutConstraint!
    var viewThatMoves : UIView!
    var myEmbeddedVC : UIViewController!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = UIColor.gray

        self.viewThatMoves = UIView()
        self.viewThatMoves.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(self.viewThatMoves)

        // Relayout during animation work with multiplier = 0.5
        // With any greater value, like 0.51 (meaning part of the view is offscreen), relayout does not happen
        self.viewThatMoves.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.5).isActive = true
        self.viewThatMoves.heightAnchor.constraint(equalTo: self.view.heightAnchor).isActive = true
        self.viewThatMoves.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
        self.leftEdgeConstraint = self.viewThatMoves.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0)
        self.leftEdgeConstraint.isActive = true

        // Embed child ViewController
        self.myEmbeddedVC = MyTableViewController()

        self.addChildViewController(self.myEmbeddedVC)
        self.myEmbeddedVC.view.translatesAutoresizingMaskIntoConstraints = false
        self.viewThatMoves.addSubview(self.myEmbeddedVC.view)
        self.myEmbeddedVC.didMove(toParentViewController: self)

        // Fill containing view
        self.myEmbeddedVC.view.leftAnchor.constraint(equalTo: self.viewThatMoves.leftAnchor).isActive = true
        self.myEmbeddedVC.view.rightAnchor.constraint(equalTo: self.viewThatMoves.rightAnchor).isActive = true
        self.myEmbeddedVC.view.topAnchor.constraint(equalTo: self.viewThatMoves.topAnchor).isActive = true
        self.myEmbeddedVC.view.bottomAnchor.constraint(equalTo: self.viewThatMoves.bottomAnchor).isActive = true

        let swipeLeftRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(recognizer:)))
        swipeLeftRecognizer.direction = .left
        let swipeRightRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(recognizer:)))
        swipeRightRecognizer.direction = .right

        self.viewThatMoves.addGestureRecognizer(swipeLeftRecognizer)
        self.viewThatMoves.addGestureRecognizer(swipeRightRecognizer)
    }

    @objc func handleSwipe(recognizer:UISwipeGestureRecognizer) {
        UIView.animate(withDuration: 1) {

            if recognizer.direction == .left {
                self.leftEdgeConstraint.constant = 0
            }
            else if recognizer.direction == .right {
                self.leftEdgeConstraint.constant = self.viewThatMoves.frame.size.width
            }

            self.view.setNeedsLayout()
            self.view.layoutIfNeeded()

            // Tried this: has no effect
            // self.myEmbeddedVC.viewSafeAreaInsetsDidChange()
        }
    }
}

class MyTableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = UIColor.blue
        self.title = "Test Table"

        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Left", style: .plain, target: nil, action: nil)
        self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Right", style: .plain, target: nil, action: nil)
    }

    // MARK: - Table view data source

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 25
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
        cell.contentView.backgroundColor = UIColor.green
        cell.backgroundColor = UIColor.yellow
        cell.textLabel?.text = "This is row \(indexPath.row)"
        cell.textLabel?.backgroundColor = UIColor.clear

        return cell
    }
}
Run Code Online (Sandbox Code Playgroud)

Sam*_*fes 6

半相关的是,我希望一个包含的视图控制器始终具有与另一个视图相同的插图。就我而言,它始终是全屏的,所以我只使用窗口,但这种方法适用于任何其他视图。

private final class InsetView: UIView {
    override var safeAreaInsets: UIEdgeInsets {
        window?.safeAreaInsets ?? .zero
    }
}

final class MyViewController: UIViewController {
    override func loadView() {
        view = InsetView()
    }
}
Run Code Online (Sandbox Code Playgroud)

这对我来说完美无缺。最初,我试图通过使附加的视图控制器应该拥有的和实际拥有的之间的差异additionalSafeAreaInsets来计算,但这在滚动视图中确实很紧张。safeAreaInsetsDidChange


str*_*mes 5

我已经设法用一个丑陋的黑客来“修复”这个问题。在父视图控制器中,您需要执行以下操作:

- (void) viewSafeAreaInsetsDidChange {
  [super viewSafeAreaInsetsDidChange];

  // Fix for child controllers not receiving an update on safe area insets
  // when they're partially not showing
  for (UIViewController *childController in self.childViewControllers) {
    UIEdgeInsets prevInsets = childController.additionalSafeAreaInsets;
    childController.additionalSafeAreaInsets = self.view.safeAreaInsets;
    childController.additionalSafeAreaInsets = prevInsets;
  }
}
Run Code Online (Sandbox Code Playgroud)

这会强制子视图控制器正确更新其 safeAreaInsets。


elm*_*mon -1

这似乎是一种系统行为:如果子视图控制器的视图在垂直或/和水平方向上似乎不完全位于其父视图控制器的边界内,则其安全区域的相应方向不会更新。

听起来,如果您希望子视图控制器超出其父视图控制器的范围,您应该使用变换属性,但安全仍然不会表现完全正确,但是,它会在旋转时更新。