I setup my UIViewController in app delegate as self.window.rootViewController. For some reason I need open sign view as modal from my root view controller like:
self.window.rootViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self.window.rootViewController presentModalViewController:signViewC animated:NO];
If I change device orientation in sign view, my root view controller doesn't change orientation and I get incorrect orientation after sign view dismissed. Is it possible to change root view orientation if I changed orientation in sign view?
In my root controller I added:
- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
And autorotation works if I rotate root view without modal view.
First of all, you shouldn't be presenting a modal from your app delegate. Whatever check you are doing to determine if you should present that view controller should be done in your main view controller. From there, you should call the code to present the modal, like so:
[self presentModalViewController:signViewC animated:NO]
That way, your main view controller isn't dismissed, it's only pushed down the view stack and should still respond to rotation notifications.
Try that out and let me know how it turns out.
EDIT
Take out the ModalPresentationStyle. That's what's messing you up.
Related
I have a very basic container view that contains a sidebar and swaps out the view controllers in the content area (think UISplitView but with a small icon sidebar / vertical UITabBar).
The container view controller uses autoLayout and resizes correctly when rotated.
Content viewController 1 uses autolayout and was made with IB, so it has a xib file.
Content viewController 2 inherits from UITableViewController and does not use a xib.
If I assign viewController 1 as the root view controller and rotate, the resize works and here are the callbacks that I get in viewController 1:
willRotateToInterfaceOrientation
updateViewConstraints
viewWillLayoutSubviews
didRotateFromInterfaceOrientation
However, if I assign my container view controller as the root view controller, load viewController 1 and rotate, the resize does not work. And I only get the following callbacks inside viewController 1:
willRotateToInterfaceOrientation
didRotateFromInterfaceOrientation
Inside my view controller container, here's how I swap the view controllers:
[self addChildViewController:toViewController];
[toViewController didMoveToParentViewController:self];
// Remove the old view controller
[fromViewController willMoveToParentViewController:nil];
[fromViewController.view removeFromSuperview];
[fromViewController removeFromParentViewController];
// Add the new view
[self.contentContainerView addSubview:toViewController.view];
Now, I do get the callbacks that a rotation is about to happen, but it seems as if neither updateViewConstraints nor viewWillLayoutSubviews is called. This explains why the resize is not happening, but why are those methods not called once I put the view controller in a container view?
I also tried to explicitly return YES in my container on both
shouldAutomaticallyForwardAppearanceMethods
and
shouldAutomaticallyForwardAppearanceMethods
although this should be the default already.
Also, the view controller not made with IB (view controller 2) resizes correctly when rotating inside the container. However, I don't explicitly use NSLayoutConstraints on this one, so I suspect it's defaulting to Springs and Struts for the resizing when rotating.
Do I need to forward some other events on my view controller container to get the auto layout view controller to resize correctly when rotating?
OK, I think I was missing this method here in my view controller container:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
self.contentViewController.view.frame = self.contentContainerView.bounds;
}
While this resizes now correctly when rotating, it still doesn't trigger
updateViewConstraints
in my child view controller. Interesting
Seems like iOS 8 does call updateViewConstraints for you. But iOS 7 didn't. To get this called in iOS 7, call setNeedsUpdateConstraints, like this:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
{
[super willAnimateRotationToInterfaceOrientation:interfaceOrientation duration:duration];
BOOL isiOS7 = floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1;
if (isiOS7) {
// Trigger a call to updateViewConstraints
[self.view setNeedsUpdateConstraints];
}
}
In updateLayoutConstraints, a good way to check which orientation is the one to layout for is to check the status bar's orientation. This works for 7 and 8.
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
BOOL layoutAsLandscape = UIInterfaceOrientationIsLandscape(orientation);
I have a main navigation controller with a root view controller. In the root view controller, on the push of a button I present second view controller like this:
SecondVC *secondVC = [[SecondVC alloc] initWithNibName:#"SecondVC" bundle:nil];
[self.navigationController presentModalViewController:secondVC animated:YES];
In the second view controller, on the push of an other button, I want to present a third view controller (this time from a Storyboard):
ThirdVC *thirdVC = [[UIStoryboard storyboardWithName:#"Settings" bundle:nil] instantiateInitialViewController];
[self.navigationController presentModalViewController:thirdVC animated:YES];
However this doesn't do anything. I debugged and it turned out, that self.navigationController is nil.
Shouldn't it be the main navigation controller? Or doesn't presentModalViewController: add the view controller to the stack? Do I always have to put a view controller in a navigation controller before presenting id modally?
The new view controller SecondVC is being presented modally, and it's not added to the view controller stack of the navigationController. You need to create a new UINavigationController, and put SecondVC inside the navController before presenting it modally.
You'll need to add something like:
UINavigationController *navControl = [[UINavigationController alloc] initWithRootViewController:secondVC];
[self addChildViewController:navController];
[self.navigationController presentModalViewController:secondVC animated:<#(BOOL)#>]
your view controller while being presented is not inside a navigation controller. And will not have access to the presenting controllers navigation controller.
Furthermore if you push or pop stack items on the navigation controller beneath the modal view controller you will likely not notice anything.
If you want to put the controller in the stack you can alternatively show the view controller yourself.
[self.view addSubView:myViewController.view]
myViewController.view.frame = self.view.bounds;
and to dismiss the view controller you would simply remove it from its superview.
the drawback here is that some of the did and will appear methods are not called on the view controller. Therefore you may want to call them yourself.
But the principal is much the same. And you can easily simulate the presenting animation with the animation system.
Give it a starting point below your form, then start your animation block and put the view.frame to superview.bounds also giving it an animation time. I find that 2 seconds is ok. sometimes less.
at this point the presented view is inside the controller which is on the stack. Now while you cant directly modify the navigation controller within the presented view controller you could set a delegate that tells the original your intentions and therefore the presenting view controller (the one on the navigation stack) can push or pop the view controllers as requested. And the presented view controller will be pushed along with it.
Another positive point is that you can do much like other apps do, and present a semi modal view. With a partially transparent background. this way you can show things happening behind the view even tho they dont directly manipulate it.
I setup a storyboard based on the Master-Detail Application, embed the detail view in a navigation controller, and add a new table view controller object which I will use as a second detail view controller.
I then push the new detail view controller with the following code (instead of a segue because I am pushing both a root view and a detail view controller at the same time. Only the detail view code is shown).
// Push the detailView view controller:
NewClass *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"test"];
newViewController.navigationItem.hidesBackButton = YES;
self.splitViewController.delegate = newViewController;
[self.detailViewController pushViewController:newViewController animated:YES];
This works perfectly, EXCEPT that the splitView delegate methods are never called before or after the push. If I do this while in portrait mode, after it pushes the detailViewController, the button to drop down the masterView popover does not show up UNTIL I rotate to landscape mode and then back to portrait mode.
How can I cause the willHideViewController/willShowViewController split view controller delegate methods to be called or manually cause them to be called?
So from what I found, it doesn't call the method because the orientation hasn't changed.
What you have to do is to pass the button from the presenting view controller since it's already tied to the popover like this:
if(self.navigationItem.leftBarButtonItem != nil) {
newViewController.navigationItem.leftBarButtonItem = self.navigationItem.leftBarButtonItem;
}
// Push the newViewController
I'm implementing a Passcode feature in my iPhone app which has a UITabBarController as a root view controller. I have everything working great in most situations, by displaying a modal Passcode ViewController from the tabBarController when the app goes into the background, like so:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
if ([[NSUserDefaults standardUserDefaults] valueForKey:kPasscodeStringKey]) {
PasscodeEntryVC *passcodeView = [[PasscodeEntryVC alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:passcodeView];
[tabBarController presentModalViewController:nav animated:NO];
}
}
My problem comes when the app is already displaying a modal view controller when it enters the background. Then, no passcode view appears. What would be the correct way to do this? Instead of just sending the message to the tabBarController to present the view, should I be checking first to see what the current view is, then have that present the passcode? If so, how is this done? Thanks.
First - you are leaking memory because you do not release your passcodeView and navigation controller nav.
Second - you could keep a simple BOOL variable that is updated whenever a modal view is presented or dismissed. If there is a modal view, just call dismissModalViewController:animated: in your applicationDidEnterBackground: method.
You could also check the frontmost view controller with [self.navigationController.topViewController class], but I have found this to be unreliable.
What I usually do is to ensure that any views I have that may present a modal view controller to dismiss the modal view controller whenever it is sent the UIApplicationWillResignActiveNotification notification, while over in my app delegate, I set it up exactly like yours.
One caveat though, is that whenever you dismiss the said modal view controllers, you need to ensure that you dismiss them with animated: set to NO before presenting your passcode view controller.
I have an app delegate, whose default view should be preceeded by a modal view controller, and sometimes by two modal view controllers. So in the app delegate's didFinishLaunchingWithOptions, I'm checking if there is need for, and in that case displays, the first modal view controller.
Upon dismissing the first modal view controller (using [self dismissModalViewControllerAnimated:YES];), I may want to display the second modal view controller. This is known by the app delegate as well.
So my solution was to use NSNotificationCenter to tell the app delegate that the first modal view controller now have been dismissed. When that happens, the second modal view controller can be displayed by the app delegate, if it is needed.
It works fine, but is there a cleaner solution? I think NSNotificationCenter is really ugly stuff.
Note on displaying multiple modal view controllers at once
I did try to display the first AND the second modal view controller inside of didFinishLaunchingWithOptions, but I never got it working. Here's what I tried:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window setRootViewController:tabBarController];
[self.window makeKeyAndVisible];
[tabBarController presentModalViewController:pinViewController animated:NO];
if([self needsActivation]) {
[tabBarController presentModalViewController:activationViewController
animated:YES];
}
}
UPDATE: The above code works with the following correction:
if([self needsActivation]) {
[pinViewController presentModalViewController:activationViewController
animated:YES];
}
In this particular case, there was no need for NSNotificationCenter, as I thought. I had tried to display multiple modal view controllers, but I'd made an error.
When displaying the modal view controller B from the modal view controller A, it works fine. I had tried presenting modal view controller A and B from a parent view controller.
So when presenting modal view controllers in a hierarchy instead, there is no need for NSNotificationCenter. The view controllers are dismissing themselves, animations works and I'm a step further towards bending the UIKit to my will.
I've edited the code in my question, which now works fine.