Storyboards, Segues and Different views for different orientations - ios5

I have been working on iOS5 and have been struggling with storyboards, segues and adjusting the views for different orientations. After going through some Apple's help for the past couple of days; I have not found one place for my answers, I have come to the following conclusions that I will articulate very carefully and I hope someone can tell me if I am correct:
1- Using a storyboard does not mean you have to use segues (yes this is confusion for a beginner).
2- You can only use segues between VCs that belong to a Navigation controller.
3- On the other hand, having a Navigation controller does not necessarily imply that you have to use a segue from one VC to another VC (yep!). So if you are trying to go from one VC to another VC (regardless of whether the first VC belongs to a Nav Controller or not),you can do the following:
Assume you have a WhiteVC class and a YellowVC class and you placed a button on the WhiteVC view (in the storyboard and changed the class of the generic VC to WhiteVC) and linked it to an IBAction in the WhiteVC class. This would be the code you would implement, in WhiteVC.m, to be able to click that button and have the YellowVC view appear:
-(IBAction)GoToYellow
{
YellowViewController *YellowVC=[self.storyboard instantiateViewControllerWithIdentifier:#"myYellowVC"];
[self presentViewController:YellowVC animated:YES completion:nil];
}
To return from the YellowVC to the WhiteVC, you would then create another button on the YellowVC view (follow same process as above) and use the following code:
-(IBAction)ReturnToWhiteVC
{
[self dismissViewControllerAnimated:YES completion:nil];
}
This will dismiss the YellowVC and return to the White VC.
4- It is not recommended to have one VC own (instantiate) multiple views - for different orientations for example- it is better to have a separate VC for each view. It is ok though to have VCs instantiate other VCs as part of the MVC.
5- Now all of this came about because I was building an app that has a Nav Controller but its root VC will be different (different VCs with different Views) depending on whether it is landscape or portrait. So I could either instantiate the VCs programmatically (like #3 above) which worked well. Or, if I wanted to do the transition using segues, the apple help docs provides the following code:
BOOL isShowingLandscapeView;
- (void)awakeFromNib
{
isShowingLandscapeView = NO;
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) && !isShowingLandscapeView)
{
[self performSegueWithIdentifier:#"AlternateView" sender:self];
isShowingLandscapeView = YES;
}
else if (UIDeviceOrientationIsPortrait(deviceOrientation) && isShowingLandscapeView)
{
[self dismissViewControllerAnimated:YES completion:nil];// breakpoint shows that this is triggered
isShowingLandscapeView = NO;
}
}
The above code obviously only applies with a Nav Controller (because of the segues) and it worked when the orientation changed from portrait to Landscape but when I rotate back to portrait, I end up with the landscape VC rotated back to portrait. What am I doing here wrong?
I hope someone can validate/confirm these points and answer my question.
Thanks
KB.

First of all : segues don't apply only with a UINavigationController, any UIViewController is able to perform a segue.
The Apple documentation also say that you'll have to restrict supported orientation of each UIViewController you'll use to handle orientation i.e. you'll have to restrict portrait to portrait only and landscape to landscape only). This could be done following the doc :
For iOS 6 and later : override supportedInterfaceOrientation
For iOS 5 and earlier : override shouldAutoRotateToInterfaceOrientation
Then, in order to use Apple design to manage orientation, you can follow this blog post by Alex Moffat.
It basically describe how to design your view hierarchy using two view controllers displaying the same content (one for Landscape and one for Portrait), embedded in a Navigations Controller. Both view controller should be able to push a hird view controller able to autorotate.
The main idea is to push/pop the Landscape view on/from the navigation stack, and to handle the transition with the third view accordingly.
You should be able to adapt it's code to your own view hierarchy.

Related

Handling autorotation for one view controller in iOS7

I've read many answers on SO but I can't seem to get autorotation working on iOS7.
I only need one view controller to rotate, so I don't want to set rotation settings in my Info.plist.
As I understand Apple's documentation, a single view controller can override global rotations settings (from Info.plist) by simply overriding two methods. Info.plist is set to only allow Portrait, and my view controller implements the following methods:
- (NSUInteger)supportedInterfaceOrientations
{
NSLog(#"%s", __PRETTY_FUNCTION__);
return UIInterfaceOrientationMaskAllButUpsideDown;
}
- (BOOL)shouldAutorotate
{
NSLog(#"%s", __PRETTY_FUNCTION__);
return true;
}
I'm seeing those NSLog statements upon rotation but nothing rotates.
If I do configure Info.plist with the proper rotation settings, my view will rotate, but not if I try and rely on my view controller.
Not sure if it matters, but the view I'm trying to rotate is from a .xib using auto layout.
Also, my ViewController is being presented modally and is contained in a navigation controller. I've tried just presenting the view controller by itself and that doesn't work. I've also tried adding a category to UINavigationController to get it's autorotation directions from it's topViewController.
In my case, I had a new iOS7 app with about 30 view controllers created already. I needed auto rotation on just a single modal view controller. I didn't want to have to update the preexisting view controllers.
I selected the orientations I wanted in the plist:
Then I added a category to my app delegate on UIViewController:
#implementation UIViewController (rotate)
-(BOOL)shouldAutorotate {
return NO;
}
#end
Then in the single modal view controller I WANTED to rotate I added this method:
-(BOOL)shouldAutorotate {
return YES;
}
I also discovered, that if my view controller wasn't a modal VC I would need to add category methods on UINavigationController instead, for all VCs that were subsequent to the root view controller, as part of the navigation stack of view controllers - similar to this: https://stackoverflow.com/a/20283331/396429
Simple but it work very fine. IOS 7.1 and 8
AppDelegate.h
#property () BOOL restrictRotation;
AppDelegate.m
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if(self.restrictRotation)
return UIInterfaceOrientationMaskPortrait;
else
return UIInterfaceOrientationMaskAll;
}
ViewController
-(void) restrictRotation:(BOOL) restriction
{
AppDelegate* appDelegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
appDelegate.restrictRotation = restriction;
}
viewDidLoad
[self restrictRotation:YES]; or NO
You need to set the plist value to all possible values, then limit them as you see fit (in the Navigation Controllers and TabBar Controllers. From the UIViewController class description:
In iOS 6 and later, your app supports the interface orientations
defined in your app’s Info.plist file. A view controller can override
the supportedInterfaceOrientations method to limit the list of
supported orientations. Typically, the system calls this method only
on the root view controller of the window or a view controller
presented to fill the entire screen; child view controllers use the
portion of the window provided for them by their parent view
controller and no longer participate directly in decisions about what
rotations are supported. The intersection of the app’s orientation
mask and the view controller’s orientation mask is used to determine
which orientations a view controller can be rotated into.
I've faced such problem - had only one landscape view in my app.
I've used below code to to handle that.
#import "objc/message.h"
-(void)viewWillAppear:(BOOL)animated{
objc_msgSend([UIDevice currentDevice], #selector(setOrientation:), UIInterfaceOrientationLandscapeLeft);
}
I know this is old but I ended up in a more unique situation where we have 50+ ViewController all over the app that I refused to go through and modify and support the same orientation in all of them but one or 2. Which brings me to my answer. I created a UIViewController category that overrides - (BOOL)shouldAutorotate to always return NO or YES depending on device type etc. (this can be done with supported interface orientations too). Then on the ViewControllers I wanted to support more then just portrait, I swizzled shouldAutorotate to return YES. Then forced the orientation change when the view is dismissed on the parent ViewControllers viewWillAppear method using:
[[UIDevice currentDevice] setValue:#(UIInterfaceOrientationPortrait) forKey:#"orientation"].
When all was said and done, I accomplished everything I wanted on a few ViewControllers with < 30 lines of code using a macro for swizzling. Had I done it by replacing shouldAutorotate and supportedInterfaceOrientations on all of the VC's in the application I would have ~250 extra lines of code. and a lot of grunt work adding it in the first place.

Understanding iOS 6 Interface orientation change

ADDED:
I see that my question is viewed often without upvotes so I decided that you guys do not get what you search. Redirecting you to question that has really nice answer about
How to handle orientation changes in iOS6
Specific demands to orientation changes:
Restricted rotation
Upvotes are welcome :)
I've created a new project from Master Detail template and trying to start it with landscape orientation.
As you know the
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
method is deprecated and we must use
- (NSUInteger)supportedInterfaceOrientations
and/or
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
Here's my code:
- (NSUInteger)supportedInterfaceOrientations {
NSLog(#"supported called");
return UIInterfaceOrientationMaskAll;//Which is actually a default value
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
NSLog(#" preferred called");//This method is never called. WHY?
return UIInterfaceOrientationLandscapeRight;
}
As you can see I'm trying to return landscape orientation in preferred method but it is never called.
p.s. documentation states:
Discussion The system calls this method when presenting the view
controller full screen. You implement this method when your view
controller supports two or more orientations but the content appears
best in one of those orientations.
If your view controller implements this method, then when presented,
its view is shown in the preferred orientation (although it can later
be rotated to another supported rotation). If you do not implement
this method, the system presents the view controller using the current
orientation of the status bar.
So, the question is: Why the prefferredOrientation method is never get called? And how should we handle different orientations in different controllers?. Thanks!
P.S don't mark the question as duplicate. I've investigated all similar questions and they do not have answer for mine.
About preferredInterfaceOrientationForPresentation
preferredInterfaceOrientationForPresentation is never called because this is not a "presented" view controller. There is no "presentation" involved here.
"Presented" and "presentation" are not some vague terms meaning "appears". These are precise, technical terms meaning that this view controller is brought into play with the call presentViewController:animated:completion:. In other words, this event arrives only if this is what we used to call a "modal" view controller.
Well, your view controller is not a modal view controller; it is not brought into play with presentViewController:animated:completion:. So there is no "presentation" involved, and therefore preferredInterfaceOrientationForPresentation is irrelevant here.
I'm being very explicit about this because I'm thinking that many folks will be confused or misled in the same way you were. So perhaps this note will help them.
Launch into Landscape
In iOS 6, the "Supported Interface Orientations" key in your Info.plist is taken much more seriously than previously. The solution to your overall problem of launching into a desired orientation is:
Make sure that "Supported Interface Orientations" in your Info.plist lists all orientations your app will ever be allowed to assume.
Make sure that the desired launch orientation is first within the "Supported Interface Orientations".
That's all there is to it. You should not in fact have to put any code into the root view controller to manage the initial orientation.
If you would like launch modal view in Landscape Mode, just put this code in presented view controller
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
UIInterfaceOrientation orient = [[UIApplication sharedApplication] statusBarOrientation];
if (UIInterfaceOrientationIsLandscape(orient)) {
return orient;
}
return UIInterfaceOrientationLandscapeLeft;
}
Then, present this controller as usual
UIViewController *vc = [[UIViewController alloc] initWithNibName:nil bundle:nil];
[vc setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[vc setModalPresentationStyle:UIModalPresentationFullScreen];
[self.navigationController presentViewController:vc animated:YES completion:^{}];
There is a very simple answer: You can only change or fix the interface orientation of a modal presented view controller. If you do so i.e. with a Present Modally segue in Interface builder (or the navigation controller method) you can define the allowed orientations with
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait; // for example else an addition of all allowed
}
This event doesn't fire up when you only push a view controller on the navigation Controller ... so : You don't have a BACK button and need a
[self dismissViewControllerAnimated:YES completion: ^(void) {
}];
to close it.

iphone screen orientation

Hi I am trying to provide screen orientation in my app and I have read many docs online but when I try that It just is a total disaster (I tried emulator and iphone). On one partwhen the screen is rotated to landscape half of the views are not visible any more. Their position is on the left side and right side is empty, plus if I open a next view controller and then rotate again I can see through the second view parts of the first view it just is terrible I don't know how to fix it (Excluding blocking the possibility to lock orientation in portrait).
I know in android you can provide different xml layouts for portrait and landscape. Is there a way to fo that in iphone? If so how?
each of my views are composed of a controller and .xib
There is a "main" controller and the other controllers are called like that:
ayuda=[[AyudaView alloc]
initWithNibName:#"AyudaView"
bundle:nil];
[self.view addSubview:ayuda.view];
also I have already added these metods to the maincontroller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if(UIDeviceOrientationIsValidInterfaceOrientation(orientation)) {
[self handleInterfaceRotationForOrientation:orientation];
}
}
#pragma mark -
#pragma mark Screen Orientation Handling
- (void)handleInterfaceRotationForOrientation:(UIInterfaceOrientation)interfaceOrientation {
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[self handleInterfaceRotationForOrientation:interfaceOrientation];
return YES;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[self handleInterfaceRotationForOrientation:toInterfaceOrientation];
}
as indicated here
and have tried to play with the autosizing param but it only gets worse
Thank you very much
EDIT: ok I have been reading some more:
http://www.geckogeek.fr/iphone-forcer-le-mode-landscape-ou-portrait-en-cours-dexecution.html
http://www.cocoaosx.com/2011/11/10/rotatingviewcontroller-display-a-different-uiviewcontroller-depending-on-the-rotation-of-the-device/
and basically what I have found is that you have to create 2 controllers but this solution is problematic for me because of the nib files. I mean I would also have to create 2 nibs so double the amount of code (or use the library above). What if I have values in my textfields , do I just have to send them around?
So, apart from the comments suggesting you don't have any idea about what's going on in your code (which may or may not be true -- who am I to judge?), I see a few problems.
The first issue is that I don't think you understand MVC and how iOS handles the view hierarchy. You're correct in having each of your views controlled by a controller and designed by a .xib (there are cases where a view doesn't need its own controller, but for simplicity, we'll assume yours do). However, you are incorrect in the way you are presenting those views on screen. A view's controller is responsible for presenting or removing a view from the screen. The view only knows what it displays, such as labels or buttons. The controller is responsible for telling the view what to display on those labels and buttons. Instead of adding the new VC's view as a subview of your main view, push it onto a navigation controller's stack or present the view modally. If you're in your main VC, you can say
ayuda = [[AyudaView alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:ayuda animated:YES];
I'm thinking that getting the view hierarchy correct might just be enough to solve your landscape/portrait swapping chaos. But while we're on that topic...why are you creating this handleInterfaceRotation method? View Controllers already have a method for this called
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
Use this method to handle rotation.

UIViewController in UINavigationController does not rotate as expected

I have a UINavigationController who's root UIViewController only supports Portrait orientation. When the user presses a button a second UIViewController gets loaded and pushed onto the stack, this view controller, let's call it secondViewController, only supports landscape orientation.
Usually what would happen is that the interface rotates during the transition to secondViewController, but this doesn't happen despite shouldAutorotateToInterfaceOrientation: getting called on secondViewController. Once I tilt the device manually when secondViewController is displayed it rotates as expected and when switching back to the first UIViewController it rotates back to portrait as it should.
To test whether the allowed interface Orientations of the first UIViewController have any influence on the process I set a return YES; for all the interfaces there, however, the problem persisted. I also read the technical Q&A on the subject but according to this doc everything should be fine, the UINavigationController is the only subview of the window.
I create secondViewController completely programmatically (overwriting of loadView) and had this particular case working before in other apps. Does anybody have an idea what I might be doing wrong?
Apparently the change doesn't happen automatically, the following gives the neccessary "push":
- (void) viewWillAppear:(BOOL)animated {
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeRight];
}

Autorotation on a gallery - willAnimateRotation... isn't invoked

I'm using this.
I've put this in where the images are added:
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
I've put these overrides in SlideShowViewController:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
return YES;
}
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)InterfaceOrientation duration:(NSTimeInterval)duration {
}
The first is called, but not the second.
I'm not using a nib; it uses a custom init which returns a self.view with the 3 images on it.
Am I missing something? What would be the best way to modify this script to get the landscape photos stretch to full width when the iPhone is rotated by the user?
Edit: Just tried adding willAnimate... to the Root View Controller of my NavigationController, but it's not invoked. Perhaps the NavigationController is the problem? In that case, where do I put the willAnimate...? Alternatively, how do I receive and pass on messages sent to it towards the visible viewController?
Edit 2: Cause of the error in topic was that the TabBarController was missing a connection in IB (which I was sure was there). So now my problem is the same as for a few other similar questions - willAnimate... is called in root VC but not in the pushed VCs. The code I've used fades in the gallery VC rather than slide it in, ie. it's not pushed but currently added as a subview to the window - to fill the entire display.
Suggestions for how to message that subview's controller to set the frames would be appreciated, or alternate solutions!
This is not an answer to why your "willAnimateRotation.."-method doesn't trigger but instead an alternative to how the image gallery can adapt to rotations itself.
In initWithImages add:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didRotate:)
name:UIDeviceOrientationDidChangeNotification object:nil];
Add this method to receive the notification. In didRotate you can handle the resizing an repositioning of the images.
- (void) didRotate:(NSNotification *)notification
{
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (orientation == UIDeviceOrientationLandscapeLeft)
{
NSLog(#"Landscape Left!");
}
}
Don't forget to unregister the notification in dealloc:
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
May I also suggest that you use a UIScrollView instead of handling scrolling and gestures yourself?
Is SlideShowViewController pushed directly onto the Navigation Controller stack, or is it part of some other view? The UIView delegate methods get kind of messed up when working with subviews on views on the Navigation Controller stack.
If SlideShowViewController is just a subview of some other view that's been pushed onto the Navigation Controller stack, I would try to listen for this method in the view controller that is actually on the Navigation Controller stack, and then calling the appropriate method in SlideShowViewController from there.
I had a similar problem in getting viewWillAppear and similar method called within a navigation controller.
I solved by calling them manually when the root method is called.
I don't know if it's the same issue with rotation, but you may try putting in your root view controller
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)InterfaceOrientation duration:(NSTimeInterval)duration {
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self.navigatioController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
This question was a bit tricky to answer I guess, since it was about my specific non-rudimentary app setup. Sorry for letting the bounty time run out, it was basically too late the same day I asked ;)
I thought I'd post this 'not buried in comments' for anyone who gets the same symptoms.
The first was that I noticed an IBOutlet that I'm sure I connected at one point was no longer connected (Tab Bar Controller -> tabBarController object). Since I was racking my brains and moved stuff around to make this work, this probably was due to me trying something and then restoring a backup.
Second part of the solution was to addSubView the gallery view to the tabBarController's view instead of the navigated-to view. Then the root view controller's shouldAuto... and willAuto methods were invoked.
(To hide the Tab Bar and Navigation Bar while rotating I had to set their .alpha to 0 while the gallery is displayed, otherwise the navigated-to view was partly hidden under the navigation Bar when returning to it. I found no way of hiding the status bar while showing the gallery.)
After this there was a request to have the gallery in a detail-view navigated to from another tab, and I got the tricky error that only while the OTHER (working) detail-view was shown under the other tab would autorotate work with the new gallery. Will get back to the solution to this, quittin time.