89

I have a UIPopoverController hosting a UINavigationController, which contains a small hierarchy of view controllers.

I followed the docs and for each view controller, I set the view's popover-context size like so:

[self setContentSizeForViewInPopover:CGSizeMake(320, 500)];

(size different for each controller)

This works as expected as I navigate forward in the hierarchy-- the popover automatically animates size changes to correspond to the pushed controller.

However, when I navigate "Back" through the view stack via the navigation bar's Back button, the popover doesn't change size-- it remains as large as the deepest view reached. This seems broken to me; I'd expect the popover to respect the sizes that are set up as it pops through the view stack.

Am I missing something?

Thanks.

Julian
  • 9,299
  • 5
  • 48
  • 65
Ben Zotto
  • 70,108
  • 23
  • 141
  • 204

21 Answers21

95

I was struggling with the same issue. None of the above solutions worked for me pretty nicely, that is why I decided to do a little investigation and find out how this works.

This is what I discovered:

  • When you set the contentSizeForViewInPopover in your view controller it won't be changed by the popover itself - even though popover size may change while navigating to different controller.
  • When the size of the popover will change while navigating to different controller, while going back, the size of the popover does not restore
  • Changing size of the popover in viewWillAppear gives very strange animation (when let's say you popController inside the popover) - I'd not recommend it
  • For me setting the hardcoded size inside the controller would not work at all - my controllers have to be sometimes big sometimes small - controller that will present them have the idea about the size though

A solution for all that pain is as follows:

You have to reset the size of currentSetSizeForPopover in viewDidAppear. But you have to be careful, when you will set the same size as was already set in field currentSetSizeForPopover then the popover will not change the size. For this to happen, you can firstly set the fake size (which will be different than one which was set before) followed by setting the proper size. This solution will work even if your controller is nested inside the navigation controller and popover will change its size accordingly when you will navigate back between the controllers.

You could easily create category on UIViewController with the following helper method that would do the trick with setting the size:

- (void) forcePopoverSize {
    CGSize currentSetSizeForPopover = self.contentSizeForViewInPopover;
    CGSize fakeMomentarySize = CGSizeMake(currentSetSizeForPopover.width - 1.0f, currentSetSizeForPopover.height - 1.0f);
    self.contentSizeForViewInPopover = fakeMomentarySize;
    self.contentSizeForViewInPopover = currentSetSizeForPopover;
}

Then just invoke it in -viewDidAppear of desired controller.

pkamb
  • 33,281
  • 23
  • 160
  • 191
krasnyk
  • 3,458
  • 3
  • 24
  • 20
  • 1
    The above category is the only (sane) way I can make it work. Thanks. – RickiG Feb 25 '11 at 16:16
  • This works. I need to figure out how to keep the table view from becoming "black" in the contracting area when the popover shrinks, but this solution does (finally!) really allow the popover to move to the correct size for each stack level. Thanks! – Ben Zotto Mar 10 '11 at 18:25
  • 2
    I wrapped it in [UIView beginAnimations:nil context:nil]; and [UIView commitAnimations]; -- makes it less jarring. – Dustin Jun 25 '11 at 16:47
  • 2
    For me, using self.contentSizeForViewInPopover = CGSizeZero; saved a line and had the same effect. Many thanks! – rob5408 Mar 28 '13 at 20:14
  • I could only get this solution to work if I added self.contentSizeForPopover = CGSizeZero; in my viewWillDisappear method of the VC I was popping from. – LightningStryk Apr 05 '13 at 16:35
  • Only problem I have is the scroll view indicators on my UITableView at first show according to the old height. Anyone know a fix? – elsurudo Apr 06 '13 at 15:17
  • @Clement please take a look at my [answer](http://stackoverflow.com/a/26507012/3151066) which should solve a problem for you on iOS 8. – Julian Oct 22 '14 at 12:02
  • Take a look at my answer below for how I solved this for iOS 8 and iOS 7. http://stackoverflow.com/a/26964838/1219822 – Wesley Filleman Nov 17 '14 at 03:03
  • @JulianKról: Which answer? – testing Dec 10 '14 at 14:22
  • some people down voted (don't know why, no comment just hate) so I removed the answer,sorry – Julian Dec 10 '14 at 14:23
  • @JulianKról: Looks familiar to me. I also have downvotes on my answer without obvious reason. I only delete them if I know they are completely wrong. Perhaps you can undelete them if it's not too late. – testing Dec 10 '14 at 14:46
  • here you are, please check it :) and let me know your opinion – Julian Dec 10 '14 at 14:58
  • In iOS 8 set preferredContentSize property on contentViewController before setting popoverContentSize and it will work. – dispatchMain Apr 29 '15 at 08:31
20

Here's how I solved it for iOS 7 and 8:

In iOS 8, iOS is silently wrapping the view you want in the popover into the presentedViewController of the presentingViewController view controller. There's a 2014 WWDC video explaining what's new with the popovercontroller where they touch on this.

Anyways, for view controllers presented on the navigation controller stack that all want their own sizing, these view controllers need (under iOS 8) to call this code to dynamically set the preferredContentSize:

self.presentingViewController.presentedViewController.preferredContentSize = CGSizeMake(320, heightOfTable);

Replace heightOfTable with your computed table or view height.

In order to avoid a lot of duplicate code and to create a common iOS 7 and iOS 8 solution, I created a category on UITableViewController to perform this work when viewDidAppear is called in my tableviews:

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

Category.h:

#import <UIKit/UIKit.h>

@interface UITableViewController (PreferredContentSize)

- (void) setPopOverViewContentSize;

@end

Category.m:

#import "Category.h"

@implementation UITableViewController (PreferredContentSize)

- (void) setPopOverViewContentSize
{
    [self.tableView layoutIfNeeded];
    int heightOfTable = [self.tableView contentSize].height;

    if (heightOfTable > 600)
        heightOfTable = 600;

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0)
            self.preferredContentSize=CGSizeMake(320, heightOfTable);
        else
            self.presentingViewController.presentedViewController.preferredContentSize = CGSizeMake(320, heightOfTable);
    }
}

