以模态方式展示的视图控制器关闭时出现框架错误。

21

我正在使用自定义转场和自定义 UIPresentationController 来展示一个 UIViewController,但是该视图控制器的视图并没有覆盖整个屏幕,所以呈现视图控制器仍然可见。

接下来,我在这个视图控制器上方展示了一个 UIImagePickerController 的实例。问题是,当我关闭图像选择器时,呈现视图控制器的框架会覆盖整个屏幕,而不仅仅是我想要它覆盖的部分。在我的自定义 UIPresentationController 中通过 frameOfPresentedViewInContainerView 指定的框架似乎完全被忽略了。

只有当我使用 modalPresentationStyle 设置为 UIModalPresentationOverCurrentContext 来展示图像选择器时,我的框架才会保持不变(因为第一次没有从视图层次结构中移除任何视图,所以这是有道理的)。不幸的是,这不是我想要的。我希望图像选择器能够全屏展示,但由于某种原因,这似乎破坏了我的布局。我可能在这里做错了什么或遗漏了什么?有什么建议吗?


2
我有类似的问题,但是只在使用全屏共享(例如消息或邮件)时才会出现 UIActivityViewController 的问题。 - race_carr
对我来说,在模态呈现的视图控制器被解除显示时,全屏框架出现了错误,然后才出现正确的视图。 - sandpat
5个回答

18

我尝试了所提到的两种包装方法。使用这种包装方法的一个副作用是设备旋转不能很好地显示,会在呈现的视图周围引入黑色框。

与其使用包装技巧,不如将呈现的UIImagePickerControllermodalPresentationStyle设置为UIModalPresentationOverFullScreen。这意味着图像选择器下面的视图在演示/解除演示期间不会从视图层次结构中删除/恢复。


