70

I have a popover containing a UINavigationController. I can display the popover fine, and it contains the navController just fine. The navController contains a tableView and when I select an item it creates a new detail view:

     DeviceDetailViewController *detailViewController = 
[[[DeviceDetailViewController alloc] initWithNibName:@"DeviceDetailViewController" bundle:nil] autorelease];

I then push it the new view controller:

    [self.navigationController pushViewController:detailViewController animated:YES];

This is when the problem occurs: after pushing the new view the popover resizes to the maximum height available on the iPad.

I have tried setting the height of all the views in the xib to fixed height rather than flexible. I have tried explicitly setting the height of the popover. I have also tried using different view controllers as the child view. The problem remains: the popover wants to resize itself to max height automatically whenever a new view is pushed to the navigation controller.

Here's a question which discusses trying to deliberately control the size of the popover when pushing new views:

I thought this might be a brute force method to control the size. Strangely enough, though, it actually causes some quick graphics quirks (as if the view were being freshly animated in) followed by continuing to resize as described above.

In other words, something is literally forcing the popover to its maximum height, and it seems to occur after all delegate methods have been called.

Is it the navigation controller? Has anyone seen this kind of thing?

Community
  • 1
  • 1
SG1
  • 718
  • 1
  • 6
  • 4

13 Answers13

122

This fixed it for me after I had the same issue (coincidently also today):

EDIT : As contentSizeForViewInPopover is deprecated in iOS7.0 so use preferredContentSize.

Original answer below:

In your detailViewController add this:

- (void)viewWillAppear:(BOOL)animated {

    CGSize size = CGSizeMake(320, 480); // size of view in popover
    self.contentSizeForViewInPopover = size;

    [super viewWillAppear:animated];

}

You also want to add something similar to your original DeviceDetailViewController to prevent resizing when tapping the back NavbarItem.

Craig
  • 8,093
  • 8
  • 42
  • 74
borked
  • 1,550
  • 1
  • 11
  • 10
  • 4
    Thanks! I had the same problem and UIViewController's contentSizeForViewInPopover attribute fixed it. – titaniumdecoy Sep 03 '10 at 22:30
  • 2
    In at least one case, I still had to explicitly set the popup height itself (if the popover controller was already visible with a defined height). But otherwise this worked for returning to a prior page in a navigation controller. – Kendall Helmstetter Gelner Aug 22 '11 at 05:23
  • WOW! This was hard to find. Thanks a lot. This is really helping. If one was rotating the view controller inside a popup manually, all pushed view controllers will change their size. Using this workaround makes everything smooth. I was searching for Days for a solution like this. – JackPearse Dec 15 '11 at 09:13
  • Additionally you can use "self.contentSizeForViewInPopover = self.navigationController.view.frame.size;". Then all child view controllers are using the size of the navigation controller. This is helpful if you start the popover programatically. The size of the navController can be set before the popover starts and all subviews will use this size. – JackPearse Dec 15 '11 at 09:21
  • @JackPearse: I liked that idea, not having to hardcode some numbers, but (in my case) this grows the box by the height of the navigation bar per transition. Unfortunately. – mvds Dec 23 '11 at 01:50
  • This doesn't seem to resolve it for me. I'm pushing on a UITableViewController subclass, without a nib. I'm gonna fight it for a bit longer... – bandejapaisa Feb 28 '12 at 20:57
  • @bandejapaisa this is probably too late, but since I just came across this: adding that code to viewWillAppear only applied the effect to my view controllers after I rotated them or pushed/popped from my navigation controller's stack. To get the initial size applied, I also had to set the popover's instance popoverContentSize property (as well as setting the contentSizeForViewInPopover in the navigationController itself). – André Morujão Jun 12 '12 at 09:59
  • Yes, I got it working eventually and I think I ended up doing that too. Thanks – bandejapaisa Jun 12 '12 at 11:35
  • 1
    This doesnt work for me, needed to put it in viewDidLoad, not viewWillAppear – daihovey Jan 10 '13 at 00:31
  • This isn't working for me, popping a view controller doesn't restore the height of that controller although it does restore the correct width for that controller. Please see my open repo, https://bitbucket.org/danielphillips/uipopovercontroller-resize-bug – Daniel May 14 '13 at 21:07
  • if you are using navigationController inside popover and problem is occurring even after setting preferredContentSize. Try [self.parentPopOverController setPopoverContentSize:CGSizeMake(250, 400) animated:YES]; before pushing the UIViewController – Hassy May 24 '14 at 08:16