@end
Wesley Filleman
  • 661
  • 5
  • 12
  • Your tip with `presentingViewController` works. If I set the `preferredContentSize` in `viewDidLoad` there is a strange behavior: Navigating back from another view controller in the popover leads to a wrong popover size change. It appears like a popover size of zero was set, but the size is correct. In such a case the popover takes the whole screen height. Do you know perhaps why this is the case? I don't have the restriction with 600 points implemented because in my experience the operating system doesn't let you specify a size higher than the screen size. – testing Dec 10 '14 at 16:31
  • Try viewDidAppear instead of viewDidLoad so that the code executes when you navigate back up the stack. – Wesley Filleman Dec 11 '14 at 18:04
  • That was the way I took. But I don't get why it won't work if you set this in `viewDidLoad` ... – testing Dec 12 '14 at 07:49
  • 1
    Unfortunately that's just not the way Apple wrote the view stack. These contentSize values don't really persist once the viewController is hidden from view. That's why you have to "remind" the popover each time a view comes into the foreground by either a push or pop. My recommendation is to file a bug report with Apple if you think the popover should retain this information. – Wesley Filleman Dec 12 '14 at 20:13
12

This is an improvement on krasnyk's answer.
Your solution is great, but it isn't smoothly animated.
A little improvement gives nice animation:

Remove last line in the - (void) forcePopoverSize method:

- (void) forcePopoverSize {
    CGSize currentSetSizeForPopover = self.contentSizeForViewInPopover;
    CGSize fakeMomentarySize = CGSizeMake(currentSetSizeForPopover.width - 1.0f, currentSetSizeForPopover.height - 1.0f);
    self.contentSizeForViewInPopover = fakeMomentarySize;
}

Put [self forcePopoverSize] in - (void)viewWillAppear:(BOOL)animated method:

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

    [self forcePopoverSize];
}

And finally - set desired size in - (void)viewDidAppear:(BOOL)animated method:

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

    CGSize currentSetSizeForPopover = self.contentSizeForViewInPopover;
    self.contentSizeForViewInPopover = currentSetSizeForPopover;
}
DarkDust
  • 90,870
  • 19
  • 190
  • 224
adnako
  • 1,287
  • 2
  • 20
  • 30
8

You need to set the content size again in viewWillAppear. By calling the delagate method in which you set the size of popovercontroller. I had also the same issue. But when I added this the problem solved.

One more thing: if you are using beta versions lesser than 5. Then the popovers are more difficult to manage. They seem to be more friendly from beta version 5. It's good that final version is out. ;)

Hope this helps.

