为什么在呈现另一个控制器时 UIPresentationController 高度会发生变化?

Why UIPresentationController's height is changed when present another controller?

我用UIPresentationController来显示底端。有时 presentationController 可能会出现另一个控制器。并且当呈现的控制器被关闭时,presentationController 的高度发生了变化。那么为什么它会改变,我该如何解决这个问题。代码如下:

class ViewController: UIViewController {

    let presentVC = UIViewController()
    override func viewDidLoad() {
        super.viewDidLoad()
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
            guard let `self` = self else { return }
            self.presentBottom(self.presentVC)
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
            guard let `self` = self else { return }
            let VC = UIViewController()
            VC.view.backgroundColor = .red
            self.presentVC.present(VC, animated: true, completion: {

                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                    VC.dismiss(animated: true, completion: nil)
                }

            })
        }
    }

}
public class PresentBottom: UIPresentationController {

    public override var frameOfPresentedViewInContainerView: CGRect {
        let bottomViewHeight: CGFloat = 200.0
        let screenBounds              = UIScreen.main.bounds
        return CGRect(x: 0, y: screenBounds.height - bottomViewHeight, width: screenBounds.width, height: bottomViewHeight)
    }

}
extension UIViewController: UIViewControllerTransitioningDelegate {

    public func presentBottom(_ vc: UIViewController ) {
        vc.view.backgroundColor = .purple
        vc.modalPresentationStyle = .custom
        vc.transitioningDelegate = self
        self.present(vc, animated: true, completion: nil)
    }

    public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        let vc = PresentBottom(presentedViewController: presented, presenting: presenting)
        return vc
    }
}

我的代码如下:

presentationController身高正好在下图中:

让我感到困惑的是,当前视图控制器的高度已更改:

默认情况下,当呈现视图控制器时,UIKit 会在动画结束后删除呈现的 VC。呈现 VC 的视图只是在转换期间临时添加到容器视图。

您可以使用 UIModalPresentationStyle.overFullScreen 或在自定义演示文稿的情况下使用 shouldRemovePresentersView 更改此行为。在这种情况下,只有呈现的视图被移动到临时 containerView 并且所有动画都在呈现 VC 的视图上方执行 - none 下面的视图被操纵。

此处您要在 VC 上方执行 fullScreen 过渡,并呈现自定义过渡。 UIKit 保持视图层次结构完整,仅在解散和呈现转换期间将呈现 VC 的视图(紫色)与呈现的 VC 的视图(红色)一起移动到容器视图.一旦动画完成,它就会删除紫色的。

问题,显然(我在 didMoveToSuperviewsetFrame 方法中使用了断点),当解雇转换发生时,默认的 "built-in" 动画控制器不关心前一个当前 VC 的视图临时添加到 containerView 时的框架。它在移动到 containerView.

后立即将其设置为 containerView 的框架

不知道是bug还是故意的选择

这是我使用的代码:

class BottomVCView: UIView {
    override var frame: CGRect {
        set {
            super.frame = newValue // BREAKPOINT : newValue.height == 568.0
        }
        get {
            return super.frame
        }
    }
}

class BottomVC: UIViewController {

    lazy var myView = BottomVCView()

    override func loadView() {
        view = BottomVCView()
    }
}

这里是 UIViewControllerBuiltinTransitionViewAnimator 设置呈现 VC 视图的框架的堆栈。

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000102ca0ce8 Project`BottomVCView.frame.setter(newValue=(origin = (x = 0, y = 0), size = (width = 320, height = 568)), self=0x00007ffde5e059f0) at ViewController.swift:35:13
    frame #1: 0x0000000102ca0c9f Project`@objc BottomVCView.frame.setter at <compiler-generated>:0
    frame #2: 0x0000000108e2004f UIKitCore`-[UIViewControllerBuiltinTransitionViewAnimator animateTransition:] + 1149
    frame #3: 0x0000000108d17985 UIKitCore`__56-[UIPresentationController runTransitionForCurrentState]_block_invoke + 3088
    frame #4: 0x0000000109404cc9 UIKitCore`_runAfterCACommitDeferredBlocks + 318
    frame #5: 0x00000001093f4199 UIKitCore`_cleanUpAfterCAFlushAndRunDeferredBlocks + 358
    frame #6: 0x000000010942132b UIKitCore`_afterCACommitHandler + 124
    frame #7: 0x00000001056fe0f7 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    frame #8: 0x00000001056f85be CoreFoundation`__CFRunLoopDoObservers + 430
    frame #9: 0x00000001056f8c31 CoreFoundation`__CFRunLoopRun + 1505
    frame #10: 0x00000001056f8302 CoreFoundation`CFRunLoopRunSpecific + 626
    frame #11: 0x000000010d0dd2fe GraphicsServices`GSEventRunModal + 65
    frame #12: 0x00000001093f9ba2 UIKitCore`UIApplicationMain + 140
    frame #13: 0x0000000102ca60fb NavBar`main at AppDelegate.swift:12:7
    frame #14: 0x0000000106b9f541 libdyld.dylib`start + 1
    frame #15: 0x0000000106b9f541 libdyld.dylib`start + 1

如果您使用 containerViewWillLayoutSubviews,您可以将紫色视图的框架设置回其正确的值,但它只会在解雇转换完成并且视图从 fullScreen 转换中移除后发生容器视图:

class PresentBottom: UIPresentationController {

    override func containerViewWillLayoutSubviews() {
        super.containerViewWillLayoutSubviews()
        presentedView?.frame = frameOfPresentedViewInContainerView
    }
}

我想你可以简单地使用:

let VC = UIViewController()
VC.view.backgroundColor = .red
VC.modalPresentationStyle = .overFullScreen

保留所有视图层次结构。动画师不会触及呈现 VC 的视图 - viewController(for: .from) returns nil 因为它的视图没有移动到容器视图。