I have been working on app for months without any popover issues. All of a sudden today popovers have gone to hell.
I have tried this a few different ways and no matter when I dismiss the popover by pressing a button in the popover OR tapping outside of the popover the app crashes.
Using Instruments, I can see there is a zombie reference that only happens after the popover has been dismissed:
*** -[UITransitionView willRemoveSubview:]: message sent to deallocated instance 0x7f9985bcd980
So I have logged that stack and there isn't much help as the whole stack has to do with UIKit and built in animations, etc.
Using standard UIPopovers
self.popover = [[UIPopoverController alloc] initWithContentViewController:self.navVC];
self.popover.backgroundColor = [UIColor accentColor];
self.popover.delegate =self;
[self.seatingChartPopover presentPopoverFromRect:CGRectMake(x,y, 1, 1)
inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
Or use the new UIPopoverPresentationController method in ios8
self.navVC.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController* popover = self.navVC.popoverPresentationController;
self.navVC.preferredContentSize = [self.childVC preferredContentSize];
popover.sourceView = self.view;
popover.sourceRect = CGRectMake(x,y, 1, 1);
popover.permittedArrowDirections = UIPopoverArrowDirectionAny;
[self presentViewController:self.seatingChartNavController animated:YES completion:nil];
I have made sure that all of the view controllers involved have a strong reference.
As I mentioned, I have not changed the the code that presents this popover in months and it's been working fine until today. I have spent hours debugging this to no avail.
Also, If I just present self.navVC as a normal modal view, the view displays fine and dismisses fine. It's only when it's set as a popover that it fails
Thanks in advance for any help.
I'm ashamed to admit that I accidentally put a dealloc method in one of my UIView Categories.
I am surprised this didn't bring up any sort of compiler warning, but after removing that there hasn't been any more bizarre crashing.
Related
I am using this code:
mediaLibraryPopover = [[UIPopoverController alloc]
initWithContentViewController:avc];
[self.mediaLibraryPopover presentPopoverFromRect:[theButton bounds]
inView:theButton
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
And I am getting this warning in Xcode 7:
UIPopoverController is deprecated, first deprecated in iOS 9.0 - UIPopoverController is deprecated. Popovers are now implemented as UIViewController presentations. Use a modal presentation style of UIModalPresentationPopover and UIPopoverPresentationController.
You no longer need UIPopoverController for presenting a view controller.
Instead you can set the modalPresentationStyle of view controller to UIModalPresentationPopover.
You can use the following code for that:
avc.modalPresentationStyle = UIModalPresentationPopover;
avc.popoverPresentationController.sourceView = theButton;
[self presentViewController:avc animated:YES completion:nil];
UIModalPresentationPopover
In a horizontally regular environment, a
presentation style where the content is displayed in a popover view.
The background content is dimmed and taps outside the popover cause
the popover to be dismissed. If you do not want taps to dismiss the
popover, you can assign one or more views to the passthroughViews
property of the associated UIPopoverPresentationController object,
which you can get from the popoverPresentationController property.
In a horizontally compact environment, this option behaves the same as
UIModalPresentationFullScreen.
Available in iOS 8.0 and later.
Reference UIModalPresentationStyle Reference
You need to set either sourceView or barButtonItem property, else it will crash with the following message:
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'UIPopoverPresentationController (***) should have a non-nil
sourceView or barButtonItem set before the presentation occurs.'
For anchoring the popover arrow correctly, you need to specify the sourceRect property also.
avc.modalPresentationStyle = UIModalPresentationPopover;
avc.popoverPresentationController.sourceView = self.view;
avc.popoverPresentationController.sourceRect = theButton.frame;
[self presentViewController:avc animated:YES completion:nil];
Refer sourceView and sourceRect for more details.
Apple has the official way to present and configure popovers for iOS8 here: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIPopoverPresentationController_class/index.html
While similar to #MidhunMP's answer, it's worth noting the paragraph:
Configuring the popover presentation controller after calling
presentViewController:animated:completion: might seem
counter-intuitive but UIKit does not create a presentation controller
until after you initiate a presentation. In addition, UIKit must wait
until the next update cycle to display new content onscreen anyway.
That delay gives you time to configure the presentation controller for
your popover.
Configuration and responding to events can also be done via a delegate if you wanted (https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIPopoverPresentationControllerDelegate_protocol/index.html).
An example, setting aside the use of the delegate:
// Present the controller using the popover style.
controller.modalPresentationStyle = UIModalPresentationPopover;
[self presentViewController:controller animated:YES completion:nil];
// Popover presentation controller was created when presenting; now configure it.
UIPopoverPresentationController *presentationController =
[controller popoverPresentationController];
presentationController.permittedArrowDirections = UIPopoverArrowDirectionLeft;
presentationController.sourceView = containerFrameOfReferenceView;
// arrow points out of the rect specified here
presentationController.sourceRect = childOfContainerView.frame;
But you'll also want to dismiss this. To do so without using a delegate, your presenting controller can just call:
[self dismissViewControllerAnimated:YES completion:nil];
But what if I rotate my device, and the popover doesn't point to the right area? Your presenting controller can handle it:
// Respond to rotations or split screen changes
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:nil
completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// Fix up popover placement if necessary, after the transition.
if (self.presentedViewController) {
UIPopoverPresentationController *presentationController =
[self.presentedViewController popoverPresentationController];
presentationController.sourceView = containerFrameOfReferenceView;
presentationController.sourceRect = childOfContainerView.frame;
}
}];
}
If wanted directly from Button Action then this code can be used
SecondViewController *destinationViewController = (SecondViewController *)[self.storyboard instantiateViewControllerWithIdentifier:#"second"];
destinationViewController.modalPresentationStyle = UIModalPresentationPopover;
destinationViewController.popoverPresentationController.sourceView = self.customButton;
// Set the correct sourceRect given the sender's bounds
destinationViewController.popoverPresentationController.sourceRect = ((UIView *)sender).bounds;
[self presentViewController:destinationViewController animated:YES completion:nil];
EDIT: Regarding the down-votes. First a fun and relevant story:
http://www.folklore.org/StoryView.py?story=Negative_2000_Lines_Of_Code.txt
I posted the answer below in order to help coders that might have been stuck in the mindset that iPad/iPhone still needed separate execution paths when presenting a media picker UI. At the time of my original post this was not clear in the other answers. This solution path simplified my code and the future maintenance of my App. I think others might find this perspective useful because sometimes removing the right lines of code can be a good solution.
End of edit.
If you already have a working program and just want to get rid of the depreciation warning this could work for you.
In my code, and in my understanding, Popovers were introduced for the iPad and are iPad specific. Apple seems to have changed that. So, if you already have a working program that uses something like this:
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
// use popovers
else
// don't use popovers
what you can do is just get rid of the iPad specific code (which is probably the code using Popovers) and have your program run the same instructions for both iPhone and iPad. This worked for me.
I have a UITableViewController which I present modally (full screen) on iPad.
Since ios11 (and never before) I've got a lots of strange display problem where the UITableViewController is apparently, at some point, unable to properly compute its heights, and when I click on a cell actions for another index path is triggered. The controller contains cells which is defined in a xib and the problem disappears if I replace this cell by a vanilla UITableViewCell. On the other hand, fixing the height of the cell from heightForRowAtIndexPath does not resolve the issue.
Now the strange thing is, if this TableViewController is not presented within a new NavigationController but is simply pushed from the current view controller, everything works (almost) fine.
I am presenting my controller like that:
MyTableViewController *tvc = [[MyTableViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:tvc];
[self.navigationController presentViewController:navController animated:YES completion:nil];
Is there anything obviously wrong with the above or that could explain what I am getting ?
I've recently been updating an application to replace instances of the deprecated UIPopoverController with UIViewControllers presented modally, and when I create one, it takes about 250% as long to appear. Also, setting animated:NO in the presentViewController method also seems to have no effect.
Does anyone know why this might be?
Here's some of the relevant code I've written (it's in an IBAction from a UIButton being pressed):
UIViewController *popover = [[MyViewControllerForPopover alloc] initWithNibName:nibName bundle:nil];
popover.modalPresentationStyle = UIModalPresentationPopover;
popover.preferredContentSize = popover.view.frame.size;
popover.popoverPresentationController.sourceView = parentView;
popover.popoverPresentationController.sourceRect = location;
popover.popoverPresentationController.permittedArrowDirections = arrows;
[parentViewController presentViewController:popover animated:NO completion:nil];
I tried setting modalTransitionStyle, but it didn't seem to have any effect on the style or duration of animation. What I see is a delay, followed by the animation, so it looks like there's an extended amount of time spent setting up the view controller.
This is the same view controller that used to be put inside the UIPopover, via the initWithContentViewController init method, so I can't understand why this is slower.
Thanks
MyVC* bottle = [[MyVC alloc] initWithDelegate:self];
bottle.title = #"bottle";
if ( water == nil )
water = [[UIPopoverController alloc] initWithContentViewController:bottle];
else
water.contentViewController = bottle;
This code runs once....when I click again on UITableViewCell my app crashes..
I am unable to figure it Out Why??
ERROR
-[UIPopoverController setContentViewController:animated:] can only be called after the popover has been presented.'
NOTE When Change my code this to following it works:
MyVC* bottle = [[MyVC alloc] initWithDelegate:self];
bottle.title = #"bottle";
if ( water == nil )
water = [[UIPopoverController alloc] initWithContentViewController:bottle];
else
water = [[UIPopoverController alloc] initWithContentViewController:bottle];
But i uses memory as double allocation is present.How can I resolve this
You are making a confusion between initialisation, presentation, and content modification of your UIPopoverController.
It is OK to store your popover on an instance variable, to avoid recreating it every time. It doesn't cause any trouble either to set the contentViewController to a new instance of "Bottle" if that "water" exists already.
But your error messages says it all: -[UIPopoverController setContentViewController:animated:] : you are trying to change the content of your popover with an animation. It means, your popover must already be presented (i.e. visible). For doing so, use one of the two methods starting with presentPopoverFrom... before changing its content.
In other words, only once your popover is presented, you can animate a change of content. Note also that it makes no sense to animate a change when your popover is not yet presented / visible. Use the setter .contentViewController = for that (or the non-animated method).
A note on your edit/note: Writing the same allocation in the two branches of the if/else clause is pointless. And it doesn't cause the system to use twice memory. Because when the compiler sees the second line ("else"...), it first releases the existing instance of the popovercontroller before allocating the new one and making the "water" instance variable point to it.
If you want to push then try this
MyVC *Newpage = [[MyVC alloc] initWithNibName:#"MyVC" bundle:nil];
[self.navigationController pushViewController:Newpage animated:YES];
If you want too go back from this page i.e pop then use this
You are using wrong code.
[self.navigationController popViewControllerAnimated:YES];
There is problem in hiding the popover view. Check if presented correctly.
if (popover == nil) {
popover = [[UIPopoverController alloc] initWithContentViewController: MYVC];
popover.delegate = self;
[self.tablesPopoverController presentPopoverFromBarButtonItem:sender
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
Use delegate methods to check if popover is visible or not.
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
// do something now that it's been dismissed
}
I am converting an iPhone App to an iPad App. I wish to present several of the view controllers as popovers that were previously pushed on the iPhone by changing the segue type in the storyboard from push to popover. The view controllers have a title, a done button, and a cancel button in the navigation bar. Now, when I run the iPad App the popover appears but without the navigation bar. How can I get the navigation bar to show. I have already tried expected setting it not hidden in viewWillAppear which did not work.
I have been able to change the segue to an IBAction with the following code and got it to work. It seems somehow the popover does not have a navigation controller so I created one. `
// Device is iPad
addNoteVC = [self.storyboard instantiateViewControllerWithIdentifier:#"addNoteVC"];
popoverNav = [[UINavigationController alloc] initWithRootViewController:addNoteVC];
[addNoteVC setManagedObjectContext:self.managedObjectContext];
[popoverNav.navigationBar setTintColor:[UIColor darkTextColor]];
NSDictionary *textAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
[UIColor darkTextColor],NSForegroundColorAttributeName,nil];
[popoverNav.navigationBar setTitleTextAttributes:textAttributes];
[addNoteVC setSelectedClient:selectedClient];
[addNoteVC setAddNoteTableViewControllerDelegate:self];
popover = [[UIPopoverController alloc] initWithContentViewController: popoverNav];
[popover setBackgroundColor:[UIColor groupTableViewBackgroundColor]];
popover.delegate = self;
[popover presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
`
While the above code works it creates significant differences from the iPhone App code. I would rather that the iPhone and iPad stayed the same as much as possible. If adding a another navigation controller for the popover is the only solution I would prefer to do this in a custom segue so the view controller code can stay the same. In addition that approach would have the economy of one custom segue instead of changes for every view controller presented as a popover. If that indeed is the best solution, details on the custom segue would be appreciated and accepted.