Madhup Singh Yadav
  • 8,110
  • 7
  • 51
  • 84
  • I hate this too. It caught me as well. Apple: why can't we lock a popover with navcontroller to a specified size?! – Jann May 04 '10 at 06:39
  • 2
    Setting the content size in `viewWillAppear` didn't work for me. Setting the popover's size explicitly did work, but that's ghetto. – Ben Zotto May 13 '10 at 19:19
  • @quixoto I don't what your problem was but I still use the same thing and it works perfectly. – Madhup Singh Yadav Dec 22 '10 at 05:05
5

In the -(void)viewDidLoad of all the view controllers you are using in navigation controller, add:

[self setContentSizeForViewInPopover:CGSizeMake(320, 500)];
Robert
  • 37,670
  • 37
  • 171
  • 213
MouzmiSadiq
  • 2,069
  • 3
  • 18
  • 21
3

I reset the size in the viewWillDisappear:(BOOL)animated method of the view controller that is being navigated back from:

-(void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    CGSize contentSize = [self contentSizeForViewInPopover];
    contentSize.height = 0.0;
    self.contentSizeForViewInPopover = contentSize;
}

Then when the view being navigated back to appears, I reset the size appropriately:

-(void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    CGSize contentSize;
    contentSize.width = self.contentSizeForViewInPopover.width;
    contentSize.height = [[self.fetchedResultsController fetchedObjects] count] *  self.tableView.rowHeight;
    self.contentSizeForViewInPopover = contentSize;
}
mattjgalloway
  • 34,792
  • 12
  • 100
  • 110
Greg C
  • 889
  • 8
  • 6
  • Hmm.. reset was not needed. I put self.contentSizeForViewInPopover = self.view.frame.size in all viewWillAppear of all view controller. – alones Feb 08 '11 at 02:44
  • What would I put for fetchedResultsController fetchedObjects ? Can't get this to work – Jules Apr 30 '14 at 08:16
2

For iOS 8 the following works:

- (void) forcePopoverSize {
    CGSize currentSetSizeForPopover = self.preferredContentSize;
    CGSize fakeMomentarySize = CGSizeMake(currentSetSizeForPopover.width - 1.0f, currentSetSizeForPopover.height - 1.0f);
    self.preferredContentSize = fakeMomentarySize;
    self.navigationController.preferredContentSize = fakeMomentarySize;
    self.preferredContentSize = currentSetSizeForPopover;
    self.navigationController.preferredContentSize = currentSetSizeForPopover;
}

BTW I think, this should be compatible with previous iOS versions...

Zeroid
  • 31
  • 2
  • I had the issue with an app in iOS8 compiled with iOS7 sdk. This worked, thanks. – Climbatize Mar 11 '15 at 14:20
  • To fix sizing when changing rotation, call this method in `willTransitionToTraitCollection`, in `animateAlongsideTransition` completion block. – Geva May 16 '16 at 14:54
2
Well i worked out. Have a look.


Made a ViewController in StoryBoard. Associated with PopOverViewController class.


import UIKit

class PopOverViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        self.preferredContentSize = CGSizeMake(200, 200)

        self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Done, target: self, action: "dismiss:")

    }

    func dismiss(sender: AnyObject) {
        self.dismissViewControllerAnimated(true, completion: nil)
    }
}




See ViewController:


//
//  ViewController.swift
//  iOS8-PopOver
//
//  Created by Alvin George on 13.08.15.
//  Copyright (c) 2015 Fingent Technologies. All rights reserved.
//

import UIKit

class ViewController: UIViewController, UIPopoverPresentationControllerDelegate
{
    func showPopover(base: UIView)
    {
        if let viewController = self.storyboard?.instantiateViewControllerWithIdentifier("popover") as? PopOverViewController {


            let navController = UINavigationController(rootViewController: viewController)
            navController.modalPresentationStyle = .Popover

            if let pctrl = navController.popoverPresentationController {
                pctrl.delegate = self

                pctrl.sourceView = base
                pctrl.sourceRect = base.bounds

                self.presentViewController(navController, animated: true, completion: nil)
            }
        }
    }

    override func viewDidLoad(){
        super.viewDidLoad()
    }

    @IBAction func onShow(sender: UIButton)
    {
        self.showPopover(sender)
    }

    func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
        return .None
    }
}


Note: The func showPopover(base: UIView) method should be placed before ViewDidLoad. Hope it helps !
Alvin George
  • 14,148
  • 92
  • 64
