带有嵌入式导航控制器的 Popover 不考虑后面导航的大小

我有一个 UIPopoverController 托管一个 UINavigationController,它包含一个视图控制器的小层次结构。

我遵循文档,对于每个视图控制器,我设置视图的 popover-context 大小如下:

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

(每个控制器的大小不同)

这和我在层次结构中向前导航时所期望的一样——弹出窗口自动生成大小变化的动画,以对应于按下的控制器。

然而,当我通过导航栏的 Back 按钮在视图堆栈中导航“ Back”时,弹出窗口并没有改变大小——它仍然保持最深视图的大小。对我来说,这似乎是错误的; 我希望弹出窗口能够尊重通过视图堆栈弹出时设置的大小。

我错过了什么吗?

谢谢。

46707 次浏览

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.

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

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

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.

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

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

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!

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;

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

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

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

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.

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;

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

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

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

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

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.

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

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

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

-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navigationController.preferredContentSize = CGSizeMake(320, 500);}
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 !