30

Much like handling it in viewWillAppear, another way to deal with this is to override contentSizeForViewInPopover. Very terse:

-(CGSize)contentSizeForViewInPopover
{
    return self.view.bounds.size;
}
Peter DeWeese
  • 18,141
  • 8
  • 79
  • 101
  • Works with iOS5.borked solution didn't seem to work with ios5. – palaniraja Mar 14 '12 at 17:50
  • 1
    In my case, this just made the popover the size of the entire screen. – Greg Maletic Jul 12 '12 at 18:16
  • @Greg Maletic, which view was `self.view` in your case? I just tested it again and it still works. – Peter DeWeese Jul 13 '12 at 11:49
  • @PeterDeWeese see the header on that property: `contentSizeForViewInPopover` allows you to set the size of the content from within the view controller. This property is read/write, **and you should generally not override it**. – Till Aug 08 '12 at 19:25
  • @Till still setting the property at contentViewController `viewWillAppear` never worked for me while overriding the getter always did. – A-Live Oct 22 '12 at 11:24
  • You want to grab the size of the superview's bounds, not the view's. – Ben Jackson Feb 18 '13 at 23:21
30

For IOS5

I recommend you do it in:

- (void)viewDidLoad {
    [super viewDidLoad];

    CGSize size = CGSizeMake(320, 480); // size of view in popover
    self.contentSizeForViewInPopover = size;

}
Flexo
  • 87,323
  • 22
  • 191
  • 272
MUH Mobile Inc.
  • 1,462
  • 1
  • 16
  • 25
  • This isn't working for me, popping a view controller doesn't restore the height of that controller although it does restore the correct width for that controller. Please see my open repo, https://bitbucket.org/danielphillips/uipopovercontroller-resize-bug – Daniel May 14 '13 at 21:15
11

I had a similar issue.

I had a popover present from a button in a toolbar. The popover was set to a specific size. It was a table view. When the table row was selected, a new view controller with a navigation controller was called.

When the back button was selected, the popover became the default size (320x1100 I believe), instead of the specific size that I desired.

The original code was:

  MyTableViewController *myVC = [[MyTableViewController alloc] init];
  UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:myVC];

  UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:navController];
  popover.delegate = self;

  popover.popoverContentSize = CGSizeMake(400.0, 500.0);

  [myVC release];
  [navController release];
  [popover release];

I added one line to solve the problem. Granted it is kind of a work around because I had to subtract the height of the header. Maybe one of you could enlighten me with a better method. Anyway, it works.

  MyTableViewController *myVC = [[MyTableViewController alloc] init];

  UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:myVC];

  UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:navController];
  popover.delegate = self;

  popover.popoverContentSize = CGSizeMake(400.0, 500.0);

  //Subtract the height of the header to match the total popover size (not just the view).
  myVC.contentSizeForViewInPopover = CGSizeMake(400.0, 500-44);

  [myVC release];
  [navController release];
  [popover release];

I believe that when a nav controller is involved, and the back button is pressed, it causes the popover to default to its default size. By adding the contentSizeForViewInPopover property for the view controller myVC, it forces the specific size.

Hope this is helpful.

Kurt

Kurt
  • 855
  • 2
  • 12
  • 22
  • Other options didn't work when i replaced an existing view controller with a new one in an already resized popover controller, this option did work – Yogev Shelly Jul 21 '12 at 23:19
9

For iOS 7 use the following:

- (void)viewDidLoad
{
    [super viewDidLoad];

    CGSize size = CGSizeMake(320, 768); // size of view in popover
    self.preferredContentSize = size;
}

UIViewController.contentSizeForViewInPopover has been deprecated first in iOS 7.

Abdullah Umer
  • 4,234
  • 5
  • 36
  • 65
6

In response to graphical glitches with animations:

The UIPopoverController animations conflict with the UINavigation controllers animations, if you create the popover with a UINavigationController inside it. It results in graphical glitches when animating. To fix the issue, set animated parameter to false when pushing other controllers, or when displaying the toolbar.

Pushing View Controllers:

[self.navigationController pushViewController:detailViewController animated:NO];

Making the Toolbar visible:

[[self navigationController] setToolbarHidden:NO animated:NO]; 

Setting the animated:NO will make the animations look correct in a UIPopoverController.