1

if you can imagine the assambler, I think this is slightly better:

- (void) forcePopoverSize {
    CGSize currentSetSizeForPopover = self.contentSizeForViewInPopover;
    self.contentSizeForViewInPopover = CGSizeMake(0, 0);
    self.contentSizeForViewInPopover = currentSetSizeForPopover;
}
user1375355
  • 79
  • 1
  • 4
  • 2
    even better will be using `CGSizeZero` instead making it by yourself in `CGSizeMake(0,0)` – Julian Oct 22 '14 at 09:42
1

The accepted answer is not working fine with iOS 8. What I did was creating my own subclass of UINavigationController for use in that popover and override the method preferredContentSize in this way:

- (CGSize)preferredContentSize {
    return [[self.viewControllers lastObject] preferredContentSize];
}

Moreover, instead of calling forcePopoverSize (method implemented by @krasnyk) in viewDidAppear I decided to set a viewController (which shows popover) as a delegate for previously mentioned navigation (in popover) and do (what force method does) in:

-(void)navigationController:(UINavigationController *)navigationController
      didShowViewController:(UIViewController *)viewController 
                   animated:(BOOL)animated  

delegate method for a passed viewController. One important thing, doing forcePopoverSize in a UINavigationControllerDelegate method is fine if you do not need that animation to be smooth if so then do leave it in viewDidAppear.

Community
  • 1
  • 1
Julian
  • 9,299
  • 5
  • 48
  • 65
  • why down voting? maybe some feedback different than just disagreement :) – Julian Oct 29 '14 at 21:35
  • hello @JulianKról, can you have a look at this http://stackoverflow.com/questions/28112617/change-preferred-content-size-of-popoverviewcontroller-on-fly, I have same problem. – Ranjit Jan 23 '15 at 15:20
  • Thanks, this helped me. Might want to make it clear at the top of your comment that the problem is you need to update the navigationController preferredContentSize as well as the visibleVC preferredContentSize. So setting both of them directly works too. – Michael Kernahan Feb 09 '15 at 16:44
1

For me this solutions works. This is a method from my view controller which extends UITableViewController and is the root controller for UINavigationController.

-(void)viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
     self.contentSizeForViewInPopover = self.tableView.bounds.size;
}

And don't forget to set content size for view controller you gonna push into navigation stack

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath{
    dc = [[DetailsController alloc] initWithBookmark:[[bookmarksArray objectAtIndex:indexPath.row] retain] bookmarkIsNew:NO];
    dc.detailsDelegate = self;
    dc.contentSizeForViewInPopover = self.contentSizeForViewInPopover;
    [self.navigationController pushViewController:dc animated:YES]; 
 }
Hamed Rajabi Varamini
  • 3,439
  • 3
  • 24
  • 38
Koteg
  • 497
  • 7
  • 16
0

All that you have to do is:

-In the viewWillAppear method of the popOvers contentView, add the snippet given below. You will have to specify the popOver's size first time when it is loaded.

CGSize size = CGSizeMake(width,height);
self.contentSizeForViewInPopover = size;
Deepukjayan
  • 3,525
  • 1
  • 20
  • 31
0

I had this issue with a popover controller whose popoverContentSize = CGSizeMake(320, 600) at the start, but would get larger when navigating through its ContentViewController (a UINavigationController).

The nav controller was only pushing and popping custom UITableViewControllers, so in my custom table view controller class's viewDidLoad i set self.contentSizeForViewInPopover = CGSizeMake(320, 556)

The 44 less pixels are to account for the Nav controller's nav bar, and now I don't have any issues anymore.

leukosaima
  • 504
  • 5
  • 6
0

Put this in all view controllers you are pushing inside the popover

CGSize currentSetSizeForPopover = CGSizeMake(260, 390);
CGSize fakeMomentarySize = CGSizeMake(currentSetSizeForPopover.width - 1.0f,
                                      currentSetSizeForPopover.height - 1.0f);
self.contentSizeForViewInPopover = fakeMomentarySize;
self.contentSizeForViewInPopover = currentSetSizeForPopover;
Sishu
  • 1,510
  • 1
  • 21
  • 48
0

