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
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.
The following code should popover a view and the view size should be customized as I customized it by code, but when I run it, I see it's not customized.
Can anyone help?
- (IBAction)barButtonLeft:(id)sender {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
_myViewController = [storyboard instantiateViewControllerWithIdentifier:#"Sort"];
_myViewController.modalPresentationStyle = UIModalPresentationPopover;
self.myViewController.modalPresentationStyle = UIModalPresentationFormSheet;
self.myViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
self.myViewController.preferredContentSize = CGSizeMake(139, 70);
[self presentViewController:_myViewController animated:YES completion:nil];
UIPopoverPresentationController *popController = [_myViewController popoverPresentationController];
}
You're using _myViewController and self.myViewController together here, which is a little confusing but I assume you have a property, myViewController, backed by an instance variable, _myViewController? I'm therefore assuming both of these things are actually pointing to the same instance.
For clarity I'd recommend sticking with one or the other, probably property access.
If they are both pointing to the same instance, then what the code above is doing is setting a popover presentation style, then setting a form sheet presentation style. The last one you set will "win", so the view controller will be presented as a form sheet (on iPads, this will be a box in the middle of the screen, on iPhones, it will be full screen).
If you are running this code on an iPhone, then popovers don't work by default anyway - they will be presented full screen. To prevent that, you need to set a delegate to the popover presentation controller, implement - (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection;, and return UIModalPresentationNone.
You must also set a source view or bar button item on the popover presentation controller before attempting the presentation, or your app will crash.
As part of my updating my apps to replace the deprecated presentModalViewController with presentViewController, I did some testing.
What I found was disturbing. Whereas presentModalViewController always works and there is no question about it working, I have found the presentViewController method often will not display my VC at all. There is no animation and it never shows up.
My loadView are called without problems, but the actual view does not appear.
So here is what I am doing:
User taps a button in my main view controller.
In the callback for that tap, I create a new view controller and display it as shown above.
The VC never appears (it is an intermittent problem though) but because this VC begins playing some audio, I know that its loadView was called, which looks like as follows.
My button-pressed callback is as follows:
- (void) buttonTapped: (id) sender {
VC *vc = [[VC alloc] init];
[self presentViewController: vc animated:YES completion: nil];
[vc release];
}
Here is my loadview in the VC class:
- (void) loadView {
UIView *v = [UIView new];
self.view = v;
[v release];
... create and addsubview various buttons etc here ...
}
Thanks.
Make sure the controller that calls the function has its view currently displayed (or is a parent to the one currently displayed) and it should work.
I'm using a custom segue which looks like this:
#implementation ModalPushSegue
- (void)perform {
UIViewController *fromController = self.sourceViewController;
UIViewController *toController = self.destinationViewController;
UIView *fromView = fromController.view;
UIView *toView = toController.view;
CGPoint centerStage = toView.centerStage;
toView.center = toView.rightStage;
[fromView.window addSubview:toView];
[fromController addChildViewController:toController];
[UIView transitionWithView:toView
duration:0.5 options:0
animations:^{
toView.center = centerStage;
}
completion:nil];
}
This works well in that the view is slide on from the right as expected and the controller is added to the controller hierarchy.
But later in the added controller I do this:
[self presentViewController:anotherController animated:YES completion:nil];
I would expect this to slide the new controller's view up the screen ala modal style. But what happens instead is the the new view doesn't appear. And when I later remove this controller, it's view flashes up and slides off the screen, leaving a black background instead of the view that was originally there.
I've been playing around with this for a while and if I change the code to
//[self presentViewController:oauthController animated:YES completion:nil];
[self.view addSubview:oauthController.view];
[self addChildViewController:oauthController];
Then the view appears as expected, although not resized.
My problem appears to be with the way that the segues setup the hierarchy vs the way that presentViewController does things. I've done lots of reading and searching but so far have not been able to get a clear picture of exactly what is going on.
I've also played around with using presentViewController in the segue but instead of laying the new view over the old one, the screen goes black and the new view then slides on.
Any help appreciated.
Set a Storyboard ID on your destination view controller (in the storyboard), then try the following code:
AnotherController *viewC = [self.storyboard instantiateViewControllerWithIdentifier:#"AnotherController"];
ModalPushSegue *segue = [[ZHCustomSegue alloc] initWithIdentifier:#"coolSegueName" source:self destination:viewC];
[segue perform];
I am trying to display a UISplitViewController presenting it as a Modal View Controller in my iPad app. I manage to have it display, but for some reason there is a gap to the left of the modal view the size of a Status Bar which is also preserved when the orientation is changed.
Does anybody know why this is happening? Or if this is even possible? Maybe I'm just digging myself a huge hole.
Like for many of you, I needed a 'modal way' to use the UISplitViewController. This seems to be an old issue, but all I found in StackOverflow was at best an explanation why the problem happens when you attempt to do so (like the accepted answer above), or 'hack-arounds'.
However, sometimes it is also not very convenient to change much of your code-base and make a UISplitViewController the initial object just to get it's functionality up and running.
In turns out, there's a way to make everybody happy (including Apple guidelines). The solution that I found best, was to use the UISplitViewController normally, but when needed to be shown/dismissed, use the following approach:
-(void)presentWithMasterViewController: (UIViewController *) thisMasterViewController
andDetailViewController: (UIViewController *) thisDetailViewController
completion:(void(^)(void))completion
{
masterViewController = thisMasterViewController;
detailViewController = thisDetailViewController;
[self setViewControllers:[NSArray arrayWithObjects:masterViewController, detailViewController, nil]];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
self.window.rootViewController = self;
[self.window makeKeyAndVisible];
if(completion)
completion();
}
-(void)dismissViewControllerWithCompletion:(void (^)(void))completion
{
self.window = nil;
masterViewController = nil;
detailViewController = nil;
if(completion)
completion();
}
Where "window", is a property of your UISplitViewController subclass. And the system will take care of the rest!
For convenience/reference, I uploaded this as a UISplitViewController subclass to gitHub:
ModalSplitViewController
--EXAMPLE ON HOW TO USE --
mySplitViewController = [[ModalSplitViewController alloc] init];
mySplitViewController.delegate = self;
[mySplitViewController presentWithMasterViewController:masterViewController andDetailViewController:detailViewController completion:nil];
// when done:
[mySplitViewController dismissViewControllerWithCompletion:nil];
mySplitViewController = nil;
Side-note: I guess most of the confusion originates from the fact that
the UISplitView usage example from Apple documentation uses the window
created in the appDelegate, and for the fact that most people are not
so familiar with the window concept - because we normally don't need
to (they are created once in StoryBoards or boilerplate code).
Additionally, if you are doing state restoration, one should not
forget that programmatically-created UIViewControllers won't
automatically be restored by the system.
The stock UISplitViewController was designed for use as the root view controller only. Presenting one modally goes against the Apple Human Interface Guidelines and has a high probability of getting rejected by the App Review Team. In addition, you may receive the error:
Application tried to present Split View Controllers modally
Technically, this is what I did:
1/ Subclass a UIViewController ie. #interface aVC: UIViewController
2/ In the viewDidLoad, set up a splitViewController, ie. aSplitVC
3/ Then self.view = aSplitVC.view
After all, present aVC as modalViewController
I agree with Evan that this is slightly off-color for Apple, but I was able to complete a working version of this with the following solution:
UISplitViewController *splitVC = [[UISplitViewController alloc] init];
splitVC.delegate = VC2;
splitVC.viewControllers = [NSArray arrayWithObjects:navcon1, navcon2, nil];
UINavigationController *splitNavCon = [[UINavigationController alloc] init];
splitNavCon.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[splitNavCon.view addSubview:splitVC.view];
VC2.splitParentViewController = splitNavCon;
[self presentViewController:splitNavCon animated:YES completion:nil];
This allowed me to have a working back button in the new UISplitViewController that was presented modally on the screen.
You'll notice that I actually pass the VC2 (the delegate of the UISplitViewController) its parent UINavigationController. This was the best way that I found I could dismiss the UISplitViewController from within the VC2:
[splitParentViewController dismissViewControllerAnimated:YES completion:nil];
I believe one can do the other way around: instead of custom controller presenting split controller, one can set up the split controller as the root window controller in storyboard, and on top of its view you can add your custom controller (ie, login screen) and remove it from the screen (removeFromSuperview for example) when it is needed.
That answer is not actually correct, because it not valid any longer since iOS8 and if you need to support even iOS7 you can do that like you put actually modally UIViewController which has a container as SplitView.
let mdSplitView = self.storyboard?.instantiateViewControllerWithIdentifier("myDataSplitView") as! MyData_SplitVC
self.addChildViewController(mdSplitView)
mdSplitView.view.bounds = self.view.bounds
self.view.addSubview(mdSplitView.view)
mdSplitView.didMoveToParentViewController(self)