Resize NSPopover after it is shown - xcode

I have an NSPopover who's content in an NSView. I create the popover and add the view like this:
NSPopover *thePopover = [[NSPopover alloc] init];
[thePopover setContentViewController:myViewController];
I then show the popover and set the size:
[thePopover setContentSize:NSMakeSize(300.0f, 160.0f)];
At this point the popover is visible and its content view is correct. Is there a way that I can resize the popover at this point without closing and re-showing it?

I was able to achieve this by calling back to the controller that launched the popover via a delegate protocol and resetting the size using the setContentSize:

From the documentation:
The popover's content size is set to match the size of the content view when the content view controller is set.
So, set the frame of the popover's content view.

NSWindow has private property _popover that store a reference to NSPopover.
You can use it to access to NSPopover instance from any NSViewController (and then call setContentSize: on it) like this:
- (NSPopover *)popover
{
NSWindow *window = self.view.window;
NSPopover *popover = [window valueForKey:#"_popover"];
return popover;
}

Related

Can UIPopoverPresentationController be forced to reposition popover instead of resizing it?

I am using auto layout with Storyboard. I present a popoverPresentationController from a cell rect:
NumberController * viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"NumberController"];
UIPopoverPresentationController *pc = [viewController popoverPresentationController];
pc.delegate = self;
pc.permittedArrowDirections = UIPopoverArrowDirectionAny;
pc.sourceView = tableView;
pc.sourceRect = [tableView rectForRowAtIndexPath:indexPath];
[self.navigationController presentViewController:viewController animated:animated completion:nil];
The popover presents on an iPad in portrait mode with the arrow up.
I rotate the iPad to landscape mode. The popoverPresentationController keeps the same sourceView/sourceRect and properly points to the cell. It also keeps the up arrow.
But it is now at the bottom of the view, so the popover resizes to a shorter height. This is not desired behavior.
If the popover were simply to move to a new position and change the arrow direction, it would not need to resize at all. This is the desired behavior.
I thought the following method might permit me to make changes, but it is not called since the sourceView rect does not change:
- (void)popoverController:(UIPopoverController *)popoverController
willRepositionPopoverToRect:(inout CGRect *)rect
inView:(inout UIView **)view {
}
I have tried to reset the permittedArrowDirections (in preferredContentSize, because this seemed like the most logical place). This does not work (the popover still resizes):
- (CGSize) preferredContentSize {
[super preferredContentSize];
self.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionUnknown;
return CGSizeMake(DEFAULT_POPOVER_WIDTH,DEFAULT_POPOVER_HEIGHT);
}
I simply cannot find a way to force the popoverPresentationController to change arrow direction and reposition the popover instead of resizing the popover. I am beginning to think it is not even possible - but I still hold out hope that I am just missing something.
EDIT: In the meantime, it has occurred to me that maybe a popover is not the best way to present this view if I don't want it resized in iPad. I am going to try it with UIModalPresentationFormSheet presentation. But I would still like to find an answer to this question.
I just ran into the problem where
- (void)popoverController:(UIPopoverController *)popoverController
willRepositionPopoverToRect:(inout CGRect *)rect
inView:(inout UIView **)view {
was not being called because my view controller was detached. There may be a view in your view hierarchy whose view controller has not been added as a child view controller.
I thought the following method might permit me to make changes, but it is not called since the sourceView rect does not change
The sourceView rect does not have to change, just the interface orientation. From the UIPopoverControllerDelegate documentation:
For popovers that were presented using the presentPopoverFromRect:inView:permittedArrowDirections:animated: method, the popover controller calls this method when the interface orientation changes.

UINavigationBar jumps 20pixels during transitionFromViewController

In trying to attempt to use Apple's UIViewController containment I have encountered a problem with animating the transition between two UIViewControllers.
Here is the set up... I have created a UITabBarController and within one of the tabs I have created a UIViewController as a container. This ViewController manages the transition between a UIViewController and a UINavigationController. The before view is:
When the Next button is tapped the view begins it's transition with a flipFromRight transition. During the transition, the Navigation bar is in "to" view but is located 20pixels down from the top edge of the view. Picture below:
The green is the background color of the container view. Once the new view completes the transition, the Navigation bar snaps up to the top of the view and the final result is:
The time to snap in place is independent of the duration of the animation. I achieve the final state that I want, but the transition is a problem.
I have instrumented the viewController lifecycle and the frames of the Navigation bar and the UITableView are as specified in the XIB. The xib looks like this:
Here is the code:
In -viewDidLoad -
_fromVC = [[FromVC alloc] initWithNibName:#"FromVC" bundle:nil delegate:self];
[self addChildViewController:_fromVC];
[self.view addSubview:_fromVC.view];
[_fromVC didMoveToParentViewController:self];
In my button handler -
- (void)buttonSelected
{
//
// Create the "to" View controller
//
ToVC *toVC = [[ToVC alloc] initWithNibName:#"ToVC" bundle:nil];
//
// Create the navigation controller for the study activity
//
_toNavCon = [[UINavigationController alloc] initWithRootViewController:toVC];
[self addChildViewController:_toNavCon];
[_fromVC willMoveToParentViewController:nil];
[self transitionFromViewController:_fromVC
toViewController:_toNavCon
duration:0.7
options:UIViewAnimationOptionTransitionFlipFromRight
animations:nil
completion:^(BOOL finished) {
[_fromVC removeFromParentViewController];
[_toNavCon didMoveToParentViewController:self];
}];
}
There is no code in "to" view controller that changes the appearance of the view controller.
Another bit of information... When I "toggle In-Call status bar" in the simulator the gap at the top of the navigation bar is the height of the In-Call status bar.
I have looked at everything on the web and there is nothing that helps. Has anyone seen this and has anyone fixed it?
I did find an answer!! Subclass UINavigationController and override
- (BOOL)wantsFullScreenLayout{
return NO;
}
Apparently, UINavigationController and UITabController always want the full screen (default YES) - adjusting for the status bar - and you can not set this property any other way. It has been working for me.

How to add the Navigation Bar's view to a PopOver's PassThroughViews?

I have a PopoverController view that allows a user to download a file. On button press, the popOver view will expand in size, display download status, and the main view controller will be obscured by an unhidden "cover" view that has been added to the PopoverController's "passThroughViews" property so that the user can not accidentally dismiss the pop over while the file is downloading.
My problem is that, in storyboards, my main viewController is embedded in a Navigation Controller. I can't seem to cover the navigation controller's bar with a view in the storyboard, and if the user presses anywhere on the navigation bar then the popover will disappear and the user will lose the download's progress bar.
How do I either cover up the navigation bar with my "cover" view, or how do I add the navigation bar's view to my popOverController's passThroughViews?
Opening the Popover from the main viewController:
- (IBAction)openDataOptionsPopOver:(id)sender
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
PopOverViewController *optionsWindow = [storyboard instantiateViewControllerWithIdentifier:#"dataOptions"];
self.popUp = [[UIPopoverController alloc] initWithContentViewController:optionsWindow];
[self.popUp setDelegate:self];
[nextNavButton setEnabled:NO]; //Disabling barButtonItem on the navigationController
optionsWindow.containerPopOver = self.popUp; //Pointer to the popover, to resize it later.
optionsWindow.coverView = self.coverView; //Pointer to the coverView, to (un)hide later
[popUp presentPopoverFromRect:[sender frame] inView:[sender superview] permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES];
}
Setting the passThroughViews property inside of the PopoverViewController:
//Expands the popOver on press of "refreshFileButton" to display progressView
-(void) explodeWindow
{
//setting self.navigationController.view and ...visibleViewController.view here didn't seem to work ...
[containerPopOver setPassthroughViews:[NSArray arrayWithObjects:coverView, nil]];
[containerPopOver setPopoverContentSize:CGSizeMake(600, 400) animated:YES];
[titleBarItem setTitle:#"Downloading File. Please Wait ..."];
[refreshFileButton setHidden:YES];
[progressView setHidden:NO];
[downloadLabel setHidden:NO];
[coverView setHidden:NO];
[progressView setProgress:0.0 animated:NO];
}
I've tried adding self.navigationController.view to passThroughViews with no success--it actually turns out to be a null pointer. And I can't seem to place a UIView at any level in storyboards that will cover all my controls without obscuring the popOver. What am I missing here? And thanks for reading.
Edit:
As Aglaia points out below out, implementing the following, and avoiding passThroughViews, is probably the best way to do this.
- (BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
{
//Don't dismiss our popover when the view covering our controls is present
if([coverView isHidden]){
return YES;
}else{
return NO;
}
}
Maybe there is something I am missing, but why don′t you just implement a new view controller with its navigation bar set to none and present it modally on button press? Then when the download is finished you just dismiss the view controller.
If you want the user to see the underlying view you can use a UIAlertView instead.
Alternatively set you view controller as the delegate of the popover controller and forbid the user to dismiss your popover on touch outside through
- (BOOL) popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
{
return NO;
}
Then when you want to dismiss it call dismissPopoverAnimated:
to cover the whole screen including navigation bar:
[myView setFrame:[[UIScreen mainScreen] bounds];
[self.navigationController.view addSubview:myView];

Is there a way to get the NSPanel a button is belonging to from the sender object ?

Is there a way to get the NSPanel a button is belonging to from the sender object ?
- (void)closeButtonClicked:(id)sender {
... something like [sender getPanel]...
}
Thanks
NSButton is an NSView subclass, NSView has a window property which gives access to the NSWindow it is in. If the button is in an NSPanel then its window property would contain that panel (NSPanel is a subclass of NSWindow). So something like
NSPanel* panel = (NSPanel*)[(NSButton*)sender window];
should get you the panel. I would check that they are of the right kind of class for safety.

Show NSPopover from NSToolbarItem Button

I want to show an NSPopover from an NSToolbarItem button in my toolbar.
(i.e. positioned below the button).
Ideally, I want to pass the NSView of the button to the popover to position it.
My question is, how do I get the NSView of the NSToolbarItem?
[toolbarbutton view] always returns nil.
The answer appears to be in the video for the 2011 WWDC Session 113, "Full Screen and Aqua Changes." Basically, put an NSButton inside the NSToolbaritem and use the view of that.
A blog post is here: http://www.yellowfield.co.uk/blog/?p=33, and a sample project is on github at http://github.com/tevendale/ToolbarPopover
All in the sprit of http://xkcd.com/979!
You can send the action directly from the NSButton enclosed in the NSToolbarItem (which is what you should generally do anyways, consider segmented controls, where each segment has its own target/action), and that will do the trick.
Instead of getting the view from the IBAction sender, connect an IBOutlet directly to the toolbar item and use that to get the relative view:
In your header file:
#property (weak) IBOutlet NSToolbarItem *theToolbarItem;
#property (weak) IBOutlet NSPopover *thePopover;
In your implementation file, to show the popover:
[self.thePopover showRelativeToRect:[[self.theToolbarItem view] bounds] ofView:[self.theToolbarItem view] preferredEdge:NSMinYEdge];
This will also work for showing popups from menu item selections inside a toolbar item.
While I did achieve that the Popover was shown using the approach mentioned by Stuart Tevendale, I did run into problems when I tried to validate (enable / disable) the NSToolbarItems using the NSToolbarDelegate:
-(BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem {
BOOL enable = YES;
NSString *identifier = [toolbarItem itemIdentifier];
// This does never get called because I am using a button inside a custom `NSToolbarItem`
if ([identifier isEqualToString:#"Popover"]) {
return [self someValidationMechanism];
}
// For this the validation works when I am using a standard `NSToolbarItem`
else if ([identifier isEqualToString:#"StandardToolbarItem"]){
return [self someOtherValidationMechanism];
}
return enable;
}
So I would advise not to display a Popover from NSToolbarItem. An alternative might be to show a Page Sheet: How to show a NSPanel as a sheet

Resources