Faced the same issue and fixed it by setting content view size to navigation controller and view controller before the init of UIPopoverController was placed.

     CGSize size = CGSizeMake(320.0, _options.count * 44.0);
    [self setContentSizeForViewInPopover:size];
    [self.view setFrame:CGRectMake(0.0, 0.0, size.width, size.height)];
    [navi setContentSizeForViewInPopover:size];

    _popoverController = [[UIPopoverController alloc] initWithContentViewController:navi];
Tomasz Dubik
  • 641
  • 4
  • 6
0

I'd just like to offer up another solution, as none of these worked for me...

I'm actually using it with this https://github.com/nicolaschengdev/WYPopoverController

When you first call your popup use this.

if ([sortTVC respondsToSelector:@selector(setPreferredContentSize:)]) {
   sortTVC.preferredContentSize = CGSizeMake(popoverContentSortWidth,
        popoverContentSortHeight);
}
else 
{
   sortTVC.contentSizeForViewInPopover = CGSizeMake(popoverContentSortWidth, 
        popoverContentSortHeight);
}

Then in that popup use this.

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

  if ([self respondsToSelector:@selector(setPreferredContentSize:)]) {
    self.preferredContentSize = CGSizeMake(popoverContentMainWidth, 
        popoverContentMainheight);
  }
  else 
  {
    self.contentSizeForViewInPopover = CGSizeMake(popoverContentMainWidth, 
        popoverContentMainheight);
  }
}

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

self.contentSizeForViewInPopover = CGSizeZero;

}

Then repeat for child views...

Jules
  • 7,568
  • 14
  • 102
  • 186
0

This is the correct way in iOS7 to do this, Set the preferred content size in viewDidLoad in each view controller in the navigation stack (only done once). Then in viewWillAppear get a reference to the popover controller and update the contentSize there.

-(void)viewDidLoad:(BOOL)animated
{
    ...

    self.popoverSize = CGSizeMake(420, height);
    [self setPreferredContentSize:self.popoverSize];
}

-(void)viewWillAppear:(BOOL)animated
{
    ...

    UIPopoverController *popoverControllerReference = ***GET REFERENCE TO IT FROM SOMEWHERE***;
    [popoverControllerReference setPopoverContentSize:self.popoverSize];
}
anders
  • 4,168
  • 2
  • 23
  • 31
0

@krasnyk solution worked well in previous iOS versions but not working in iOS8. The following solution worked for me.

    - (void) forcePopoverSize {
        CGSize currentSetSizeForPopover = self.preferredContentSize;
       //Yes, there are coupling. We need to access the popovercontroller. In my case, the popover controller is a weak property in the app's rootVC.
        id mainVC = [MyAppDelegate appDelegate].myRootVC;
        if ([mainVC valueForKey:@"_myPopoverController"]) {
            UIPopoverController *popover = [mainVC valueForKey:@"_myPopoverController"];
            [popover setPopoverContentSize:currentSetSizeForPopover animated:YES];
        }
    }

It is not the best solution, but it works.

The new UIPopoverPresentationController also has the resizing issue :( .

Clement Prem
  • 3,112
  • 2
  • 23
  • 43
  • 1
    maybe instead of reaching the view controller from the AppDelegate property consider my solution (seems to be cleaner) – Julian Oct 22 '14 at 12:09
  • Yes, this was the quickest solution without modify any of my existing codebase. Btw vl try ur solution – Clement Prem Oct 23 '14 at 11:30
0

You need to set the preferredContentSizeproperty of the NavigationController in viewWillAppear:

-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navigationController.preferredContentSize = CGSizeMake(320, 500);}
0

I was facing same problem, but you don't want to set contentsize in viewWillAppear or viewWillDisappear method.

AirPrintController *airPrintController = [[AirPrintController alloc] initWithNibName:@"AirPrintController" bundle:nil];
airPrintController.view.frame = [self.view frame];
airPrintController.contentSizeForViewInPopover = self.contentSizeForViewInPopover;
[self.navigationController pushViewController:airPrintController animated:YES];
[airPrintController release];

set contentSizeForViewInPopover property for that controller before pushing that controller to navigationController

Hamed Rajabi Varamini
  • 3,439
  • 3
  • 24
  • 38
Vikas Sawant
  • 106
  • 1
  • 8
0

I've had luck by putting the following in the viewdidappear:

[self.popoverController setPopoverContentSize:self.contentSizeForViewInPopover animated:NO];

Although this may not animate nicely in the case when you're pushing/popping different-sized popovers. But in my case, works perfectly!

Chris
  • 39,719
  • 45
  • 189
  • 235