Selective Autorotation of UIControllers - iphone

I did some experiments on autorotation.
The situation: I have a TabBar 4(tabs), three should be portrait only. The last one is a UINavigationController, which by itself should not autorotate any of the stacked controllers. It is basically a browsing application, as I show file and folders everything should be portrait. Some times, a special UIViewController is pushed, and I would like only this one to autorotate (it is always the last on the stack). In this last view, the tabbar is hidden.
How I achieved the goal: I subclassed the UITabBarController, to override the standard shouldAutorotate method behaviour:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if([self.selectedViewController isKindOfClass:[UINavigationController class]])
return [[(UINavigationController*)self.selectedViewController visibleViewController] shouldAutorotateToInterfaceOrientation:interfaceOrientation];
else
return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
This way, the answer of shouldAutorotate is forwarded to the controlled tabs, and in particular for the UINavigationController, it is again delegated to the visible UIViewController. Basically this works, as I have all the UIViewControllers answering NO, except for the particular one I described above: correctly, when rotating the Simulator, only when the special UIViewController is visible, the interface rotates to landscape, whici is perfect. The Tabbar here is hidden, so user don't get that also that one is rotated (which would be unconsistent in my design: basically whenever the tabbar is visible, which means everywhere except in this special view, the application is portrait only).
The problem is that I would like that, even if the device is still in landscape mode and user pops the special ViewController, the interface should behave consistently and return to portrait mode. Instead, when I pop, the interface stays in landscape (it's not designed in that way so it's a mess, of course) even when showing a UIViewController that would answer NO to shouldAutorotate... this is because (I think) the method is called only when rotation occurs, so until the rotation actually occurs again, the interface is rotated to landscape anyway.
How do avoid this? My first solution would be somehow to intercept the popping of the last view, and rotate manually the view before popping... but I'm not sure, I hope there is some more robust method to handle!!
I use the simulator with 3.0, dunno if this makes a difference.

I know that this is not a solution to your problem, but I think you should really avoid this kind of user interface when portrait-only portrait+landscape or landscape-only pages mixed on the same UINavigationController. Unfortunately the rotation management is extremely buggy and the bugs vary on different firmware versions.
I managed to quite the same thing in one of my projects, but had to remove it later due to firmware bugs: for example if you pressed the "back" button in landscape mode and went back to a portrait-only view, it often occured that the status bar and/or the navigation bar remained in landscape mode and the layout was completely broken. As far as I know this bug is not yet fixed although it was already present in firmware 2.x.
If you still want to do this I suggest the following things:
Make sure that all overridden UIViewController methods (init, viewWillAppear, etc) calls its [super methodName]. If not, auto-rotation is silently buggy. This was mentioned in the "Getting Ready for iPhone OS 3.0 Technical Note" (https://developer.apple.com/iphone/checklist/), but currently this document is unavailable :(
You may experiment with calling the undocumented [UIDevice setOrientation:] method when leaving the landscape view. It sometimes needs to be called twice, once with the current orientation and once with the desired orientation :) You may also need to call [UIDevice setStatusBarOrientation:] if the status bar remains in landscape mode. But note that Apple is likely to reject your application if you use these methods (they introduced an automatic tool some time ago which detects the presence of undocumented symbols in your application).

I had the same problem as you, and I solved this way:
I subclassed the UITabBarController, and added the following code:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if (self.selectedViewController)
return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
else
return (interfaceOrientation == UIDeviceOrientationPortrait);
}
This way, every child view controller could control its own orientation.

The problem is that you are rotating your UITabBarController (with the child view controller on top of it) rather than just the child view controller. You should be able to implement shouldAutorotateToInterfaceOrientation: only in your child view controller and have it work properly. It would also simplify your code.
I have never had this issue, but I've also never implemented shouldAutorotateToInterfaceOrientation: in a "container" view controller like a UITabBarController or UINavigationController.

The problem with your implementation is that you use the visibleViewController member of UINavigationController. You should use topViewController instead and everything will work as expected.

Related

Best practices for landscape only apps?

