21

I am presenting a UIViewController using a custom transition and a custom UIPresentationController. The view controller's view does not cover the entire screen, so the presenting view controller is still visible.

Next, I present an instance of UIImagePickerController on top of this view controller. The problem is that when I dismiss the image picker, the presenting view controller's frame covers the entire screen instead of just the portion I want it to cover. The frame specified by frameOfPresentedViewInContainerView in my custom UIPresentationController seems to be completely ignored.

Only if present the image picker with a modalPresentationStyle of UIModalPresentationOverCurrentContext my frames remain intact (which makes sense since no views are removed from the view hierarchy in the first place). Unfortunately that's not what I want. I want the image picker to be presented full screen, which - for whatever reason - seems to mess up my layout. Anything that I might be doing wrong or forgetting here? Any suggestions?

caryot
  • 251
  • 2
  • 5
  • 2
    have similar problem but with `UIActivityViewController` and only when using full-screen sharing, such as Messages or Mail – race_carr Feb 03 '17 at 20:33
  • For me, the incorrect full screen frame when dismissing modally presented view controller appears momentarily before the correct view appears. – sandpat Sep 18 '18 at 11:13

5 Answers5

18

I tried both wrapper approaches mentioned. One side effect of using this wrapping approach is that device rotation doesn't display well - introducing black boxes around the presented view.

Instead of doing the wrapping trick, try setting the modalPresentationStyle of the presented UIImagePickerController to UIModalPresentationOverFullScreen. This means the views underneath the image picker won't be removed/restored from the view hierarchy during presentation/dismissal.

Thomas Verbeek
  • 2,361
  • 28
  • 30
  • This is much better -- much less of a hack, and more guaranteed to work long-term – Cal Stephens Aug 27 '17 at 01:42
  • 2
    Holy cow.... I was banging my head on every hard surface trying to figure this out. Never could've imagined this to be such a simple solution. Thanks so much ! :) BTW, this should be the accepted answer. – Anjan Biswas Jan 05 '18 at 07:11
  • This seriously saved my butt. Had an issue with this only related to the iPhone X. Thanks man! – G_Money Mar 14 '18 at 17:33
  • 1
    This absolutely works when you have control over the controller you're presenting, like UIImagePickerViewController. But when you present a UIActivityViewController and then the user selects something like "Save to Files" or "Messages", the UIActivityViewController is dismissed and then something else gets presented (e.g. the Messages composer), and in that situation you aren't given a callback to set the modalPresentationStyle. So the same bug occurs when the user is finished with saving to files or sending a message. Any thoughts on applying this approach in that situation? – UberJason Aug 09 '18 at 18:01
  • on dismissing modally presented view controller with this approach, the presentedviewcontroller of UIPresentationController now remains fullscreen :( – sandpat Sep 18 '18 at 11:05
4

This is expected because the fullscreen presentation does not restore the original frame computed by frameOfPresentedViewInContainerView. The recommended way to fix this is to create a wrapper view in which you will insert the presented view controller's view. Here is the relevant code for your custom presentation controller:

- (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];
}

Note that we override presentedView to return the wrapper view instead of the default value – the presented view controller's view. This way, even if the second presentation modifies the wrapper's frame the presented view controller's view will not change.

erudel
  • 482
  • 4
  • 11
  • 3
    Thanks for the suggestion @erudel but unfortunately this doesn't solve my problem. What I get from this is that the custom presented view controller is correct **after** the dismissal animation of the view controller it presented which is an effect that I had already achieved previously by simply re-adjusting the frame of the presented view controller's view in `containerViewWillLayoutSubviews` (no need for the wrapper view). **During** the dismissal animation, however, the view's frame remains full screen, which results in a jumpy layout change and is of course incorrect in the first place. – caryot Jun 23 '16 at 12:43
  • 1
    `containerViewWillLayoutSubviews` handles bound changes such as rotation or adaptation, but the wrapper view is needed exactly for avoiding that "jump" when dismissing the fullscreen presentation. – erudel Jun 23 '16 at 15:16
  • The thing is that I implemented it exactly the way you proposed but I still get the jump @erudel. – caryot Jun 23 '16 at 15:36
  • Can you share your code? I wrote a sample test app with the code above and it behaves correctly. – erudel Jun 24 '16 at 06:32
  • Work for me! Nice! – vitkuzmenko Jun 21 '17 at 09:29
3

This worked for me, in UIPresentationController:

override func containerViewWillLayoutSubviews() {

     super.containerViewWillLayoutSubviews()
     presentedViewController.view.frame = frameOfPresentedViewInContainerView
}
sash
  • 8,423
  • 5
  • 63
  • 74
2

erudel's solution didn't work for me as is, but adding another view in between wrapperView and presentedViewController.view did the trick (I have no idea why):

- (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
}

Tested this on iOS 9.3.

Community
  • 1
  • 1
iosdude
  • 1,131
  • 10
  • 27
2

I tried containerViewWillLayoutSubviews but it wasn't quite working. I wanted to avoid the extra wrapper views if possible. I came up with this strategy of correcting the frame on the view using the presentedView. In addition I was able to remove containerViewWillLayoutSubviews. forceFrame is tuned to our particular use case. presentedFrame is set by our custom animator.

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
        }
    }
SuperGuyAbe
  • 525
  • 1
  • 5
  • 5
  • This one fixed it for me. For reasons I can't be bothered to go into, changing the `modalPresentationStyle` to `UIModalPresentationOverFullScreen` would have been nice but was actually beyond my control without a larger refactor (external library usage). Wrappers seemed clunky. This fixed it nicely for me though! Thanks! – Iain Stanford Jun 10 '20 at 09:07