Paul Solt
  • 8,375
  • 5
  • 41
  • 46
  • The animation conflicts between UIPopoverController and [UINavigationController setToolbarHidden:animated:] was a problem I was facing. Good tip, as the fix is not intuitive. setToolbarHidden with no animation will actually show/hide the toolbar with animation handled by the UIPopoverController, as you say. Very subtle but annoying gotcha. – Chris Miles Feb 18 '11 at 07:42
5

Why not just set the contentSizeForViewInPopover before you push the next controller onto the navigation stack? No need to set sizes in viewWillAppear and such.

[nextController setContentSizeForViewInPopover:[self contentSizeForViewInPopover]];
[[self navigationController] pushViewController:nextController animated:YES];

Works on IOS 5.1

MrJre
  • 7,082
  • 7
  • 53
  • 66
4

Slight varient on borked's advice (which pointed me in the right direction, thanks for that!), here's what I do when pushing a new controller to maintain the size before pushing it:

productViewController.contentSizeForViewInPopover = self.view.bounds.size;
self.contentSizeForViewInPopover = self.view.bounds.size;

[self.navigationController pushViewController:productViewController animated:YES];

I like this because I don't have to hardcode the popover values in every view controller (good since I use them at various heights).

The self.contentSizeForViewInPopover line is to preserve the size when the user hits back. I guess you could put this line somewhere else, like viewWillAppear or wherever you like.

Seems to work...

William Denniss
  • 16,089
  • 7
  • 81
  • 124
2

In the -(void)viewDidLoad of all the view controllers you are using in navigation write the code:

self setContentSizeForViewInPopover:CGSizeMake(320, 500)];
MouzmiSadiq
  • 2,069
  • 3
  • 18
  • 21
2

There are two ways to set the contentSizeForViewInPopover in the storyboard. You can set your view controllers that are with your navigation controller, to FreeForm and set the root views to the desired size. Or, you can keep the simulated metric as inferred and check "Use Explicit Size" and set the size you want there.

Then, in each view controller that is within your navigation controller, add the following...

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

     ["yourpopoverController" setPopoverContentSize:CGSizeMake(self.
           contentSizeForViewInPopover.width, seld.contentSizeForViewInPopover.height + self.
           navigationController.navigationBar.frame.size.height) animated:YES];
}

In the transition animation, the new view will come in aligned with the top, and then the height will be adjusted accordingly.

This way, you don't have to override contentSizeForViewInPopover, or specify some other size specifically in your view controllers. It's all in the storyboard.

If one of your view controllers has a variable height, then you do need to override contentSizeForViewInPopover in that view like so...

- (CGSize)contentSizeForViewInPopover {
    return CGSizeMake(450, "calculate your height here");
}
Matt Becker
  • 2,338
  • 1
  • 28
  • 36
1

This stuff may have worked once but not with xCode 6 wherein contentSizeForViewInPopover is deprecated. Luckily it's respected at storyboard load time. I downloaded an xml editor (Xmplify) and hacked the storyboard. Set the key contentSizeForViewInPopover to the size you want. Save and replace (make a copy first) storyboard.

More specifically :

< viewController>

     <value key="contentSizeForViewInPopover" type="size" width="450" height="280" / >

</viewController>
Joe313
  • 11
  • 3
0

Don't work for me, when i use this:

[UIPopoverController 
    [UINavigationController] = root vc = [UIViewController vc1] => [UIViewController vc2] 
]

When appear popover is all good, press button on vc1 and push vc2 to navigation controller

Next return to vc1 (root) pressing button in vc2 (popToRootViewController method);

We can see that popover change own size, but vc1 size is old... WHAT IS THIS???

Ok, now work... Add popover property in my controller

self.contentSizeForViewInPopover = (CGSize){400, 200};
self.navigationController.contentSizeForViewInPopover = self.contentSizeForViewInPopover;
self.popover.popoverContentSize = self.contentSizeForViewInPopover;
iTux
  • 1,946
  • 1
  • 16
  • 20
0

Running Swift 4 and iOS 11 the only possible solution was for me. To use showViewController:sender: and showDetailViewController:sender: instead of presentViewController:animated:completion:.

From the Apple Doc

The showViewController:sender: and showDetailViewController:sender: methods offer the most adaptive and flexible way to display view controllers. These methods let the presenting view controller decide how best to handle the presentation. For example, a container view controller might incorporate the view controller as a child instead of presenting it modally. The default behavior presents the view controller modally.

BilalReffas
  • 8,132
  • 4
  • 50
  • 71