I'm a new iOS programming and I'm developing a simple iPhone game that needs to run in landscape only. I've ...
set supported orientations in the target settings
added the req. plist item (initial interface orientation)
overridden shouldAutorotateToInterfaceOrientation to return YES only for landscape modes
.. and the app "looks" correct, but there are a few odd things going on.
Issue 1 - I'm trying to manually position my views and not rely on autolayout. I've got a UIView in a NIB that I'm loading that needs to be positioned 150px from the right edge of the screen. I have to get the UIViewController's view's height (not width) to correctly position it - like it's not rotated to landscape at this point in the code.
Issue 2 - Implementing a UINavigationController to go from the title screen to the game interaction. When I'm pushing the interaction UIViewController to the stack, it slides in from the right like it's supposed to. When I go back to the title by popping the interaction, it slides UP to the title. It's seems like it's rotating back to portrait?
I think there is something very basic that I'm missing, but I can't find it in my app code. I've gone over the lists for a landscape app but they don't mention more than the list above.
Are there any other things/settings/methods to override that I should be on the look out for?
You need to set shouldAutorotateToInterfaceOrientation in your other viewControllers as well. Especially the ones displayed inside your UINavigationController.
Issue 2:
Don't use many UIViewController's. Use one view controller. Create one main UIViewController and for other UIViewController's just do:
[mainviewcontrl presentModalViewcontroller: child_viewcontrl animated: YES];
For delete a child view controller, use
[child_viewcontrl dismissModalViewControllerAnimated: YES];

UIViewController shouldAutorotateToInterfaceOrientation not working properly

Alright, I am not entirely sure if I will explain this sufficiently, but here it goes.
In my application I have multiple viewControllers, that are added and removed to display different views, so on and so forth. Some of these controllers, I want to allow to rotate while other I only want in say portrait mode.
However, when I go and change the shouldAutorotateToInterfaceOrientation to return YES for a desired orientation, or even just always return YES, nothing happens when I rotate the device in some of the views.
The first view that I add to the application will rotate properly and does what I want it to do, but any subsequent view that I add to the window, just does not want to rotate as desired.
I set my parentview (main UIWindow), to autoresizeSubViews, and still nothing.
Any suggestions?
I found my problem with this was simply how I was implementing my transitions and adding new views to the screen. Before I was just taking a viewcontroller viewcontroller, and adding it as a subview to the main view, so the only thing that could control the rotation was that main viewcontroller. I did not know about the [self.navigationcontroller pushviewcontroller] thing existed. So yeah, that is what I use now and it does exactly what I need it to do.
The view, you want to allow rotation, you need implement shouldAutorotateToInterfaceOrientation with return YES; for every view! That should work, however, if you don't want to allow landscape mode, then can return NO; for landscape mode. (Default mode is portrait).

iphone - TabBarController rotation question

My app has 4 tabs. All the view controllers support rotation, and indeed are rotated when I rotate the device. For one of the view controllers, I need to reposition some of the subviews upon rotation. I do this in willRotateToInterfaceOrientation of that view controller, and it works fine.
The problem comes when I switch to a different tab, then rotate the device, then go back to the original tab. It apparently has not received the rotation notification, since willRotateToInterfaceOrientation has not been called. So it seems as though only the "active" view controller gets notified that the device has rotated.
The question: how do you get all the view controllers (controlled by a TabBarController) to rotate?
Unfortunately this is a bug in iOS 3.x. It works fine in iOS 4.x. I've seen apps that manually keep track of orientation changes and then do the rotation manually for inactive viewcontrollers. Sucks.
Looking through the iOS 3.2 docs to make sure this works, there is a viewControllers property in UITabBarController. Try something like this:
for (UIViewController * viewController in tabBarController) {
// Do stuff here with each 'viewController'.
}
I recommend that you do something with the UIViewController's -shouldAutorotateToInterfaceOrientation: method but you may have another way in which you plan on achieving the rotation.
You should also check for the interface orientation in viewWillAppear method of the controller whose subviews frame you are changing.Because when you move to the new tab and rotate the device and now when you tap another tab the viewWillAppear method will we called and there you can change the frames accordingly.
I also faced the same problem which i sorted out using this approach

