UIViewController and Orientation am I heading in the right direction? - iphone

I have spent the last several hours trying to get this to work and I can't so I need to know if what I am trying to do is the correct thing. Its driving me mad!
My goal is to have a ViewController that detects orientation change. When it's portrait it shows a view with a UITableView, when its landscape it shows a UIView with content that I will programmatically create.
In my parent viewcontroller I have:
- (void)viewDidLoad
{
[super viewDidLoad];
TableDataViewController *tableDataController = [[TableDataViewController alloc]
initWithNibName:#"TableDataViewController"
bundle:nil];
self.tableDataViewController = tableDataController;
[self.view insertSubview: tableDataController.view atIndex:0];
[tableDataController release];
}
This loads my view containing the table view and the controller that goes with it. The user then rotates the device and the function:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toOrientation
duration:(NSTimeInterval)duration
{
if (toOrientation == UIInterfaceOrientationPortrait) {
NSLog(#"PerformAnalysisViewController: Gone to Potrait");
}
if ((toOrientation == UIInterfaceOrientationLandscapeLeft) ||
(toOrientation == UIInterfaceOrientationLandscapeRight)) {
NSLog(#"PerformAnalysisViewController: Gone to Landscape");
// Load new view here
CGRect frame = [[UIScreen mainScreen] applicationFrame];
UIView *horizView = [[[HorizView alloc]
initWithFrame:frame] autorelease];
[self setView:horizView];;
}
}
will trigger depending. However it doesn't trigger? Is this because control has passed to the Subview I have inserted in the viewDidLoad? If so how do I get it back? Do I have to get it to detect orientation and then remove itself from the superview?
If that was then working would the new view be added as I have it above? I have tried reading the Apple documentation but I can't make this work.
All help greatly appreciated.
Mike

I am using the willRotateToInterfaceOrientation method in order to handle UI rotation:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
if( interfaceOrientation == UIInterfaceOrientationPortrait ||
interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown ) {
self.backgroundImageView.image = [UIImage imageNamed:#"vertical.png"];
}
else {
self.backgroundImageView.image = [UIImage imageNamed:#"horizontal.png"];
}
}
Please check, that your Info.plist supports more than one orientation and that you have implemented shouldAutorotateToInterfaceOrientation:interfaceOrientation by returning YES.

Related

iOS7 / iOS6 Conditional Rotation Portrait / Landscape for different sections of App

Problem: A have an App that uses both Landscape mode (locked) and Portrait Mode (locked) for different parts of the app. Now I have a working solution however it doesn't seem correct and does have it's own problems.
Optimally I would love to force a orientation change. Thinking even about doing a view transformation if needed.
Basic flow of App:
HomeView (Portrait) (which has a few sub pushed views that are also portrait and locked to that).
LandscapeView (Landscape) (which has 5 pushed subviews that are also landscape)
Note:
HomeView has a link to LandscapeView
LandscapeView can go back to HomeView
At the end of the LandscapeView subviews it returns to the HomeView
Basic Image showing how this looks with the different view orientations. (The lines indicate flow of app, orientation of the images indicate how each screen should be )
Currently using the below implementation to call / set if the view is in portrait mode or landscape mode by [setLockedToPortait:YES] (for portrait view) etc.
This in term makes the query for what interface orientation to use from iOS if the device is rotated.
Now for the case of going to the LandscapeView, I show a temporary view over the top of the normal view asking to use to rotate their phone to landscape. (A temporary view is also shown when returning to the HomeView from a landscape view)
So once the user has rotated their device, it will trigger the correct orientation and then the temporary view will hide.
If the user then rotates their phone back to portrait at this point it will still be locked to landscape so will not trigger another view rotation (also no temp view will appear or anything)
Current Implementation Code::
// ---------------------- NavigationController (subclass of UINavigationController)
#interface NavigationController () {
BOOL isOrientationPortrait;
}
#end
#implementation NavigationController {
UIDeviceOrientation lastAccepted;
UIDeviceOrientation lastKnown;
}
-(void)setLockedToPortait:(BOOL)isLocked {
isOrientationPortrait = isLocked;
}
-(UIDeviceOrientation) getCurrentOrientation {
UIDeviceOrientation orientate = [[UIDevice currentDevice] orientation];
if(orientate == 0) { // needed for simulator
orientate = (UIDeviceOrientation)[UIApplication sharedApplication].statusBarOrientation;
}
return orientate;
}
// Deprecated in iOS6, still needed for iOS5 support.
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation
{
UIDeviceOrientation orientation = [self getCurrentOrientation];
[self setLastKnownOrientation:orientation];
if(isOrientationPortrait == YES) {
if([self isLastKnownPortrait] == YES) {
[self setLastAcceptedOrientation:orientation];
return YES;
} else {
return NO;
}
} else {
if([self isLastKnownLandscape] == YES) {
[self setLastAcceptedOrientation:orientation];
return YES;
} else {
return NO;
}
}
}
// iOS6/7 support
- (BOOL)shouldAutorotate
{
// find out the current device orientation
UIDeviceOrientation orientation = [self getCurrentOrientation];
[self setLastKnownOrientation:orientation];
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
if(isOrientationPortrait == YES) {
if([self isLastKnownPortrait] == YES)
{
UIDeviceOrientation orientation = [self getCurrentOrientation];
[self setLastAcceptedOrientation:orientation];
}
return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
} else {
if([self isLastKnownLandscape] == YES)
{
UIDeviceOrientation orientation = [self getCurrentOrientation];
[self setLastAcceptedOrientation:orientation];
}
return (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight );
}
}
-(void)setLastAcceptedOrientation:(UIDeviceOrientation)orient {
lastAccepted = orient;
}
-(void)setLastKnownOrientation:(UIDeviceOrientation)orient {
lastKnown = orient;
}
-(BOOL)isLastKnownPortrait {
return UIDeviceOrientationIsPortrait(lastKnown);
}
-(BOOL)isLastKnownLandscape {
return UIDeviceOrientationIsLandscape(lastKnown);
}
-(BOOL)isLastAcceptedPortrait {
return UIDeviceOrientationIsPortrait(lastAccepted);
}
-(BOOL)isLastAcceptedLandscape {
return UIDeviceOrientationIsLandscape(lastAccepted);
}
Current Problems:
Device rotations are always required after a view has loaded for the user going to Landscape mode from Portrait and vice versa.
If the user has the device orientation locked, this will not work at all.
When transitioning back from Landscape mode, and the user has already rotated their device to Portrait (in the last landscape view), the Portrait view's interface will be locked to a 'Landscape' layout until the user re-rotates their device (so currently I am just showing the overlay to rotate the device, but it is already rotated… very annoying for the user). Massive issue right now with the above implementation.
Would love to be able to:
Force an orientation change on the phone for the current view.
Set a preferred layout for a view which is forced between push/pops of views.
I've looked a lot at the other solutions on here and on the Apple Dev forums, however none seem to cover this problem, or still this orientation bug between the two views exists as well.
Thanks for any help or pointers! No advice will be discounted :D
--
Edit::
Solution Found thanks to #leo-natan!!
So instead of trying to force a change of orientation on the views. Just push a new modal view. This forces a change. You still need to above orientation code for managing rotations.
So what I have now in my HomeViewController:
LandscapeViewController * viewController = [[[LandscapeViewController ViewController alloc] init] autorelease];
UINib * nib = [UINib nibWithNibName:#"NavigationController" bundle:nil];
NavigationController *navController = [[nib instantiateWithOwner:nil options:nil] objectAtIndex:0];
[navController initWithRootViewController:viewController];
[self presentViewController:navController animated:YES completion:^{
// completion
}];
So it is necessary to re-add a new navigation controller for this modal view. Also note above 'presentViewController' is the new way of pushing Modal views.
Implemented this overloaded method for the managing of the view controller:
-(id)initWithRootViewController:(UIViewController *)rootViewController {
self = [super initWithRootViewController:rootViewController];
if(self){
}
return self;
}
Note: The above is not using storyboards. The problem may be solved by using storyboards and modally showing a view in the same fashion.
See my answer here, including a test project.
Basically, orientation can only be forced to change when presenting a view controller modally. For example, media playback in some apps. If you wish to transition from a view controller that can only be presented in portrait to a view controller that is only presented in landscape, you will need a modal presentation. Push will not work.

How to change the orientation of the app without changing the device orientation in iphone app

I want to change the orientation of the app without changing the device orientation in iphone app.
I want to change my view from portraid mode to landscap mode programmatically.
And also want to know that will this be accepted by the apple store or not ?
Thanks
Now I got the solution from other that is as follow
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeRight];
when you add this line at that time one warning appear and for remove this warning just add bellow code on you implementation file..
#interface UIDevice (MyPrivateNameThatAppleWouldNeverUseGoesHere)
- (void) setOrientation:(UIInterfaceOrientation)orientation;
#end
and after that in bellow method just write this code if required..
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
But now want to know is this accepted by apple app store or not ?
thanks
use this line for programmatically change orientation...
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeRight];
and also when you add this line at that time one warning appear and for remove this warning just add bellow code on you implementation file..
#interface UIDevice (MyPrivateNameThatAppleWouldNeverUseGoesHere)
- (void) setOrientation:(UIInterfaceOrientation)orientation;
#end
and after that in bellow method just write this code if required..
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
// return NO;
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
i hope this help you..
:)
Add a class variable
Bool isInLandsCapeOrientation;
in viewDidLoad
set this flag to
isInLandsCapeOrientation = false;
Add the following function
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if (!isInLandsCapeOrientation) {
return (UIInterfaceOrientationIsPortrait(interfaceOrientation));
}else {
return (UIInterfaceOrientationIsLandscape(interfaceOrientation));
}
}
To changing orientation from portrait to landscape, let it happens on a button action
- (IBAction)changeOrientationButtonPressed:(UIButton *)sender
{
isInLandsCapeOrientation = true;
UIViewController *viewController = [[UIViewController alloc] init];
[self presentModalViewController:viewController animated:NO];
[self dismissModalViewControllerAnimated:NO];
}
This works fine for me.
To change Orientation portraid mode to landscap mode use this code
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
use this code for programmatically change orientation...
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeRight];
If you want to change the particular view only in landscape..then u can try the following in its viewDidLoad
float angle = M_PI / 2;
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
[ [self.view] setTransform:transform];
The documentation describes the orientation property as being read-only, so if it works, I'm not sure you can rely on it working in the future (unless Apple does the smart thing and changes this; forcing orientation changes, regardless of how the user is currently holding their device, is such an obvious functional need).
As an alternative, the following code inserted in viewDidLoad will successfully (and somewhat curiously) force orientation (assuming you've already modified you shouldAutorotateToInterfaceOrientation ):
if (UIDeviceOrientationIsPortrait([[UIDevice currentDevice] orientation]))
{
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
UIView *view = [window.subviews objectAtIndex:0];
[view removeFromSuperview];
[window addSubview:view];
}
Clearly, this does it if the user is currently holding their device in portrait orientation (and thus presumably your shouldAutorotateToInterfaceOrientation is set up for landscape only and this routine will shift it to landscape if the user's holding their device in portrait mode). You'd simply swap the UIDeviceOrientationIsPortrait with UIDeviceOrientationIsLandscape if your shouldAutorotateToInterfaceOirentation is set up for portrait only.
For some reason, removing the view from the main window and then re-adding it forces it to query shouldAutorotateToInterfaceOrientation and set the orientation correctly. Given that this isn't an Apple approved approach, maybe one should refrain from using it, but it works for me. Your mileage may vary. But this also refers to other techniques, too. Check
SO discussion

How do i refresh a UIView when Orientation happens?

My code goes like this:
- (void)viewDidLoad
{
[self setGridView];
}
-(void)setGridView
{
CGRect frame;
frame .origin.x=0;
frame.origin.y=20;
frame.size.width=GRID_WEIGHT;
frame.size.height=GRID_HEIGHT;
GridView *ObjGridView=[[GridView alloc]initWithFrame:frame];
[[NSBundle mainBundle ] loadNibNamed:#"GridView" owner:ObjGridView options:nil];
[ObjGridView setGridViewFrame:frame];
[self.view addSubview:ObjGridView.GridCellView];
frame .origin.x+=GRID_WEIGHT;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
This code adds a subview to a view and sets the frame
My problem:
1-How do i refresh my view when Orientation(landscape or portrait) happens,
because i set the frame of subview in the lanscape mode and i wants to use the sane view in my portrait view also .(basically where do i call this -(void)setGridView delegate method)?
2-How do i know, my subview exceeding the bound of the view,so that i can handle the subview in my setGridView method ?
1.Below method will call automatically whenever your orientation changes. Do the necessary changes according to each orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if (interfaceOrientation == UIInterfaceOrientationPortrait) {}
else if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft) {}
else if (interfaceOrientation == UIInterfaceOrientationLandscapeRight) {}
else if (interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {}
return YES;
}
2.You should know the width and height of your views and set the frames accordingly. That is not a big deal.
Hope this is helpful.
I am learning the ins and outs of iOS Application development myself, so please forgive me for the brevity of my response.
I believe you may be able to find the answer to your issue within the section titled 'Responding to Orientation Changes' within this document on Apple's Developer Resources:
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/RespondingtoDeviceOrientationChanges/RespondingtoDeviceOrientationChanges.html
I hope this helps you deduce a resolution to your issue.
In viewDidLoad
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(OrientationChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
Method for notify you that Orientation Changed:-
-(void)OrientationChange:(NSNotification*)notification
{
UIDeviceOrientation Orientation=[[UIDevice currentDevice]orientation];
if(Orientation==UIDeviceOrientationLandscapeLeft || Orientation==UIDeviceOrientationLandscapeRight)
{
NSLog(#"Landscape");
}
else if(Orientation==UIDeviceOrientationPortrait)
{
NSLog(#"Portrait");
}
}
In response to your questionsL
With regard to resizing on orientation change:
If you set your springs and struts accordingly it should autoresize automatically, alternatively you can do this in code as per deamonsarea's answer
To check if a view is exceeding the bounds of the superview use CGRectContainsRect
something like.
CGRect frame0 = self.view.bounds;
CGRect frame1 = ObjGridView.frame;
if(CGRectContainsRect(frame0,frame1)==NO){
NSLog(#"exceeds bounds")
}
Also noticed you are not calling [super viewDidLoad] and this line
[[NSBundle mainBundle ] loadNibNamed:#"GridView" owner:ObjGridView options:nil];
loads a new instance of the view but you are not refering to it anywhere
I found this question while looking for a way to react to orientation change inside the UIView itself. In case anyone else comes along...
If you want to react to orientation change inside a UIView, rather than a UIViewController (for encapsulation or other reasons), you can use this method:
class MyView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
println("orientation or other bounds-impacting change")
}
}

No perfect way to detect device orientation on iPad?

EDIT: The given answer works on the device, but beware it fails on the simulator.
When my iPad starts up, I show a loading label, centered in the middle of the screen. I set its autoresizingMask so it recenters on orientation change.
As the app starts up, the label's text changes, so I want to recenter the label based on its new length. However, the following piece of code doesn't center the label correctly:
- (void) setLabelText:(NSString*)text {
CGSize maximumLabelSize = CGSizeMake(500,20);
CGSize expectedLabelSize = [text sizeWithFont:loadingLabel.font
constrainedToSize:maximumLabelSize
lineBreakMode:loadingLabel.lineBreakMode];
loadingLabel.frame = CGRectMake(self.view.frame.size.width/2-expectedLabelSize.width/2,
loadingLabel.frame.origin.y,
expectedLabelSize.width,
loadingLabel.frame.size.height);
loadingLabel.text = text;
}
I also considered checking [[UIDevice currentDevice]orientation], and if the iPad is in landscape mode, then I'd use self.view.frame.size.height to set the xOrigin of the label.
However, if the device is face up or face down, (and not landscape or portrait) then this method fails. I also have a lastOrientation variable in my appDelegate, which remembers if the app is in landscape or portrait, even when face up or face down, based on the device's last known orientation. However, at start-up, this variable isn't necessarily set.
Is there some simple solution I am missing here, so I can resize and center my label?
EDIT: I tried checking UIStatusBarOrientation based on the advice posted, but it doesn't work:
if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft
|| [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight) {
NSLog(#"landscape");
width = self.view.frame.size.height;
} else {
NSLog(#"portrait");
}
This always logs portrait, at least on start-up, on the simulator.
Check [[UIApplication sharedApplication] statusBarOrientation]
I found a trick to solve the FaceUp orientation issue!!!
Delay the orientation check till AFTER the app has started running, then set variables, view sizes, etc.!!!
//CODE
- (void)viewDidLoad {
[super viewDidLoad];
//DELAY
[NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(delayedCheck)
userInfo:nil
repeats:NO];
}
-(void)delayedCheck{
//DETERMINE ORIENTATION
if( [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait ){
FACING = #"PU";
}
if( [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortraitUpsideDown ){
FACING = #"PD";
}
if( [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft ){
FACING = #"LL";
}
if( [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeRight ){
FACING = #"LR";
}
//DETERMINE ORIENTATION
//START
[self setStuff];
//START
}
-(void)setStuff{
if( FACING == #"PU" ){
//logic for Portrait
}
else
if( FACING == #"PD" ){
//logic for PortraitUpsideDown
}
else{
if( FACING == #"LL"){
//logic for LandscapeLeft
}
else
if( FACING == #"LR" ){
//logic for LandscapeRight
}
}
//CODE
You can addSubviews, position elements, etc. in the 'setStuff' function ... anything that would initially depend on the orientation!!!
:D
-Chris Allinson

My app crashes after multiple rotates

My app is crashing when I try and rotate it more than a couple of times. I first thought it was just the iPhone Simulator, so I loaded the app onto an iPod touch, and it crashed after fewer rotates in a row. I suspect it's a memory leak in one of my rotate methods. The only place I can think that the crash is being caused is in willRotateToInterfaceOrientation:duration:. The only two methods related to rotate that I've added/extended are shouldAutorotateToInterfaceOrientation: and willRotateToInterfaceOrientation:duration and I don't think it's the first because it only contains the two words: return YES;. Here is my willRotateToInterfaceOrientation:duration: method so you can review it and see where the possible memory leak is.
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
{
UIFont *theFont;
if ((orientation == UIInterfaceOrientationLandscapeLeft) || (orientation == UIInterfaceOrientationLandscapeRight))
{
theFont = [yearByYear.font fontWithSize:16.0];
yearByYear.font = theFont;
[theview setContentSize:CGSizeMake(460.0f, 635.0f)];
}
else
{
theFont = [yearByYear.font fontWithSize:10.0];
yearByYear.font = theFont;
[theview setContentSize:CGSizeMake(300.0f, 460.0f)];
}
[theFont release];
}
yearByYear is a UITextView and theview is a UIScrollView.
You shouldn't be releasing theFont. You don't own the object.
You can also simplify what you're doing to:
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration {
if ((orientation == UIInterfaceOrientationLandscapeLeft) || (orientation == UIInterfaceOrientationLandscapeRight)) {
yearByYear.font = [yearByYear.font fontWithSize:16.0]
[theview setContentSize:CGSizeMake(460.0f, 635.0f)];
}
else
{
yearByYear.font = [yearByYear.font fontWithSize:10.0]
[theview setContentSize:CGSizeMake(300.0f, 460.0f)];
}
}
Getting rid of theFont completely.