Reputation: 251
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?
Upvotes: 21
Views: 4544
Reputation: 535
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
}
}
Upvotes: 2
Reputation: 8747
This worked for me, in UIPresentationController
:
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
presentedViewController.view.frame = frameOfPresentedViewInContainerView
}
Upvotes: 3
Reputation: 2431
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.
Upvotes: 18
Reputation: 1139
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.
Upvotes: 2
Reputation: 480
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.
Upvotes: 4