Popover with embedded navigation controller doesn't respect size on back nav

IosUiviewcontrollerUinavigationcontrollerUipopovercontroller

Ios Problem Overview


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.

Ios Solutions


Solution 1 - Ios

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.

Solution 2 - Ios

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

Solution 3 - Ios

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

Solution 4 - Ios

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.

Solution 5 - Ios

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

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

Solution 6 - Ios

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

Solution 7 - Ios

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...

Solution 8 - Ios

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 !

Solution 9 - Ios

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

Solution 10 - Ios

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

Solution 11 - Ios

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.

Solution 12 - Ios

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

Solution 13 - Ios

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!

Solution 14 - Ios

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;

Solution 15 - Ios

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.

Solution 16 - Ios

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;

Solution 17 - Ios

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

Solution 18 - Ios

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...

Solution 19 - Ios

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

Solution 20 - Ios

@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 :( .

Solution 21 - Ios

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

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBen ZottoView Question on Stackoverflow
Solution 1 - IoskrasnykView Answer on Stackoverflow
Solution 2 - IosWesley FillemanView Answer on Stackoverflow
Solution 3 - IosadnakoView Answer on Stackoverflow
Solution 4 - IosMadhup Singh YadavView Answer on Stackoverflow
Solution 5 - IosMouzmiSadiqView Answer on Stackoverflow
Solution 6 - IosGreg CView Answer on Stackoverflow
Solution 7 - IosZeroidView Answer on Stackoverflow
Solution 8 - IosAlvin GeorgeView Answer on Stackoverflow
Solution 9 - IosKotegView Answer on Stackoverflow
Solution 10 - Iosuser1375355View Answer on Stackoverflow
Solution 11 - IosJulianView Answer on Stackoverflow
Solution 12 - IosVikas SawantView Answer on Stackoverflow
Solution 13 - IosChrisView Answer on Stackoverflow
Solution 14 - IosDeepukjayanView Answer on Stackoverflow
Solution 15 - IosleukosaimaView Answer on Stackoverflow
Solution 16 - IosAlok ChandraView Answer on Stackoverflow
Solution 17 - IosTomasz DubikView Answer on Stackoverflow
Solution 18 - IosJulesView Answer on Stackoverflow
Solution 19 - IosandersView Answer on Stackoverflow
Solution 20 - IosClement PremView Answer on Stackoverflow
Solution 21 - IosPablo Alonso GonzálezView Answer on Stackoverflow