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.
Related
The rotation animation occurs for the status bar (which has the clock and the battery icon), but the view itself just changes size, it doesn't do the page flip animation. In the gif (below), I did a few screencaptures of the rotation animation in slow motion. You can see the clock and battery icon rotate into the view, even though the content just scales.
http://imgur.com/gallery/Q3OXCIH
I found some similar, but not quite the same posts:
iOS Device Rotation Instant Snap rather than animation
iOS 9 Orientation Auto-Rotation Animation Not Working, But Always on Main Thread
This is somewhat repeatable- at first, the rotation occurs correctly, but after I programmatically change the tab view controller index, it can trigger. After it triggers, the rotation animation does not occur for the view until after I reset the app.
Code where I change the tab view controller and then change it back:
[appDelegate.tabBarController setSelectedIndex:0];
...code to operate on the code at index 0...
[appDelegate.tabBarController setSelectedIndex:2];
To emphasize- it DOES animate the rotation correctly when I first run the app. Behaves the same in simulator and in hardware. IOS9. Xcode 7.1.1.
Anyone know why a viewcontroller's content would stop animating during rotation?
edit-
To answer fragilecat's questions:
1) I am set up to use the rotation functions, as described in https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/#//apple_ref/occ/clm/UIViewController/attemptRotationToDeviceOrientation
I have implemented shouldAutorotateToInterfaceOrientation and shouldAutorotate and supportedInterfaceOrientations. The supportedInterfaceOrientations gets called once, when the viewcontroller loads. shouldAutorotateToInterfaceOrientation and shouldAutorotate are apparently never called.
2) I am receiving size change messages via viewWillTransitionToSize- this is ios9 so there aren't any rotation messages. willTransitionToTraitCollection is apparently never called, though it is overriden. I am calling the super for both.
3) I am not using viewWillLayoutSubviews() or viewDidLayoutSubviews(). I am only overriding viewDidLoad and viewWillAppear. These do not affect rotation.
4) I am not dynamically changing rotation methods.
What I did notice, is that rotation works at first, but then fails (doesn't rotate but just scales), after I change the tabBarViewController selectedViewController programmatically after the use clicks "ok" to an alertview. I haven't figured out why yet, but it is repeatably after that event.
Sequence of the bug:
Works fine, rotates ok.
User hits "ok" to an alertview
I programmatically call [tabBarViewController setSelectedIndex:0]
I call some functions on the viewcontroller at index 0.
I programmatically call [tabBarViewController setSelectedIndex:2] (back to the original)
rotation now does not occur reliably
Anyone know why a viewcontroller's content would stop animating during rotation?
The child view controller's rotation method's are configure incorrectly.
Your child view controller's are not receiving rotation messages.
You have custom code in the view controller's layout methods.
Your code is dynamically altering rotation/layout/animation methods.
I am looking for direction on what kind of bugs can produce this type of error.
This is some what hard to answer with out seeing your code, but here are the steps I would take in trouble shooting this issue.
Confirm that your child view controllers are correctly setup for rotation.
UIViewController Rotation Methods
Confirm that your child view controller's are receiving the following messages so that you can rule out this as the issue as this is how rotation is handled in iOS 9.
func willTransitionToTraitCollection(_ newCollection: UITraitCollection,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
func viewWillTransitionToSize(_ size: CGSize,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
If you are overriding these methods else where you should make sure you call the super's version, see UIContentContainer for details.
Comment out any custom code that you might have in viewWillLayoutSubviews() implementation of your view controllers. Also you can check your frames between viewWillLayoutSubviews() and viewDidLayoutSubviews().
EDIT
You need to execute the tab change on the main thread. If I am correct you are using the delegate of UIAlertView? I don't think your on the main thread went you make your call!
dispatch_async(dispatch_get_main_queue(), ^{
[appDelegate.tabBarController setSelectedIndex:0];
});
I have a LoginViewController that you can navigate back and forth from the MainMenuViewController. I am adjusting subviews' positioning and size programmatically using willAnimateRotationToInterfaceOrientation
This works great. The problem I am having is that I need to check the orientation right when the ViewController is loaded, in case it is loaded and you are in landscape orientation, the same changes I make in willAnimateRotationToInterfaceOrientation will be made.
The problem is, the earliest point I can get self.interfaceOrientation is in the viewDidAppear method, and this causes the user to see the original sized/positioned subviews for a split second before it transitions to the landscape-appropriate sizes/positions. I tried in viewDidLoad and viewWillAppear and neither of these work because (what I believe), self.interfaceOrientation is still NULL at this moment. How can I work around this and get the changes to be made prior to the user viewing the page (viewDidAppear)?
Any help would be greatly appreciated. Thank you!
It seems like you are looking for this method :
- (void)viewWillLayoutSubviews
{
}
https://developer.apple.com/library/ios/documentation/uikit/reference/UIViewController_Class/index.html
"The viewWillLayoutSubviews method is also called after the view is resized and positioned by its parent."
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.
I've got a universal ipad/iphone app that allows the user to watch a video, which they can then expand into full screen mode.
I have implemented (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration, and in that method I perform various setFrame calls on my view elements depending on whether they are in landscape or portrait orientation.
That all seems to work fine in normal use, i.e. rotating back and forth works fine.
But if the user starts in portrait mode, starts a video, goes to full screen mode, turns into landscape orientation, and then the video stops -- the elements are often not resized properly. They appear to be sized still as if they are portrait mode.
If I then turn to portrait mode, and then turn back to landscape, the view resets correctly.
The strange part is, I have implemented (void)exitedFullscreen:(NSNotification*)notification and in there I print out the orientation, and it's seen correctly. I also call my code to reset the view elements based on the current orientation, and I am still having this problem.
Another related issue is sometimes when dealing with rotation, my views will end up too far up the screen, actually going under the status bar at the top of the device.
Edit Here's the latest example. I rotate to landscape mode during full screen video playback, and then when I left full screen video, you can see the issue with the navigation bar at the top of the view.
One possible way to solve this is by presenting your view controller modally instead of using the navigation view controller.
Refer to Kenny's answer at Problem pushViewController from Landscape to Portrait
Your ViewController might not be rotating because another controller is the first responder. What you can do to avoid this is register the view controller to the device rotation changes and implement the rotation in the selector you call when you receive such a notification.
In appDelegate:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
In your view controller
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didRotate:)name:UIDeviceOrientationDidChangeNotification object:nil];
In did rotate you can check the orientation with
[[UIDevice currentDevice] orientation]
The navigation bar at the top of the view. I solved it, using this code ->
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault animated:YES];
Using this after your rotation.
Mason, did you logged and checked whether your method willAnimateRotationToInterfaceOrientation:duration: gets called after each state transition?
To me this latest screenshot does not look like an orientation change issue.
The navigation bar is basically off by the status bar's height.
Possibly your position calculation fails because you are using the view's frame
while the fullscreen video (w/o status bar) is playing and this fails as soon as
the statusbar is back?
Your orientation may not get updated properly if there is another controller acting as a first responder. The best way to overcome this is to call the functions you use to orientate the screen at the method viewWillAppear: using the current orientation of the view controller: [self interfaceOrientation]
If you use a subclassed subview you may need to reimplement the methot layoutSubviews and call setNeedsLayout. Another thing that may be causing this is resigning the viewcontroller where you have the video as first responder (you mays search if somewhere you use the methon resignfirstresponder and try how it works without it). If this does not work, I don't know, this things may be very tricky and dependent on how you have implemented it. But for the things you say you do you should not need much code, since automatic rotation and resizing of views is handled now by the sizes inspector of the views editor.
I think that this should do.
How does a UISplitViewController know when it has rotated so that it can trigger the appropriate behavior with managing its views? Is there some way I can manually trigger it myself? I have a split view controller owning a view that is not at the root of my hierarchy, so it is not getting the rotation events that (I think) normally allow it to handle rotation behavior.
You can try to implement UISplitViewController delegate which is:
// Landscape mode
– splitViewController:willShowViewController:invalidatingBarButtonItem:
// Portrait mode
– splitViewController:willShowViewController:invalidatingBarButtonItem:
Since the masterView (left) will show/hide accordingly when the rotation occurs, I found this is more effective compared to handling the orientation changes if each view
I guess UiSplitViewController doesn't autorotate and
iPad: SplitView does not rotate pretty much say that unless the controller's view is the root view, it won't work. Oh apple.
You could sign up for notifications of orientation changing, make sure you have shouldAutorotateToInterfaceOrientation set to YES for the rotations you want to support as well.