Forcing UIInterfaceOrientation changes on iPhone

I'm strugging with getting an iPhone application which requires just about every push or pop in the Nav Controller Stack to change orientation.
Basically the first view is portrait, the second landscape the third portrait again (Yes I know this is less than ideal, but that's the design and I've got to implement it).
I've been through various advice on here....
How do I detect a rotation on the iPhone without the device autorotating?
Force portrait orientation on pushing new view to UINavigationViewController
Is there a documented way to set the iPhone orientation?
But without total success.
Setting to link against 3.1.2 my reading of the linked articles above seems to indicate that if my portrait view pushes a view with
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return ((interfaceOrientation == UIInterfaceOrientationLandscapeRight) );
}
Then then that view should appear rotated to landscape. What happens is it appears in its "broken" portrait form, then rotates correctly as the device is turned.
If I pop the controller back to my portrait view (which has an appropriate shouldAutoRotate...) then that remains in broken landscape view until the device is returned to portrait orientation.
I've also tried removing all the shouldautorotate messages, and instead forcing rotation by transforming the view. This kind of works, and I've figured out that by moving the status bar (which is actually hidden in my application) [UIApplication sharedApplication].statusBarOrientation = UIInterfaceOrientationLandscapeRight; the keyboard will appear with the correct orientation when desired.
The problem with this approach is that the status bar transform is weird and ugly when you don't have a status bar - a shadow looms over the page with each change.
So. What am I missing.
1) Am I wrong in thinking that in 3.1.2 (or possibly earlier) shouldAutorotateToInterfaceOrientation should provide the desired orientation simply by pushing controllers ?
2) Is there another way of getting keyboards to appear in the correct orientation.
3) Are the undocumented API calls the way to go (please no!)
You shouldn't use [UIViewController shouldAutorotateToInterfaceOrientation:] to trigger an orientation change; it's only there to let the system know if automatic rotations are allowed. You should still update it to specify the orientation that's allowed though.
If you want to change the orientation when a particular view is showing, you should call [UIApplication setStatusBarOrientation:animated:] inside your [UIViewController viewWillAppear:] override method for each of the view controllers that force a particular orientation. That will cause a change when a view is being pushed onto the stack and when it's being popped off it. Make sure you call super in your override method.
This is also the right place to change how the status bar is displayed, if that's something you're doing.

UISplitViewController and complex view hierarchy

