Switching back from horizontal view causes iPhone application to crash - iphone

Here is my problem:
- I have one navcontroller and inside it one tabbar controller with 4 view controller.
- I want to add following functionality:
- On landscapeRight to dismiss navcontroller, tabbar controller and everything and load whole new controller - this one goes ok, here is the code:
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
// Just delete the lines for the orientations you don't want to support
if(toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
[appDelegate ToHorizontalAverageResponseView:self];
}
return YES;
}
Here is the code in the App Delegate:
- (void)ToHorizontalAverageResponseView:(id)sender
{
HorizontalResponseViewController *tempController = [[HorizontalResponseViewController alloc] initWithNibName:nil bundle:nil];
[self setHorizontalResponseViewController: tempController];
[tempController release];
//[UIApplication sharedApplication].statusBarOrientation = UIInterfaceOrientationLandscapeRight;
// View rotation transformation
CGAffineTransform landscapeTransform = CGAffineTransformMakeRotation( M_PI * 90.0 / 180.0 );
[[horizontalResponseViewController view] setTransform:landscapeTransform];
[window addSubview:[horizontalResponseViewController view]];
}
The question is how to transfer back to portrait view and all those navbar and tabbar controllers?
Thx,
Mladen

It sounds like you are trying to build something along the lines of the iPod-CoverFlow transition. The question (on how to build the coverflow effect) was asked in Stanford's CS193P lecture and the answer hinted in the following direction:
If you can afford the memory overhead, it may be the best thing to add a new landscape view on top of all the portait views (not discarding them as you suggest). The user then interacts with this landscape view as desired until going back to portrait. All this time, the portrait views remained quietly in the background.

Related

How to rotate UIViews?

