My viewController1 has a child view controller (viewController2). viewController2.view is a subview of viewController1's view. At the user's action, I remove the view of viewController2 from it's superview. After a while I have to add it again as subview.
The problem is that if the user rotates the device while viewController2 is not visible, after adding it again to viewController1's view, it's frame and it's subviews are placed as the device was still in the old orientation. Even the viewController2.view.frame has the height and with interchanged.
Does anyone have a solution to this? Thanks in advance!
Without seeing your code, I would guess that you are keeping a reference to view controller 2 even when it isn't visible. In that case, you need to forward the view rotation events to the controller so that it knows about the rotation like this (Do this for each event that you want to forward):
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
// Forward to view controller 2 if it is not displayed
if (!viewController2.view) {
[viewController2 willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
}
A better design may be to just set view controller 2's view to hidden instead of removing it, and it will still get the events without manual intervention.
Let me guess your problem. Do you want to show the different UIView base on orientation , right?
It's about update your viewController2 to load the nib base on orientation. You can make the landscape and portrait nib to support your different UI due to orientation. Try to look from this Answer
Hope it helps you
Related
We have a MainViewController with a tableView, and it presents a new modalViewController.
The MainViewController is restricted to portrait only, and the modalViewController can rotate.
The problem is in iOS8, that when the modalViewController rotates, the callback method of rotation in iOS8 in MainViewcontroller is called - - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
Thus, the UITableView is getting its data reloaded, which is a behaviour we don't want.
Can we prevent this feature of iOS 8, and not rotate the presenting UIViewController?
So after long days of searching and investigating, I finally came up with a possible solution.
First of all, I can use navigation controller and push the viewController instead of presenting it, but it breaks my code and just isn't so true.
The second thing I can do is not setting constraints. I still can use autolayout, but if I don't set constraints, and let the default constraints to be set, the tableView doesn't get reloaded. of course this is also isn't very smart thing to do, as I have many elements in my viewController.
Finally, I figured out that I can show this "modal" viewController in another UIWindow. I create UIWindow and set the modalViewController as its rootViewController.
I put some example project in git:
https://github.com/OrenRosen/ModalInWindow
Hope it will be helpful.
I did something similar with a navigation controller, that wouldn't rotate unless the top pushed controller does rotate.
In your case check if the main controller is presenting another controller. If it isn't then just reject rotation, otherwise return whatever the presented controller returns for the rotation method.
As for your table view, it shouldn't get reloaded because of rotations.
In iOS 8 the view that rotates when you change the device orientation is the first view added to the UIWindow. So, if you save a reference to it in your presentedController, you can overwrite the shouldAutorotate and supportedInterfaceOrientations values.
I've noticed that most consumer-friendly Android and iPhone fitness apps have two interface modes - in portrait mode the user gets more detailed information, but when the user turns the device to landscape mode, a full screen graph is added to cover the entire screen.
I'm interested in how to implement transition to a different view controller in response to device rotation on iPhone. My initial thoughts are to intercept (willRotateToInterfaceOrientation event, then get the app delegate and add a full screen graph view controller to the window).
Is there a better way of turning an iPhone rotation into a transition to another view controller? Like hiding the status bar and pushing a modal view controller in landscape mode with animation?
First ask yourself whether you really need a separate view controller. One view controller can easily hide or unhide a graph.
If this graph needs its own view conroller then you could use a container view that contains the graph which refers to its own view conroller. That is what container views are made for.
The "Master" view controller then would just hide and unhide the container view in response to rotation events (and layout them accordingly etc. pp.)
If you prefer to add or remove the container view from its super view (most probably self.view from the "Master" view controller's point of view) then do that instead of hiding and unhiding. That is probably most appropriate.
The upside of this appoach would be that it works regardless of the navigaiton structure you are in, regardless of whether the rotated view controller was pushed or presented modally, regardless of whether you are in a tab bar driven app or a single view app, whether you are using storyboard, works with IB as well as programmatically, etc. pp.
There is nothing wrong with fetching the window instance from the app's delegate. I just don't see the need for doing so. Seems rather complicated to me compared to the alternatives.
The willRotateToInterfaceOrientation method works well.
In addition to switching views, two other useful things you might want to do in there are:
1) Hide/Show the status bar. (I like to hide it in landscape)
[[UIApplication sharedApplication] setStatusBarHidden:UIInterfaceOrientationIsLandscape(toInterfaceOrientation) withAnimation:UIStatusBarAnimationSlide];
2) Hide/Show any UINavigationBar. (Maybe your landscape view will benefit from the extra height)
[self.navigationController setNavigationBarHidden:UIInterfaceOrientationIsLandscape(toInterfaceOrientation) animated:YES];
You could have one view controller that has the willRotateToInterfaceOrientation method, and that viewcontroller has two other viewcontrollers as variables.
Once the device rotates, you switch the viewcontrollers' views (very crude code example:)
-(void)willRotateToInterfaceOrientation: (UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration {
if ((orientation == UIInterfaceOrientationLandscapeLeft) || (orientation == UIInterfaceOrientationLandscapeRight)) {
[self.secondViewController.view removeFromSuperView];
self.firstViewController.view.frame = self.bounds;
[self.view addSubView:self.firstViewController.view];
} else {
[self.firstViewController.view removeFromSuperView];
self.secondViewController.view.frame = self.bounds;
[self.view addSubView:self.secondViewController.view];
}
}
My app has four tabs: A, B, C and D. Their UIViewController are managed by UITabBarController. The app supports rotation, and so each view controller returns YES to shouldAutorotateToInterfaceOrientation.
Using springs and struts, most of the rotation is done automatically by iOS. However, tab A also requires further positioning, and it is done in its VC's willRotateToInterfaceOrientation method.
When the VC for tab A is selected and the screen is rotated, that VC receives a willRotateToInterfaceOrientation message (propagated by iOS from UITabBarController), and the resulting rotation is correct.
However, when the selected tab is B and the screen is rotated, A's willRotateToInterfaceOrientation is not called. Makes sense. But if I then select tab A, I get only the results of applying its springs and struts, without the post-processing done by its willRotateToInterfaceOrientation.
After struggling with this for a while, and after failing to find a solution online, I came up with the following. I subclassed UITabBarController and in its willRotateToInterfaceOrientation I call all the VCs' willRotateToInterfaceOrientation regardless of which one is the selectedViewController:
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if (self.viewControllers != nil) {
for (UIViewController *v in self.viewControllers)
[v willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
}
It works, but it looks like a hack, and my question is whether I was doing the right thing. Is there a way to tell iOS to always call a VC's willRotateToInterfaceOrientation before displaying it for the first time after a screen rotation?
The best way to handle custom layout is by subclassing UIView and overriding the layoutSubviews method. The system sends layoutSubviews to a view whenever its size is changed (and at other times). So when your view A is about to appear on screen with a different size (because the interface was rotated while view B was on screen), the system sends view A a layoutSubviews message, even though it doesn't send view controller A a willRotateToInterfaceOrientation: message.
If you are targeting iOS 5.0 or later, you can override the viewDidLayoutSubviews method of your UIViewController subclass and do your layout there, instead of subclassing UIView. I prefer to do it in my view's layoutSubviews, to keep my view-specific logic separate from my control logic.
It's also a bad idea to do layout in willRotateToInterfaceOrientation: because the system sends that message before actually changing the size of the view, and before the rotation animation block. It sends the willAnimateRotationToInterfaceOrientation:duration:, layoutSubviews, and viewDidLayoutSubviews messages inside the rotation animation block, so the repositioning of your subviews will be animated if the view is on screen during the rotation.
Background: I want to make sure my viewControllers rotate properly when it appears. My viewControllers have excellent codes managing the rotation and orientation when it is visible.
Problem: Given two viewControllers in a NavigationController, viewC1 and viewC2. I did the following:
1.) Set rootViewController to viewC1
2.) Push viewC2 into the NavigationController
3.) Rotate
4.) Pop viewC2
5.) viewC1 is still stucked in the old orientation look (as in the transformation code in willAnimateRotationToInterfaceOrientation was not called) with the new orientation.
What can I do to ensure viewC1 call willAnimateRotationToInterfaceOrientation to reconstruct itself to look correctly in the new rotation?
Additional info:
This is all code (no storyboard/xib). I have shouldAutorotateToInterfaceOrientation return YES on all the views. I use willAnimateRotationToInterfaceOrientation to manage all my rotation.
Oh, and please no hacks. For example, copy the code from rotation then check the rotation mannually and manage it in viewDidAppear.
Think about the name of the method, and what you're trying to achieve.
willAnimateRotationToInterfaceOrientation indicates that the view controlled by the view controller is about to animate to a particular orientation. If your view is in the middle of a navigation stack, then it is not being displayed on screen. To animate something that isn't on screen is costly and ultimately worthless. So, that particular method is out of the question, but the problem that remains is there isn't anything else more appropriate in UIKit. The reason is to rotate something (even if not animated) when it's offscreen is worthless cost. It's the responsibility of the developer to handle a change in orientation when the view appears ("transformation on demand" as you will).
You say you don't want hacks, but the method you've described as a hack is probably the most appropriate thing to do. Having a generic method named something like
-(void) updateLayoutForOrientation:(UIInterfaceOrientation)orientation animated:(BOOL)animated { ... }
isn't a bad idea. This can be the handler for orientation change transformations for the whole view controller.
The places you need to possibly check/handle orientation issues are
-(void) viewWillAppear:(BOOL)animated
-(void) willAnimateRotationToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation duration: (NSTimeInterval) duration
and in both of these, call updateLayoutForOrientation:animated: to do the work for you.
I have setup a new iPad project to only support UIInterfaceOrientationLandscapeRight.
In my App Delegate I add a RootViewController to the window's rootViewController.
In this UIViewController (RootViewController) I have the following:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
I have also tried with:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
However, I am not able to get the correct dimensions for my app when I create and add subviews based on the dimensions of my view controller's view.
If I output self.view.frame for my view controller I get {{0, 0}, {768, 1024}}, but I would like {1024, 768} instead. If I can't when are the dimensions correct so I can create my views with them in mind?
Sorry if this has been asked a billion times, I've browsed lots of SO questions, but nothing has solved my issue.
I was running into the same issue, and what Ash Furrow said above seems to be correct; the orientation is set after viewDidLoad is called.
I was creating an iPad app that works in all orientations, but only the portrait orientations were getting set up correctly in my root UIViewController. In order to make the views layout correctly in landscape, I had to make sure the autoresizing masks on all my subviews was set to allow the view to adjust to landscape behind the scenes before being displayed to the user.
For example, I had a UIImageView that was the same size as the UIViewController's UIView. In order to get it to adjust correctly when rotating to landscape:
UIImageView *backgroundImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
backgroundImageView.autoresizingMask = (UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight);
[self.view addSubview:backgroundImageView];
Now the UIViewController can be configured in portrait orientation in the viewDidLoad method and rotate nicely to the portrait orientation before being displayed to the user.
EDIT:
Looks like the interface orientation is being set already in viewDidLoad
p (UIInterfaceOrientation)[self interfaceOrientation]
(UIInterfaceOrientation) $1 = UIInterfaceOrientationLandscapeRight
Here's my theory: interfaces on the iPad are, by default, 1024x768 if that have a status bar, which yours does. I believe that, even though the interface orientation is correct, it's not updating the view geometry until after viewDidLoad. I believe it has a very good reason for that.
If you look at the UIViewController Life Cycle docs, viewDidLoad is called as part of the set up of the view controller. After the view is loaded, willAnimateRotationToInterfaceOrientation: duration: is called to let your view controller know it's geometry is changing.
It's not so much an answer as an explanation. Hopefully this will help you architect a solution to get around this problem.
Begin answer that doesn't actually work:
In the info.plist for your project, open the "Supported Interface Orientations" option and delete the interface orientations you don't want to support. By default, all are supported:
That should clear it up.
I have a simple solution: the Apple default app templates work with XIBs for the main view controller. To solve your problem just open the main viewcontroller XIB and set orientation to "landscape".
As I usually don't use XIBs and create all UI elements programmatically. That's why I had the same problem in previous projects that really drove me crazy. I then solved it by hard-coding the frame width & height value - not the nice way.