在实现自定义视图控制器演示时,应在何处应用演示视图的约束?

时间:2018-11-04 18:09:37

标签: ios objective-c swift autolayout

我已经阅读了有关自定义模式演示的所有Apple文档,但找不到此答案。使用自定义动画演示视图控制器时,我们可以返回3个结果(如果过渡不是交互式的;交互式可以返回4):演示控制器和两个动画控制器(一个用于演示,一个用于关闭)。

在演示控制器和当前动画控制器中,Apple的代码均未包含约束。关于框架的讨论程度是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;

...就是这样。

我遇到的问题是模拟器中这些显示的视图控制器(以及我拥有的设备)无法识别双高状态栏。更准确地说,没有一个解决方案可在所有模拟器中使用-适用于新模拟器(如iPhone 8)的解决方案不适用于较旧的模拟器(如iPhone 5)。如果我让Apple使用默认的UIKit动画处理演示文稿,则所显示的视图控制器可以很好地处理双倍高度状态栏;因此,我可以假设所提出的视图控制器中的约束不是问题。

所以我没有任何运气就转向了演示控制器的containerViewDidLayoutSubviewscontainerViewWillLayoutSubviews方法:

override func containerViewDidLayoutSubviews() {
    super.containerViewDidLayoutSubviews()
    presentedViewController.view.frame = containerView!.bounds
}

以上代码仅在首次引入双高状态栏时才在模拟器中起作用;从此以后,此方法变得无响应。要验证这一点:

override func containerViewDidLayoutSubviews() {
    super.containerViewDidLayoutSubviews()
    presentedViewController.view.frame = containerView!.bounds
    print("did layout")
}

在首次引入双倍高度状态栏后,上述方法将停止打印到控制台。但是,如果我将任务更改为不会改变所显示视图框架大小的东西,例如,更改其背景颜色:

override func containerViewDidLayoutSubviews() {
    super.containerViewDidLayoutSubviews()
    presentedViewController.view.backgroundColor = UIColor.blue
    print("did layout")
}

方法永不中断。无论出于何种原因,在此方法中更改显示视图框架的大小都会在双高状态栏的第一次切换后中断该方法。我希望对此行为做出一个解释。可能是一个错误,但似乎是一个错误。

不管怎么说,苹果公司说,如果正确使用了自动布局,程序员就什么也不需要做! 所以我的问题是,我们应该在哪里或如何给所显示的视图控制器赋予视图约束,以使其能够适应其临时容器?因为IMO,这就是问题所在。呈现的视图控制器由过渡的容器视图拥有,该视图是UIKit提供的临时视图,我们没有太多的支配力。如果我们可以将呈现的视图锚定到该容器,那么所有问题都将得到解决。但是我从来没有见过苹果这样做或者甚至没有谈论过。

注:在当前的动画设计器控制器中(在动画之前或在其完成处理程序中),在呈现控制器中或在自适应委托方法中,可以将呈现的视图明确锚定到容器视图的任何位置不能如前所述产生一致的结果。

结论:(1)无法正式,正确,整洁或始终如一地处理带有模式演示的双高状态栏; (2)苹果用双高状态栏将其弄坏了,无法等待所有iPhone都在屏幕上出现缺口的那一天。

2 个答案:

答案 0 :(得分:5)

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

因此,我知道您的痛苦,因此,我会通过提供一些我突然发现的提示来帮助您节省时间和精力。


案例:

卡片UI动画如下:

enter image description here

进一步使用的条款:

  • 父母-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 Transforms 由于未知原因而无法使用)。

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

随时提问。

UPD:根据通话状态栏

经过所有可能的实验并检查了类似项目thisthis以及诸如thisthis之类的堆栈溢出问题之后(实际上很有趣,OP的答案就在那里了)和类似的我完全困惑。似乎我的解决方案可以处理UIKit级别的Double状态栏(可以正确调整),但是相同的动作却忽略了以前的转换。原因不明。


代码示例:

您可以在Github上查看有效的解决方案

PS 。我不确定是否可以在答案中发布GitHub链接。我很乐意为您提供有关如何在答案中发布100-300行代码的建议。

答案 1 :(得分:0)

在我当前的项目中,我一直在努力使用双重高度statusBar,我几乎可以解决所有问题(当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 = …
    }
}

result image