The Twitter app is a tab bar app on the iPhone. Nothing in any of the tabs will rotate, yet, when you click on a link from a tweet, the view controller that is pushed on top of it IS allowed to rotate. The only rotations I have ever doe is from tilting the device, landscape or portrait, but I don't understand how to use 2d transformations and animations to rotate views.
How do you rotate any view with that's a subclass of UIView?
You can use UIView animations and Transform methods.
For rotating 90 degrees:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
imageView.transform = CGAffineTransformMakeRotation(M_PI_2);
[UIView commitAnimations];
Hey I had a similar problem with rotation. I had a tabbar app that I wanted to remain portrait but have a video player that allowed all rotations.
I managed to do this fairly easily by sub-classing the tabbar and only allowing portrait rotation while attaching the video player to the Root View Controller that allowed all rotations. Check out my original post here. Hope its of some help!
Check this tutorial, i think this is what you wanted to do
http://www.raywenderlich.com/9864/how-to-create-a-rotating-wheel-control-with-uikit
http://www.raywenderlich.com/5478/uiview-animation-tutorial-practical-recipes
This is the best and perfect work for me and i think this the right answer.
Just Give Number How many Times You Want To Rotate In Place Of 50 In toValue
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
NSNumber *currentAngle = [CircleView.layer.presentationLayer valueForKeyPath:#"transform.rotation"];
rotationAnimation.fromValue = currentAngle;
rotationAnimation.toValue = #(50*M_PI);
rotationAnimation.duration = 50.0f; // this might be too fast
rotationAnimation.repeatCount = HUGE_VALF; // HUGE_VALF is defined in math.h so import it
[CircleView.layer addAnimation:rotationAnimation forKey:#"rotationAnimationleft"];
Ref From How to implement UIViewController rotation in response to orientation changes?
I do this by having a root view controller (this could be a UITabBarController) and in it's viewDidLoad method i subscribe to rotation events:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(didRotate:)
name:#"UIDeviceOrientationDidChangeNotification"
object:nil];
Then in the didRotate: method i look at which view controller is visible when the rotation happened, and what orientation the phone is in:
- (void) didRotate:(NSNotification *)notification {
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
/*
DEVICE JUST ROTATED TO PORTRAIT MODE
orientation == UIDeviceOrientationFaceUp ||
orientation == UIDeviceOrientationFaceDown
*/
if(orientation == UIDeviceOrientationPortrait) {
/*
DEVICE JUST ROTATED TO LANDSCAPE MODE
*/
}else if(orientation == UIInterfaceOrientationLandscapeLeft ||
orientation == UIInterfaceOrientationLandscapeRight) {
}
}
Within that didRotate: you can look at which is the visible viewController and do what you want from there.
I present a modal view controller in landscape mode when a particular view controller is visible and the phone is rotated into landscape. If any other view controller is visible, i ignore the event.
I force my modal view controller to display in landscape mode in its viewWillAppear method - i can give anyone this code if they want it.
Hope this helps.
You are able to implement the shouldAutorotateToInterfaceOrientation method. In the Twitter app i am guessing they allow only portrait orientation for the tabBarController, but allow landscape in the modalViewController using this method. look it up in the documentation, it's pretty simple I think. I hope this answers your question!
#Mangesh-Vyas has some good information but doesn't quite answer the question (as I read it). The reason that the views that Twitter (among other apps) presents can rotate, when the tabbar or navbar cannot, is because they are presented modally. Views which are presented modally aren't constrained to the allowable orientations of the controller which presented them.
Edit:
Perhaps you are expecting a stock UIViewController to rotate. If so then you will be disappointed, as only (to the best of my knowledge) custom subclasses can be made to rotate to arbitrary interfaceOrientations. The following code will do the trick:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOr‌​ientation
{
return YES;
}
Edit 2:
Here's the code that makes the whole thing go:
AppDelegate.m
#import "RotateViewController.h"
#interface AppDelegate () {
UINavigationController* nc;
}
- (void)pushVC;
- (void)openVC;
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIViewController* vc = [[UIViewController alloc] init];
vc.view.backgroundColor = [UIColor blueColor];
vc.title = #"Root";
UIButton* b = [UIButton buttonWithType:UIButtonTypeRoundedRect];
b.frame = CGRectMake(50, 150, 220, 40);
[b.titleLabel setText:#"Push VC"];
[b addTarget:self action:#selector(pushVC) forControlEvents:UIControlEventTouchUpInside];
[vc.view addSubview:b];
b = [UIButton buttonWithType:UIButtonTypeRoundedRect];
b.frame = CGRectMake(50, 200, 220, 40);
[b.titleLabel setText:#"Modal VC"];
[b addTarget:self action:#selector(openVC) forControlEvents:UIControlEventTouchUpInside];
[vc.view addSubview:b];
nc = [[UINavigationController alloc] initWithRootViewController:vc];
UITabBarController* tc = [[UITabBarController alloc] init];
[tc addChildViewController:nc];
// uncomment this line to see this work in a nav controller
// [self.window setRootViewController:nc];
[self.window setRootViewController:tc];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
- (void)openVC {
RotateViewController* rc = [[RotateViewController alloc] init];
[nc presentModalViewController:rc animated:YES];
}
- (void)pushVC {
RotateViewController* rc = [[RotateViewController alloc] init];
[nc pushViewController:rc animated:YES];
}
RotateViewController is a stock subclass of UIViewController with the shouldAutorotate function from above.
Nota bene UITabBarControllers can rotate to a given interface orientation if, and only if, every one of their child view controllers returns YES for that orientation. UINavigationViewControllers can rotate to a given interface orientation if their top view controller returns YES for that interface orientation.

On user action rotate UIViewController

What i have:
i have a viewcontroller that supports Portrait and landscape. When in landscape i show a filter view for the content of the portrait.
What i need to do:
When the user selects a filter in the landscape view i want to force rotate the device back to portrait.
I have done some Googling and discovered
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];
This is a private API and i dont want to do that. (But excatly what i am after)
In short - on user action i want to force rotate the device.
Is there something i am missing from the docs?
Any ideas would be super.
Dan
Learned this from experience! Do this in your viewDidLoad::
UIViewController *vc = [[UIViewController alloc] init];
[self presentModalViewControllerAnimated:NO];
[self dismissModalViewControllerAnimated:NO];
It will force landscape, provided you return YES for shouldAutorotateToInterfaceOrientation for only landscape. No private APIs!
You can try with this If works.
-(void)forceFullyPortraitView{
[self.appDel.navigationController.view removeFromSuperview];
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait];
[ [UIApplication sharedApplication].self.delegate.window addSubview:self.appDel.navigationController.view];
self.navigationController.navigationBar.hidden=NO;
}
-(void)forceFullyLandscapeView{
[self.appDel.navigationController.view removeFromSuperview];
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
[ [UIApplication sharedApplication].self.delegate.window addSubview:self.appDel.navigationController.view];
self.navigationController.navigationBar.hidden=NO;
}
You can set landScepe right or left.

UIInterfaceOrientation changes not working in subviews

I am adding UIInterfaceOrientation changes to the app. My app is not a tab-based or navigation based. I am adding subviews on top of other.
I went through the document why interface orientation changes not work?
http://developer.apple.com/library/ios/#qa/qa1688/_index.html
I found that the main window controls the interface orientation changes.
But now as my app is not tab-based or navigation-based, is there still any way to achieve it?
P.S. : In my app only launch screens are working properly for interface orientation changes and not the added subviews.
Please help
Thanks in advance.
Use following method to resize subview. OR use autoresizingMask
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if(toInterfaceOrientation == UIInterfaceOrientationPortrait){
viewInformationContainer.frame = CGRectMake(0, 61, 320, 403);// here you can change resize or subviews
}
else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight || toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
viewInformationContainer.frame = CGRectMake(0, 0, 480, 227);//here you can change resize or subviews
}
}
Hope, this will help you...
Reading the comments posted above and the problem that you are facing what I can say is that You have added a hierarchy of subviews. Consider the following Case
1) I add a view controller (VC) , Navigation Controller , Tab bar controller directly to the window.. the subsequent views will get notify about any orientation changes properly.
2) I add a Parent view Controller to the window and later add views of some other view controllers to my parent view controller, in this particular case you will get the orientation change delegate methods being called properly in the Parent VC but not in the subsequent view controllers whose view you have added to your Parent VC
I once added a Base view controller to my application window where base controller had a ScrollView , I later added subviews of my four view controllers to scrollview.
(void)viewDidLoad {
DNAppDelegate *appDelegate = (DNAppDelegate*)[UIApplication sharedApplication].delegate;
CGRect viewCustormFrame = appDelegate.viewCustomTabBar.frame;
viewCustormFrame.origin.y = self.view.frame.size.height-35;
if(UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation)) {
viewCustormFrame.size.width = self.view.frame.size.width;
}
else {
viewCustormFrame.size.width = self.view.frame.size.width;
}
[self.view addSubview:appDelegate.viewCustomTabBar];
appDelegate.viewCustomTabBar.frame = viewCustormFrame;
[srclViewMainContent addSubview:appDelegate.homeController.view];
[srclViewMainContent addSubview:appDelegate.newsListController.view];
[srclViewMainContent addSubview:appDelegate.columnController.view];
[srclViewMainContent addSubview:appDelegate.whatsOnController.view];
currentVisisbleController = appDelegate.homeController;
[self resetAllSizes];
[super viewDidLoad];
}
In above case though i got all orientation delegate methods being called in base VC but not in the other Four VC.. so i had to tweak something like
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
DNAppDelegate *appDelegate = (DNAppDelegate*)[UIApplication sharedApplication].delegate;
[appDelegate.homeController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
[appDelegate.newsListController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
[appDelegate.columnController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
[appDelegate.whatsOnController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
DNAppDelegate *appDelegate = (DNAppDelegate*)[UIApplication sharedApplication].delegate;
currentPageOffset = srclViewMainContent.contentOffset.x/srclViewMainContent.frame.size.width;
[appDelegate.homeController willRotateToInterfaceOrientation:toInterfaceOrientation duration:(NSTimeInterval)duration];
[appDelegate.newsListController willRotateToInterfaceOrientation:toInterfaceOrientation duration:(NSTimeInterval)duration];
[appDelegate.columnController willRotateToInterfaceOrientation:toInterfaceOrientation duration:(NSTimeInterval)duration];
[appDelegate.whatsOnController willRotateToInterfaceOrientation:toInterfaceOrientation duration:(NSTimeInterval)duration];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
DNAppDelegate *appDelegate = (DNAppDelegate*)[UIApplication sharedApplication].delegate;
[self resetAllSizes];
CGRect visibleRect = CGRectMake(currentPageOffset*srclViewMainContent.frame.size.width, 0, srclViewMainContent.frame.size.width, srclViewMainContent.frame.size.height);
[srclViewMainContent scrollRectToVisible:visibleRect animated:NO];
[appDelegate.homeController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
[appDelegate.newsListController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
[appDelegate.columnController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
[appDelegate.whatsOnController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
}
i.e call these delegate methods on respective VCs from the Base VC. I had to do this because my application was completely build and later I had to change the architecture to allow scrolling among various views, But I had already implemented these views by taking four View controllers.
In a nutshell any VC (also Navigation Controller, tab Bar controller, split View controller)added directly to window will propagate these Orientation change delegate methods to subsequent VCs but a View controller will not pass these delegate methods to VCs coming down the lane
I hope this gives you some insight. Just in case if you come across any good solution do post in as an answer and share with us all.

20px gap on top of landscape view in iPhone app running on iPad

I am running an "iPhone-only" app in the iPad simulator...When the orientation of the device is changed to landscape mode, I have a view controller that kicks in and programmatically loads a WebView. This works swimmingly in the iPhone (no gap on top of landscape view), but when simulating in the iPad, there's a 20px (I think?) gap at the top of the view.
Here's the code in the landscape view controller's viewDidLoad where I load the WebView:
[super viewDidLoad];
// Initialize webview and add as a subview to LandscapeController's view
CGRect webFrame = [[UIScreen mainScreen] bounds]; // Use bounds to take up entire screen
self.myWebView = [[[UIWebView alloc] initWithFrame:webFrame] autorelease];
self.myWebView.scalesPageToFit = YES;
self.myWebView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
self.myWebView.delegate = self;
[self.view addSubview: self.myWebView];
// remove status bar from top of screen
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackOpaque];
Setting the status bar to default, has no influence.
I can't seem to figure out why this would be fine on the iPhone, but emerge on the iPad???
Any ideas? Thanks in advance!
The screen bounds are in screen coordinates. You're adding the webview as a subview of self.view; its frame is in self.view coordinates. You want to fill your view, not the screen (your view is automatically resized by UIViewController/the rest of UIKit, which should end up resizing the web view to to auto-resizing):
self.myWebView = [[[UIWebView alloc] initWithFrame:self.view.bounds] autorelease];
It's not safe to change the status bar in -viewDidLoad. View-loading can happen anywhere (it happens when anything calls viewController.view). I'm also not sure why you're setting the style; you want to set hidden-ness:
In -viewWillAppear:, do [application setStatusBarHidden:YES animated:animated];
In -viewWillDisappear:, do [application setStatusBarHidden:NO animated:animated];
Finally, you might be seeing different behaviour because the iPad is running OS 3.2.x and the phone is running 3.1.x or 4.x. Additionally, the iPhone-compatibility mode uses a dummy status bar; the "real" status bar always stays at the edges of the screen.
20 pixels at the top is almost always invariably related to the height of the status bar.
The code you posted looks fine... for setting up the UIWebView. But you're adding it as a subview to your view controller's view. How is that sized? What is its frame?
I finally figured this out. The key missing component, which I didn't mention in the original post, is that the view controller that manages landscape is actually implemented as a modal view. (See the View Controller User Guide for code on how to do this) In concept, I have a Portrait view controller. (which is the primary controller) In the Portrait view controller's viewDidLoad I apply for a Notifier that is triggered off of a change in the orientation like so:
- (void)viewDidLoad {
[super viewDidLoad];
// SECTION to setup automatic alternate landscape view on rotation
// Uses a delegate to bring the landscape view controller up as a modal view controller
isShowingLandscapeView = NO;
// Create Landscape Controller programmatically
self.landscapeViewController = [[LandscapeViewController alloc] initWithNibName:#"LandscapeViewController" bundle:[NSBundle mainBundle]];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
// END SECTION landscape modal view controller
Then, when orientation changes this method is called:
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) && !isShowingLandscapeView)
{
// Load Landscape view
landscapeViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:self.landscapeViewController animated:YES];
isShowingLandscapeView = YES;
}
At the same time I was removing the status bar from the Landscape view controller's viewWillAppear method:
- (void)viewWillAppear:(BOOL)animated
{
// remove status bar from top of screen
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:animated];
self.myWebView.delegate = self; // setup the delegate as the web view is shown
}
and this is where the problem is introduced. The Portrait view controller captures the screen dimensions, before transitioning to landscape as a Modal View. Then, viewWillAppear, in the Landscape view controller, removes the status bar.
So, the solution is to move the
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:animated];
statement to the orientationChanged method in the Portrait view controller, BEFORE transitioning to the Landscape Modal View.
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) && !isShowingLandscapeView)
{
// remove status bar from top of screen
// NOTE: this must be declared BEFORE presenting the Modal View!!!! If it's not, the landscape view will
// contain an ugly white bar in place of the missing status bar at the top of the view.
[[UIApplication sharedApplication] setStatusBarHidden:YES];
// Load Landscape view
landscapeViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:self.landscapeViewController animated:YES];
isShowingLandscapeView = YES;
}
Note, that as tc helpfully mentioned above, if you want the status bar to appear when orienting back to Portrait, then you need
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:animated];
in the viewWillDisappear method in the Landscape view controller.