I'm doing an iPad tech demo and I'm running into a serious technical problem.
I have an app concept that leverages UISplitViewController, but NOT as the primary controller for the entire app.
The app flow could be described roughly as this:
Home screen (UIViewController)
List->Detail "Catalog" (UISplitViewController)
Super Detail Screen (UIViewController but could conceivable also be a child of SplitView).
The problem is in the flow between Home and Catalog. Once a UISplitViewController view is added to the UIWindow, it starts to throw hissy fits.
The problem can be summarized at this:
When a UISplitView generates a popover view, it appears to then be latched to its parent view. Upon removing the UISplitView from the UIWindow subviews, you will get a CoreGraphics exception and the view will fail to be removed.
When adding other views (presumably in this case, the home screen to which you are returning), they do not autorotate, instead, the UISplitView, which has failed to be removed due to a CG exception, continues to respond to the rotation instead, causing horrible rendering bugs that can't be just "dealt with". At this point, adding any views, even re-adding the SplitView, causes a cascade of render bugs.
I then tried simply to leave the SplitView ever present as the "bottom" view, and keeping adding and removing the Home Screen from on top of it, but this fails as SplitView dominates the Orientation change calls, and Home Screen will not rotate, even if you call [homeScreen becomeFirstResponder]
You can't put SplitView into a hierarchy like UINavigationController, you will get an outright runtime error, so that option is off the table. Modals just look bad and are discourages anyway.
My presumption at this moment is that the only proper way to deal with this problem is so somehow "disarm" UISplitViewController so that it can be removed from its parent view without throwing an unhandled exception, but I have no idea how.
If you want to see an app that does exactly what I need to do, check out GILT Groupe in the iPad app store. They pulled it off, but they seem to have programmed an entire custom view transition set.
Help would be greatly appreciated.
Apple states:
The split view controller’s view
should always be installed as the root
view of your application window. You
should never present a split view
inside of a navigation or tab bar
interface.
This does mean it should be root view and not subview of another view. Even though they add:
You should never present a split view inside of a navigation or tab bar interface
That does not mean you can add it as a subview of any other controller either. (sorry)
I have a feeling that what you are experiencing is the byproduct of trying to do so. I am actually surprised that GILT Groupe's app did not get rejected. Apple has a tendency to enforce these HIG guidelines rather strictly lately. They (as you found out already) cause a rather nasty runtime error when you attempt to add them to a NavigationController.
I've solved this for myself... actually worked around... by presenting all other possible full screen views as modals of the SplitView...
This is an unsavory way of doing things in my book, but Apple leaves you little choice if you want to leverage a SplitView only "sometimes" within an app.
I had some success by creating a second UIWindow. I associate the UISplitViewController with that, and switch it out with the main window when I want to show the splitview. It seems to work they way I wanted, except for a slight delay in rotations and a log message about "wait_fences".
Unless your developing for jail-broken devices then bending apples rules/wishes isn't a good idea. Like Jann and Jasconius state above this means keeping a splitView controller view root, not over-using modals (vague) and not using multiple windows.
Also, the Gilt app is only available in the US
I'v been trying to find a solution too and have ended up programatically removing views from the window like Tuannd talks about but the landscape rendering bug is unforgivable.
#Jasconius, What is the max number of modals are you are presenting at any time?
I am struggling with this same issue. I've been trying various things poking at the UISplitViewController as a black box and see how it reacts.
I seem to have come up with a solution to my case which seems to be working satisfactorily.
The key appears to be that the first view added to the UIWindow is the only view properly initialized. All the problems I've had tend to stem from incorrect notification of the orientation of the device. The first view added, apparently has this correctly configured.
In my case I didn't want the UISplitView as the first view. The following is working for me.
The app delegate application:didFinishLaunching method is special. Adding the view to the UIWindow must occur here. If it is done elsewhere it does not get configured properly.
Essentially the magic sauce, is have the split view be the first view added to the window. Its then ok to remove it as long as you retain the UISplitViewController. From then on you can swap other views in and out, including the UISplitView and most things seem to be ok.
I've still run into a few issues. Popovers on views other than the split view are confused on the views frames and location of toolbar buttons and will display in the wrong location. I place then in a specific location and that seems to handle that case.
If a popover on the split view is still displayed, and you try to view another view, the orientation of the second view is confused and shows up sideways. If that view is accessed before the popup is displayed, all is well. I've fixed this my manually dismissing the popover before switching to any other view.
Here's the code if it helps. All the controllers are instance variables of appDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// This also seems to work as good magic. Seems to set orientation and size properties that persist.
[window addSubview:splitViewController.view];
[splitViewController.view removeFromSuperview];
[self switchToNewViewController:firstController];
[window makeKeyAndVisible];
return TRUE;
}
- (void)switchToNewViewController:(UIViewController *)newViewController {
[popoverController dismissPopoverAnimated:FALSE];
if (newViewController != currentViewController) {
[currentViewController removeFromSuperview];
currentViewController = newViewController;
[window addSubView:newViewController.view];
}
}
Just wanted to say that I was running into these same issues, found this forum topic, and followed the advice from g051051 above. This is working perfectly for me. I am not seeing any glitch, and no messages about wait_fences in the console of the device.
I simply used IB to create two UIWindow objects in the main XIB, created as normal the UISplitViewController and then also an instance of my other controller derived from UIViewController (which I am using for full screen display). I have simply hooked them up by attaching the rootViewController for each UIWindow to its appropriate controller.
In application:didLaunch...: method I can decide which window to send the makeKeyAndVisible method and which to set to hidden. When the user want to switch back and forth I simply have to send makeKeyAndVisible to one and set the hidden property on the other, that's all there is to it.
As indicated all of the rotation related messages are sent to each controller appropriately, regardless of which one is associated with the currently visible window.
Anyway, works great for me, and actually quite easy to set up.