这样做好多了——不那么像黑客,而且更有长期保障。 - Cal Stephens
2
天哪...我一直在试图弄清楚这个问题,不停地敲打着每一个硬表面。从来没有想过这会是如此简单的解决方案。非常感谢!:) 顺便说一句,这应该是被采纳的答案。 - Anjan Biswas
这真的救了我的命。只有iPhone X才会出现这个问题。谢谢,伙计! - G_Money
1
当您控制您正在呈现的控制器时,例如UIImagePickerViewController,这绝对有效。但是,当您呈现UIActivityViewController并且用户选择类似“保存到文件”或“消息”的内容时,UIActivityViewController将被解除显示,然后会呈现其他内容(例如消息编辑器),在这种情况下,您无法获得回调以设置modalPresentationStyle。因此,当用户完成保存文件或发送消息时,同样的错误会发生。您对在这种情况下应用此方法有何想法? - UberJason
用这种方法解除以模态方式呈现的视图控制器后,UIPresentationController的presentedViewController仍然保持全屏 :( - sandpat

4
这是预期的,因为全屏展示不会恢复由frameOfPresentedViewInContainerView计算出的原始框架。 推荐的解决方法是创建一个包装器视图,在其中插入被展示的视图控制器的视图。以下是您的自定义呈现控制器的相关代码:
- (void)presentationTransitionWillBegin {
    // wrapper is a property defined in the custom presentation controller.
    self.wrapper = [UIView new];
    [self.wrapper addSubview:self.presentedViewController.view];
}

- (CGRect)frameOfPresentedViewInContainerView {
    CGRect result = self.containerView.frame;

    // In this example we are doing a half-modal presentation
    CGFloat height = result.size.height/2;
    result.origin.y = height;
    result.size.height = height;

    return result;
}

- (UIView *)presentedView {
    return self.wrapper;
}

- (BOOL)shouldPresentInFullscreen {
    return NO;
}

- (void)containerViewWillLayoutSubviews {
    self.wrapper.frame = self.containerView.frame;
    self.presentedViewController.view.frame = [self frameOfPresentedViewInContainerView];
}

请注意,我们重写了presentedView方法,使其返回包装视图而不是默认值——被展示的视图控制器的视图。这样,即使第二次展示修改了包装视图的框架,被展示的视图控制器的视图也不会改变。

3
谢谢您的建议@erudel,但不幸的是这并不能解决我的问题。我从中得到的是,自定义的呈现视图控制器在呈现出的视图控制器的退出动画之后是正确的,这是我之前通过在containerViewWillLayoutSubviews 中重新调整呈现视图控制器视图的框架(不需要包装视图)已经实现的效果。然而,在退出动画期间,视图的框架仍然保持全屏状态,这导致布局发生跳跃的变化,显然在第一次就不正确了。 - caryot
1
containerViewWillLayoutSubviews 处理边界变化,例如旋转或适应性,但包装器视图正是为了避免在取消全屏演示时出现“跳跃”。 - erudel
问题在于我按照你提出的方式实现了它,但我仍然遇到了@erudel跳跃。 - caryot
你能分享一下你的代码吗?我用上述代码编写了一个样例测试应用程序,它的行为是正确的。 - erudel
为我工作!不错! - vitkuzmenko

3
这个方法对我有用,是在UIPresentationController中实现的:
override func containerViewWillLayoutSubviews() {

     super.containerViewWillLayoutSubviews()
     presentedViewController.view.frame = frameOfPresentedViewInContainerView
}

这对我帮助很大!谢谢! - aleksie3006

2

erudel的解决方案 对我来说并没有起作用,但是在wrapperViewpresentedViewController.view之间再添加一个视图就解决了问题(我不知道为什么):

- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController
                       presentingViewController:(UIViewController *)presentingViewController {
    if (self = [super initWithPresentedViewController:presentedViewController
                             presentingViewController:presentingViewController]) {
        _wrapperView = [[UIView alloc] init];
        _wrapperView2 = [[UIView alloc] init]; // <- new view
    }
    return self;
}

- (CGRect)frameOfPresentedViewInContainerView {
    return self.containerView.bounds;
}

- (UIView *)presentedView {
    return self.wrapperView;
}

- (BOOL)shouldPresentInFullscreen {
    return NO;
}

- (void)containerViewWillLayoutSubviews {
    self.wrapperView.frame = self.containerView.frame;
    self.wrapperView2.frame = /* your custom frame goes here */;
    self.presentedViewController.view.frame = self.wrapperView2.bounds;
}

- (void)presentationTransitionWillBegin {
    [self.wrapperView addSubview:self.wrapperView2];
    [self.wrapperView2 addSubview:self.presentedViewController.view];

    // Set up a dimming view, etc
}

我在iOS 9.3上进行了测试。


2

我尝试使用containerViewWillLayoutSubviews,但它并没有完全起作用。如果可能的话,我想避免使用额外的包装视图。我想出了一种使用presentedView来校正视图框架的策略。此外,我还能够删除containerViewWillLayoutSubviewsforceFrame针对我们特定的用例进行了调整。 presentedFrame由我们的自定义动画师设置。

class CustomModalPresentationController: UIPresentationController {

        var presentedFrame = CGRect.zero
        var forceFrame = false

        override func dismissalTransitionWillBegin() {
            forceFrame = false
        }
        override func presentationTransitionDidEnd(_ completed: Bool) {
            forceFrame = true
        }
        override var presentedView: UIView? {
            if forceFrame {
                presentedViewController.view.frame = presentedFrame
            }
            return presentedViewController.view
        }
        override var frameOfPresentedViewInContainerView: CGRect {
            return presentedFrame
        }
    }

这个对我有用。由于某些原因,我不想深入讨论,将 modalPresentationStyle 更改为 UIModalPresentationOverFullScreen 会很好,但实际上超出了我的控制范围,需要进行更大的重构(使用外部库)。包装器看起来很笨重。不过这个方法对我很有效!谢谢! - Iain Stanford

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接