I have a simple single view app with two storyboards with different layouts for portrait and landscape. The landscape layout is completely different which is why I am using a second storyboard.
When I switch to landscape the view doesn't change and I get the
"Application tried to present a nil modal view controller on target"
` error in the debugger. I have checked that the landscape storyboard is referencing to the CalculatorViewController class.
This is the line of code which generates the error:
[self presentViewController:landscapeViewController animated:YES completion:nil];
Here is the whole method from CalculatorViewController.m:
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) &&
!isShowingLandscapeView)
{
// code here to show landscape storyboard
UIStoryboard *landscapeStoryboard = [UIStoryboard storyboardWithName:#"LandscapeStoryboard" bundle:nil];
CalculatorViewControllerLandscape *landscapeViewController = [landscapeStoryboard instantiateInitialViewController];
[self presentViewController:landscapeViewController animated:YES completion:nil];
isShowingLandscapeView = YES;
}
else if (UIDeviceOrientationIsPortrait(deviceOrientation) &&
isShowingLandscapeView)
{
[self dismissViewControllerAnimated:YES completion:nil];
isShowingLandscapeView = NO;
}
}
The Initial View Controller is called CalculatorViewController.
The View Controller for the landscape layout is called CalculatorViewControllerLandscape.
This is my first app so I really appreciate any help. I have tried to find a solution in similar questions posted without success.
It means that landscapeViewController is nil.
This can be cause by either:
landscapeStoryboard being nil (most likely because a storyboard named LandscapeStoryboard cannot be found)
no initial view controller being indicated in the storyboard.
You need to make sure that the identifier for the storyboard landscape-view is set to "LandscapeStoryboard". Select the storyboard and check the identifier.
Related
Is there way to make show segue works only on iPhone? Some kind off turn it off at iPad? I have show segue on iPhone and it works perfect but on iPad I am using UISplitVC with same VC. And now every time when I choose something on left part of UISplitVC it make that segue instead my left UITableVC.
Set an identifier for the segue in Interface builder, then in your view controller class you can use this in a method instead of presenting it in your storyboard. You can do that by clicking the class button in your view controller and entering it in "Storyboard ID" as shown here.
IF YOU HAVE A BUTTON THAT YOU CLICK to present the new view you can drag an IBAction from the storyboard to this method on the view controller you are presenting it from.
-(IBAction)launchIphoneOnlyController (id)sender {
if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone ) {
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"identifier"];
[self presentViewController:vc animated:true completion:nil];
}
}
Otherwise make a call to the following method from wherever you want to instantiate the view from.
-(void)launchIphoneOnlyController {
if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone ) {
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"identifier"];
[self presentViewController:vc animated:true completion:nil];
}
}
In shouldPerformSegueWithIdentifier check the seque ID you set in the story board and check (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) for iPhone.
See apple developer.
I have a UIViewController, which is presented modally (full screen) and I would like to disable autorotation in that view. I do not want to restrict it to landscape or portrait, just would like it to stay in whatever orientation it was originally presented.
On iOS 6 it was sufficient to just override the method:
- (BOOL)shouldAutorotate {
return NO;
}
And it did exactly what I wanted. On iOS 7 however, this seems to have no effect. The method does get called, but the return value seems to be ignored by the OS - it auto rotates no matter what.
The documentation does not mention any changes to this method. How can I achieve the desired effect on iOS 7?
Edit: the view controller is being presented (not pushed!) by a UINavigationViewController:
[self.navigationController presentViewController:vc animated:YES completion:nil];
Solution:
As odd as it may seem, but this solution was not published in the numerous existing questions on this topic. On iOS 7 it seems the answer the UINavigationController gives to
shouldAutorotate is what the OS acts on. We need to subclass UINavigationController to modify its behaviour.
When dealing with a regular navigation stack it is indeed sufficient to just use [self.topViewController shouldAutorotate], but when there is modal view, it resides in self.presentedViewController, not self.topViewController. Thus the full solution looks like:
- (BOOL)shouldAutorotate {
UIViewController *vc;
if (self.presentedViewController) vc = self.presentedViewController;
else vc = [self topViewController];
return [vc shouldAutorotate];
}
So I just tried you code and it worked which leads me to believe that you are presenting your UIViewController in a UINavigationController. For whatever reason, iOS 7 changed how UINavigationController handle rotations.
The easiest solution is to create a subclass of UINavigationController that overrides the shouldAutorotate method and returns the value from the topViewController.
#interface CustomNavigationController : UINavigationController
#end
#implementation CustomNavigationController
- (BOOL)shouldAutorotate
{
return [[self topViewController] shouldAutorotate];
}
#end
So instead of doing this, where viewController is your object that return NO for shouldAutorotate.
UINavigaitonController *navController = [UINavigationController alloc] initWithRootViewController:viewController];
[self presentViewController:navController animated:YES completion:nil];
You would use the CustomNavigationController instead
CustomNavigationController *customNavController = [CustomNavigationController alloc] initWithRootViewController:viewController];
[self presentViewController:customNavController animated:YES completion:nil];
#import <objc/message.h>
-(void)viewDidAppear:(BOOL)animated{
objc_msgSend([UIDevice currentDevice], #selector(setOrientation:), UIInterfaceOrientationPortrait );
}
see How do I programmatically set device orientation in iOS7?
But take the case you are using this method because it is a private API, and your app may be rejected by Apple. So, maybe is better to set orientation from Project details -> General -> Deployment Info tab, choose Landscape Left and Landscape Right choice only. This can be a better approach, if all your views need only one kind of orientation.
I am trying to define supported orientations depending on where the user is in my app, I am having a very difficult time doing so.
I have found out thus far that I should use the supportedInterfaceOrientationsForWindow: and shouldAutorotate methods that are now supported in iOS6, however neither method is ever called where I am defining them in my UIViewController.
This is what my code looks like
- (BOOL)shouldAutorotate {
return YES;
}
- (NSUInteger)supportedInterfaceOrientationsForWindow {
return UIInterfaceOrientationMaskPortrait;
}
In my Target Summary Supported Orientatoin I have de-selected all options.. thinking I would just define supported orientation in each of m ViewControllers... I would like to know if this is the correct thing to do?
Now I have read what I am trying to do is dependant on the structue of my app, so Here I will outline my app.
main UIViewController (3 buttons taking you to (3 different navigationControllerViews) Wrong! only one navigationController... sorry its been a long time since I looked at this code.)
secondary UIViewController (holds navigation controller)
other UIViewControllers (appear in secondarys NavigationController)
I would like every ViewController up untill the last one in the NavigationController stack to appear in portrate. The last view in the NavigationController is a special view that needs to be able to rotate its orientation to left or right if needed.
I would like to know if this is possible and if so why isnt the code that I have above working/being called.
Any help would be greatly appreciated.
// Update to question Re:
RootView loads with (three buttons, here is the Method that is called when a button is selected to load the View containing the navigation controller)
- (IBAction)buttonClick: (UIButton *) sender
{
//..
// v ----->
if ([sender isEqual:vUIButton]) {
VSearchViewController *vSearchViewController = [[VSearchViewController alloc] initWithNibName:#"VSearchViewController" bundle:nil];
[self.navigationController pushViewController:vehicalSearchViewController animated:YES];
}
//..
}
Then inside VSearchViewController I load the new views onto the UINavigation stack like this
//..
FModelsViewController *fModelsViewController = [[FModelsViewController alloc] initWithNibName:#"FModelsViewController" bundle:nil];
// Sets the back button for the new view that loads (this overrides the usual parentview name with "Back")
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Back" style: UIBarButtonItemStyleBordered target:nil action:nil];
[self.navigationController pushViewController:fModelsViewController animated:YES];
//..
So in review I have set up the navigation controller in the appDelegate and all views in my app are on the navigationStack... I was wrong in saying there are 3 NavigationControllers.. there is only one and every view is added to the stack.. Sorry about that.. Its been a year and a half since I looked at this code..
Are you running the above code on iOS6? Those methods will only be called on iOS6.
Also maybe you could post some code to better illustrate how you are transitioning to these viewControllers so we can get a better understanding of the view hierarchy.
You might want to look at UIViewController's addChildViewController: method.
I think your last view could take advantage of the code written below. It senses the orientation of the device and will show a different view controller for the landscape view (I'm assuming that is what you are trying to do). This means your last view will have a portrait and a landscape option.
#implementation LastViewController
- (void)awakeFromNib
{
isShowingLandscapeView = NO;
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationLastViewChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
- (void)orientationLastViewChanged:(NSNotification *)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) &&
!isShowingLandscapeView)
{
[self performSegueWithIdentifier:#"LandscapeLastView" sender:self];
isShowingLandscapeView = YES;
}
else if (UIDeviceOrientationIsPortrait(deviceOrientation) &&
isShowingLandscapeView)
{
[self dismissViewControllerAnimated:YES completion:nil];
isShowingLandscapeView = NO;
}
}
on the view controllers leading up to this view that you want locked into portrait, write this code:
- (BOOL)shouldAutorotate {
return NO;
}
as for your supported interface orientations, leave that how you have it.
With iPad with iOS6, we have this bug where a modal view controller will expand to full screen, even if it is told to
be using "form sheet" presentation style. But, this happens only if there are two modals, a parent one and its child.
So this is how the first modal is created and presented:
UINavigationController *navigationController = [[[UINavigationController alloc] initWithRootViewController:controller] autorelease];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[parentController presentModalViewController:navigationController animated:YES];
// parentController is my application's root controller
This is how the child modal is created and presented:
UINavigationController *navigationController = [[[UINavigationController alloc] initWithRootViewController:controller] autorelease];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[parentController presentModalViewController:navigationController animated:YES];
// parentController is the navigationController from above
So when rotating from landscape to portrait, the parent modal will expand to full screen and remain that way even if we rotate back to landscape.
When we have the parent modal all by itself (no child modal), then it works as expected, which is that it remains in form sheet style.
Note that this happens on iOS6 only (device and simulator) and doesn't happen on iOS 5 (simulator and reported to work by testers).
So far, I have tried the following without success:
setting wantsFullScreenLayout to NO
forcing wantsFullScreenLayout to always return NO by overriding it
Making certain my controllers inside the navigation controller also specify UIModalPresentationFormSheet
implementing preferredInterfaceOrientationForPresentation
upgrade to iOS 6.0.1
Thanks!
UPDATE:
So, I adapted the response from the Apple Developer Forums (https://devforums.apple.com/message/748486#748486) so that it works with multiple nested modal.
- (BOOL) needNestedModalHack {
return [UIDevice currentDevice].systemVersion.floatValue >= 6;
}
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
// We are the top modal, make to sure that parent modals use our size
if (self.needNestedModalHack && self.presentedViewController == nil && self.presentingViewController) {
for (UIViewController* parent = self.presentingViewController;
parent.presentingViewController;
parent = parent.presentingViewController) {
parent.view.superview.frame = parent.presentedViewController.view.superview.frame;
}
}
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
// We are the top modal, make to sure that parent modals are hidden during transition
if (self.needNestedModalHack && self.presentedViewController == nil && self.presentingViewController) {
for (UIViewController* parent = self.presentingViewController;
parent.presentingViewController;
parent = parent.presentingViewController) {
parent.view.superview.hidden = YES;
}
}
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
// We are the top modal, make to sure that parent modals are shown after animation
if (self.needNestedModalHack && self.presentedViewController == nil && self.presentingViewController) {
for (UIViewController* parent = self.presentingViewController;
parent.presentingViewController;
parent = parent.presentingViewController) {
parent.view.superview.hidden = NO;
}
}
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
}
Not sure if this should be considered as a bug and I'm curious what iOS 7 will bring, but the current workaround for this issue is to set modalPresentationStyle to UIModalPresentationCurrentContext for the child-viewController.
Set modalPresentationStyle = UIModalPresentationCurrentContext
This makes the child still beeing presented as FormSheet but prevents the parent from beeing resized to fullscreen on rotation.
Dirk
I can see 2 problems here.
1) in iOS 6 the method presentModalViewController:animated: is deprecated, try using presentViewController:animated:completion:
(despite this might not help, you still may want to do it)
2) In iOS 6 somehow appeared that container controllers (such as UINavigationController) don't resend the autorotate messages to their children. Try subclassing the UINavigationController and redefine the corresponding autorotation methods to be sent to all of the children. This might help.
You need to instanciate your navigation controller after your main view.
So that you will be able to manage rotation in each view.
If your AppDelegate RootViewController is a navigation controller, you will not be able to manage rotation with native functions.
When the application is in landscape mode (which I plan to force), displaying a modal view causes the parent view to rotate to portrait mode. If I set the return value of shouldAutoRotateToInterfaceOrientation to NO, the parent does not rotate, however the modal then slides in from the side and displays sideways. Below is the code that reveals the modal.
- (IBAction)loadExistingGame:(id)sender {
SavedGamesTableViewController *savedGames = [[SavedGamesTableViewController alloc] initWithStyle:UITableViewStyleGrouped];
savedGames.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:savedGames animated:YES];
[savedGames release];
}
As per request here is the contents of the shouldAutoRotate method of the SavedGamesTableViewController
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Override to allow orientations other than the default portrait orientation.
return YES;
}
Ok I figured out what needed to be done to fix it. The plist file that contains a list of the possible orientations needs to be limited to a single landscape view. The parent to the modal table view needs to have the shouldAutoRotateToInterfaceOrientation method return YES only if the orientation matches the only orientation in the plist file.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Overriden to allow any orientation.
return interfaceOrientation = UIInterfaceOrientationLandscapeRight;
}
the modal viewcontroller should return NO for the same method.
Based on
When the application is in landscape
mode (which I plan to force),
displaying a modal view causes the
parent view to rotate to portrait
mode.
and
As per request here is the contents of
the shouldAutoRotate method of the
SavedGamesTableViewController
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Override to allow orientations other than the default portrait orientation.
return YES;
}
So what you're saying is that the parent view controller is not yet set to force only using landscape orientation, and when you show a modal view that is set to allow all orientations, you're wondering why your parent view rotates to portrait when you rotate the device to portrait? I don't understand your question... aren't you saying that parent view controller is currently set to allow rotation to portrait? Isn't this behaviour exactly what should happen?
I had a similar problem when bringing up a modal mail view. Forcing the rotation didn't work for me, but calling presentModalViewController on the application's main view controller rather than a child view controller solved the issue.
I was seeing the same behavior; in my case the problem was I had implemented shouldAutorotateToInterfaceOrientation to return YES unconditionally for the parent view controller but NOT for the presented modal view controller. So I suspect Shaggy Frog's comment is the key: whether you want to force landscape mode or not, you need to make sure that the two view controllers' shouldAutorotateToInterfaceOrientation implementations agree or weirdness will ensue.
UIViewController *vc = /* create view controller */;
UINavigationController *nc = nil;
if (IOS_VERSION_LESS_THAN_6_0) {
nc = [[MyCustomNavigationControllerSupportingAllOrientations alloc] initWithRootViewController:vc];
} else {
nc = [[UINavigationController alloc] initWithRootViewController:vc];
}
[self.navigationController presentModalViewController:nc animated:YES];
On iOS6 I use a UINavigationController.
On pre-iOS6 I subclass UINavigationController, like this:
#interface MyCustomNavigationControllerSupportingAllOrientations : UINavigationController
#end
#implementation MyCustomNavigationControllerSupportingAllOrientations
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
#end