Iphone: Cannot switch back from my landscape view to a portrait view

I am working on an app (my first one), which is basically a TabBar app.
To be more precise there are:
- a login view controller
- a tab bar controller (when login is done)
- a landscape view controller that is used when the first itel of the TabBar is switch from Portrait to Landscape.
So, when I am in the first tab, I need to be able to move to landscape view to display some other data. In my tab bar controller, I have implemented those methods:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if([self selectedIndex] == 0)
return YES;
return NO;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
// Get AppDelegate
MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
// Remove TabBarView and add graph Landscape View
[self.view removeFromSuperview];
[delegate setSubViewLandscapeViewController];
}
}
In the delegate, I have implemented the setSubViewLandscapeViewController and the setSubViewTabBarController:
- (void)setSubViewTabBarViewController {
[window addSubview:[tabBarController view]];
}
- (void)setSubViewGraphLandscapeViewController {
[window addSubview:[landscapeViewController view]];
}
I want the landscapeViewController to display only in landscape mode, I have then (in my landscapeViewController):
- (void)viewWillAppear:(BOOL)animated {
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeRight];
}
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
NSLog(#"willRotateToInterfaceOrientation");
}
A part of this works fine, I mean the switch from portrait to landscape is ok (when I am in the first tab), the tabbarcontroller is remove from the SuperView and the landscape view is added as a subview instead.
The thing is... I do not know how to switch back to portrait mode (and then load the previous controller, the tabBar one using the setSubViewTabBarViewController of my delegate). It seems none of the willRotateToOrientation, willRotateFromOrientation, .. are triggered when I actually move the device from the landscape view...
In short, when I am in the landscape view I do not know what to do to move back to the tabbar view... I am kind of stuck in the landscape view once I am in this one.
Thanks a lot for your help,
Luc
Look at the pie chart in CPTestApp-iPhone in the examples folder. It handles rotation by implementing -(void)didRotateFromInterfaceOrientation: and resizing the graph after a rotation.
Well, I managed to get a solution for this problem.
In fact, while moving from portrait to landscape I removed the tabbarcontroller from window subview and add the landscapeviewcontroller instead.
It seems it was not the correct thing to do.
Instead, I add the landscapeViewController as subview of the tabbarcontroller and remove it when going from landscape to portrait.
I still have a problem however with the y position of the landscape view which seems to changes when I do several decive rotation in a row....
Regards,
Luc