【问题标题】:When implementing custom view controller presentations, where to apply presented view's constraints?在实现自定义视图控制器演示时,在哪里应用呈现视图的约束?
【发布时间】:2019-04-08 04:34:33
【问题描述】:

当使用自定义动画呈现视图控制器时,Apple 的文档或示例代码均未提及或包含以下约束:

// Always add the "to" view to the container.
// And it doesn't hurt to set its start frame.
[containerView addSubview:toView];
toView.frame = toViewStartFrame;

问题是自定义呈现的视图控制器无法识别双高状态栏(使用非自定义呈现的视图控制器没有这个问题)。呈现的视图控制器由转换的容器视图拥有,这是一个由 UIKit 提供的临时视图,我们几乎没有控制权。如果我们将呈现的视图锚定到该瞬态容器,它只适用于某些操作系统版本;更不用说,Apple 从未建议这样做。

更新 1: 无法始终如一地处理带有自定义模式演示的双高状态栏。我认为 Apple 在这里搞砸了,我怀疑他们最终会逐步淘汰它。

更新 2:双高状态栏已被淘汰,不再存在于非边缘到边缘设备上。

【问题讨论】:

    标签: ios objective-c swift autolayout


    【解决方案1】:

    我的回答是:你不应该在自定义模态演示的情况下使用约束

    因此我知道你的痛苦,所以我会通过提供一些我突然透露的提示来帮助你节省时间和精力。


    示例案例:

    卡片界面动画如下:

    进一步使用条款:

    • 父级 - UIViewController 带有“详细信息”栏按钮项
    • 孩子 - UIViewController 和“另一个”

    你提到的麻烦开始了,当我的动画涉及随着运动的大小变化时。它会引起不同类型的影响,包括:

    • 父母的状态栏下方区域出现和消失
    • 父子视图的动画效果不佳 - 跳跃、重复和其他故障。

    经过几天的调试和搜索,我想出了以下解决方案(对不起,一些神奇的数字;)):

    UIView.animate(withDuration: transitionDuration(using: transitionContext),
                           delay: 0,
                           usingSpringWithDamping: 1,
                           initialSpringVelocity: 0.4,
                           options: .curveEaseIn, animations: {
                toVC.view.transform = CGAffineTransform(translationX: 0, y: self.finalFrame.minY)
                toVC.view.frame = self.finalFrame
                toVC.view.layer.cornerRadius = self.cornerRadius
                
                fromVC.view.layer.cornerRadius = self.cornerRadius
                var transform = CATransform3DIdentity
                transform = CATransform3DScale(transform, scale, scale, 1.0)
                transform = CATransform3DTranslate(transform, 0, wdiff, 0)
                fromVC.view.layer.transform = transform
                fromVC.view.alpha = 0.6
            }) { _ in
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            }
    

    这里的重点是,您必须使用 CGAffineTransform3D 来避免动画问题和子视图动画问题(2D 变换由于未知原因无法正常工作)。

    我希望这种方法可以在不使用约束的情况下解决您的所有问题。

    欢迎提问。

    UPD:根据通话状态栏

    经过数小时的所有可能的实验并检查类似的项目,如 thisthis 以及 stackoverflow 问题,如 thisthis(这实际上很有趣,OP 的答案就在那里)和类似的我完全糊涂了。似乎我的解决方案在 UIKit 级别处理双状态栏(它可以正确调整),但同样的动作忽略了以前的转换。原因不明。


    代码示例:

    您可以在Github 上查看工作解决方案

    P.S.我不确定是否可以在答案中发布 GitHub 链接。对于如何在答案中发布 100-300 行代码的建议,我将不胜感激。

    【讨论】:

    • 感谢您的回答和努力,但 Github 链接中的项目不是一个有效的解决方案。它没有解决问题中提出的问题,即呈现的视图对框架变化的适应。在您的 Github 项目中,切换双高状态栏会破坏 UI。这就是我要解决的问题。
    • @narddog,明白了。抱歉,我什至不知道我是如何跳过这一点的。所以我玩的多一点。我们可以分别使用NotificationCenter.default.addObserver(self, selector: #selector(SELECTOR), name: UIApplication.willChangeStatusBarFrameNotification, object: nil) 和更新ViewControllers 帧来监听状态栏帧的变化。我的解决方案中的 scale 将再次出现问题。我会继续玩它并分享结果。
    【解决方案2】:

    在我当前的项目中,我一直在努力解决双高状态栏的问题,并且我能够解决几乎所有问题(当presentingViewController 嵌入在UITabBarController 中时,剩下的最后一个问题是一个非常奇怪的转换问题)。

    当状态栏的高度发生变化时,会发布通知。
    您的 UIPresentationController 子类应该订阅该特定通知并调整 containerView 及其子视图的框架:

    UIApplication.willChangeStatusBarFrameNotification

    这是我正在使用的代码示例:

    final class MyCustomPresentationController: UIPresentationController {
    
        // MARK: - StatusBar
    
        private func subscribeToStatusBarNotifications() {
            let notificationName = UIApplication.willChangeStatusBarFrameNotification
            NotificationCenter.default.addObserver(self, selector: #selector(statusBarWillChangeFrame(notification:)), name: notificationName, object: nil)
        }
    
        @objc private func statusBarWillChangeFrame(notification: Notification?) {
            if let newFrame = notification?.userInfo?[UIApplication.statusBarFrameUserInfoKey] as? CGRect {
                statusBarWillChangeFrame(to: newFrame)
            } else {
                statusBarWillChangeFrame(to: .zero)
            }
        }
    
        func statusBarWillChangeFrame(to newFrame: CGRect) {
            layoutContainerView(animated: true)
        }
    
        // MARK: - Object Lifecycle
    
        deinit {
            // Unsubscribe from all notifications
            NotificationCenter.default.removeObserver(self)
        }
    
        // MARK: - Layout
    
        /// Called when the status-bar is about to change its frame.
        /// Relayout the containerView and its subviews
        private func layoutContainerView(animated: Bool) {
            guard let containerView = self.containerView else { return }
    
            // Retrieve informations about status-bar
            let statusBarHeight = UIApplication.shared.statusBarFrame.height
            let normalStatusBarHeight = Constants.Number.statusBarNormalHeight // 20
            let isStatusBarNormal = statusBarHeight ==~ normalStatusBarHeight
    
            if animated {
                containerView.frame = …
                updatePresentedViewFrame(animated: true)
            } else {
                // Update containerView frame
                containerView.frame = …
                updatePresentedViewFrame(animated: false)
            }
        }
    
        func updatePresentedViewFrame(animated: Bool) {
            self.presentedView?.frame = …
        }
    }
    

    【讨论】:

    • 您是否将scaletranslation 一起使用?在我的情况下,它也会破坏 UI。
    • 我在presentationTransition 期间确实使用了比例变换,但我没有使用任何翻译。对于有缺口的设备,我直接设置框架的垂直原点(视图位于顶部安全区域插图下方)。
    • 将尝试直接设置框架,不进行翻译。谢谢。
    猜你喜欢
    • 2012-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多