9
votes

My iPad application opens modal view controller with 'Page' presentation style. As you know 'Page' presentation style doesn't cover status bar of presenting view controller to indicate page presentation.

Initial view controller

Page modal view controller

From the modal view controller the app opens UIImagePickerController to make photo. UIImagePickerController has 'Full screen' presentation style. After dismissing image picker presenting modal view controller become 20px taller and overlaps status bar of the initial view controller.

I tried to replace UIImagePickerController with simple UINavigationController and it breaks my modal view controller too.

There are screen shots: Full screen view controller

Broken modal view controller

They only way to restore size of 'Page' view controller is changing height of viewController.view.superview.superview.superview.superview frame after returning to 'Page' view controller. But it's really weird.

Is there another way to fix 'Page' modal view controller presentation after dismissing nested modal view controller?

UPDATE: I used such weird code to solve my problem:

#define STATUS_BAR_HEIGHT 20
#define IPAD_PORTRAIT_HEIGHT 1004
#define IPAD_LANDSCAPE_HEIGHT 748
UIView *superview = nil;

// In case of this view controller included in navigationController we have to use superview of navigation's controller view
if (self.navigationController)
    superview = self.navigationController.view.superview;
else
    superview = self.view.superview;

CGRect r = superview.frame;

// Sometimes we have to fix height + origin, sometimes only height (becase view has bottom magnifying)
// In landscape orientation we have to fix 'width' instead of 'height', because that view controller always works in 'portrait' mode
if (self.interfaceOrientation == UIInterfaceOrientationPortrait && r.size.height > IPAD_PORTRAIT_HEIGHT) {
    r.origin.y = STATUS_BAR_HEIGHT;
    r.size.height = IPAD_PORTRAIT_HEIGHT;
}
else if (self.interfaceOrientation == UIInterfaceOrientationMaskPortraitUpsideDown && r.size.height > IPAD_PORTRAIT_HEIGHT) {
    r.size.height = IPAD_PORTRAIT_HEIGHT;
}
else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft && r.size.width > IPAD_LANDSCAPE_HEIGHT) {
    r.size.width = IPAD_LANDSCAPE_HEIGHT;
    r.origin.x = STATUS_BAR_HEIGHT;
}
else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeRight && r.size.width > IPAD_LANDSCAPE_HEIGHT) {
    r.size.width = IPAD_LANDSCAPE_HEIGHT;
}

superview.frame = r;

I don't believe that there is no more elegant solution. Any ideas how to improve it?

UPDATE2: I've just opened a bug. You can follow it there: rdar://15949644

UPDATE3: There is my sample project: link

5
This is a known Apple bug. It has not been fixed in iOS7.1. Make sure to open a bug report.Leo Natan
@LeoNatan, have you ever seen this problem somewhere else? I want to collect a bit more details before opening a bug report.Vitaly S.
Yes, in our own app. We have a modal page sheet view controller presenting a camera view controller in fullscreen, and we experience the same visual anomaly after dismissing. We deemed it minor problem, so we only opened a bug report and didn't look for a workaround. I could take a look during the weekend and see if I can come up with a more elegant workaround, perhaps somewhere in the layouting system, but I can make no promises.Leo Natan
@LeoNatan, anyway thanks for the info. I'll update my question after opening a bug report.Vitaly S.
This reproduces very easily, and your workaround is good.Leo Natan

5 Answers

2
votes

There is no good solution, it's an Apple bug, and until it is fixed, you have to work around it. It has not been fixed in iOS 7.1. I worked on a solution to this and realized I was implementing the same solution as well. It's ugly, but it works.

A word on this design. My guess why Apple overlooked this problem is because presenting a view controller in fullscreen is not something Apple would do. This is not an excuse of course, and sometimes there is no other option but to present fullscreen (we had to open a camera view, which must be opened in fullscreen, for example). Perhaps you can change your design to accommodate Apple's bugs.

1
votes

"Ensure that the ViewController that is presenting the modal view is in a NavigationController and this weirdness should stop. " My initial answer - wrong

Updated This does indeed sound like a bug, although from a user experience point of view one you shouldn't really hit. It seems a little wrong to present a view controller that is in the 'Page' presentation style and then to present another over it in full screen mode.

IMO that's just bad design from the outset so the fact that it doesn't act as you'd expect probably is because whoever set it up didn't anticipate someone using it like that.

I would, pretty much as my initial answer stated albeit briefly, embed your modally presented view controller in a navigation controller and push the UIImagPickerViewController in to that or add it's view animated to yours as if it then appears to be presented like another page styled modal view. If that's you desired affect.

None of that sounds perfect, a more perfect solution would be to look at the flow of you app and perhaps reevaluate how things are presented.

-1
votes

I think it is a problem of iOS7 where layouts of Views has been slightly changed. I had similar problem with a pop modal custom View where I noticed the same behavior. I solved by adding this line in the viewDidLoad

self.edgesForExtendedLayout = UIRectEdgeNone;

Maybe it could be useful for some hint to your problem.

-1
votes

There is definitely a problem with UIImagePicker (or more broadly, UINavigationController) not obeying any status bar settings in the application.

This has been discussed and solved by reapply your status bar settings in the UINavigationController delegate (see UIImagePickerController breaks status bar appearance).

In your case, you might want to call self.edgesForExtendedLayout = UIRectEdgeNone; or try ressetting some status bar properties in the delegate callback

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
-3
votes

Looks like it's an iOS bug and for now it can be solved with such workaround (it's a method of 'Page' modal view controller)

#define STATUS_BAR_HEIGHT 20
#define IPAD_PORTRAIT_HEIGHT 1004
#define IPAD_LANDSCAPE_HEIGHT 748

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear: animated];

    // In case of iOS7 we need this weird code, because after displaying full screen view controller from 'Page' modal view controller our 'Page' controller
    // incorrectly shifts 20px up
    //
    // I haven't found best solution for now
    // There is my question on SO: http://stackoverflow.com/questions/21146801/ipad-ios7-page-modal-view-controller-strange-behaviour-after-presenting-full
    if (!isIOS6()) {
        [self setNeedsStatusBarAppearanceUpdate];

        UIView *superview = nil;

        // In case of this view controller included in navigationController we have to use superview of navigation's controller view
        if (self.navigationController)
            superview = self.navigationController.view.superview;
        else
            superview = self.view.superview;

        CGRect r = superview.frame;

        // Sometimes we have to fix height + origin, sometimes only height (becase view has bottom magnifying)
        // In landscape orientation we have to fix 'width' instead of 'height', because that view controller always works in 'portrait' mode
        if (self.interfaceOrientation == UIInterfaceOrientationPortrait && r.size.height > IPAD_PORTRAIT_HEIGHT) {
            r.origin.y = STATUS_BAR_HEIGHT;
            r.size.height = IPAD_PORTRAIT_HEIGHT;
        }
        else if (self.interfaceOrientation == UIInterfaceOrientationMaskPortraitUpsideDown && r.size.height > IPAD_PORTRAIT_HEIGHT) {
            r.size.height = IPAD_PORTRAIT_HEIGHT;
        }
        else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft && r.size.width > IPAD_LANDSCAPE_HEIGHT) {
            r.size.width = IPAD_LANDSCAPE_HEIGHT;
            r.origin.x = STATUS_BAR_HEIGHT;
        }
        else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeRight && r.size.width > IPAD_LANDSCAPE_HEIGHT) {
            r.size.width = IPAD_LANDSCAPE_HEIGHT;
        }

        superview.frame = r;
    }
}