Placing and Moving Views on Rotation (UIView) - iphone

What's the "correct" way of exactly placing and moving views when an app rotates? That is, how can I have fine-grained control of the position, size, and reflow of my views when the UI rotates from portrait to landscape orientation (or vice-versa)? I think my two options are:
Use two superviews (portrait and landscape). On rotation: toggle between them.
Use one superview. On rotation: change each subview's frame, bounds, and center properties.
If you have two views with distinct enough layouts and elements, then the first way might be good enough. If your two views are essentially the same thing sized for different orientations, the second way is probably a better way to do it using only one view.
I suspect the former could be done with IB and the latter should be done programmatically.

To have fine-grained control over the position and size of your subviews when the iPhone rotates, change the frame of the subviews in the UIViewController method willAnimateRotationToInterfaceOrientation:duration:. This method is called within an animation block, so all the changes to your subviews' frames that you make inside of it are animated.
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation)) {
// Portrait frames
self.subviewA.frame = CGRectMake(x, y, width, height);
self.subviewB.frame = CGRectMake(x, y, width, height);
self.subviewC.frame = CGRectMake(x, y, width, height);
} else {
// Landscape frames
self.subviewA.frame = CGRectMake(x, y, width, height);
self.subviewB.frame = CGRectMake(x, y, width, height);
self.subviewC.frame = CGRectMake(x, y, width, height);
}
}

Here's one idea. I worry that the animations will work funny, but try it and see.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toOrientation
duration:(NSTimeInterval)duration {
if (toOrientation == UIInterfaceOrientationLandscapeLeft ||
toOrientation == UIInterfaceOrientationLandscapeRight) {
[UIView beginAnimations:nil context:NULL]; {
[UIView setAnimationDuration:1.0];
}
[UIView setAnimationDelegate:self];
[viewA setFrame:CGRectMake(?,?,?,?)];
[viewB setFrame:CGRectMake(?,?,?,?)];
[viewC setFrame:CGRectMake(?,?,?,?)];
} [UIView commitAnimations];
} else if (toOrientation == UIInterfaceOrientationPortrait ||
toOrientation == UIInterfaceOrientationPortraitUpsideDown) {
[UIView beginAnimations:nil context:NULL]; {
[UIView setAnimationDuration:1.0];
}
[UIView setAnimationDelegate:self];
[viewA setFrame:CGRectMake(?,?,?,?)];
[viewB setFrame:CGRectMake(?,?,?,?)];
[viewC setFrame:CGRectMake(?,?,?,?)];
} [UIView commitAnimations];
}

I have an idea which is working properly for me where I am using a small view on a view where it rotates itself when orientation of device changes. Use one notifier then in the method change the orientation.
#define DegreesToRadians(x) (M_PI * x / 180.0)
.......
[[NSNotificationCenter defaultCenter] addObserver:showIndicator
selector:#selector(setProperOreintation)
name:UIDeviceOrientationDidChangeNotification
object:nil];
.......
-(void)setProperOrientation {
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
if (orientation == UIDeviceOrientationPortraitUpsideDown)
self.transform = CGAffineTransformRotate(CGAffineTransformIdentity, DegreesToRadians(180));
else if (orientation == UIDeviceOrientationLandscapeLeft)
self.transform = CGAffineTransformRotate(CGAffineTransformIdentity, DegreesToRadians(90));
else if (orientation == UIDeviceOrientationLandscapeRight)
self.transform = CGAffineTransformRotate(CGAffineTransformIdentity, DegreesToRadians(-90));
[UIView commitAnimations]; }
Here instead of rotate u can use translate or scale

There is a lot solutions for that. One of them is animations for controls as you read before. Another option is prepare 2 UIView one for portrait mode and another for landscape mode. And on switch orientation - load proper UIView. That's the way i used in my applications. Because sometimes differences between landscape and portrait is huge.
In simple cases is enough to set up correct autoresizingMask for subviews. Read about it. It works really nice.

Related

Programatic UIViews being rotated correctly

I have been doing pretty much all of my User Interface programmatically with slight alterations being performed in Interface Builder.. but 99% of all the UI is exclusively done in code, because I feel there is a certain amount of flexibility gained by doing it this way.
However I am now having issues dealing with the rotation of the device, as I have several UIViews being added as subviews I am faced with a rotational problem as this is how I declare the views generally
htmlTest.webViewTest.frame = CGRectMake(4.0, 4.0, 312.0, 363.0);
and because of this fixed CGRectMake when the device is rotated the view stays the same size and dosent fit the orientation of the view properly.
So I have worked on a solution which is in my opinion horrible.. There are a couple of views that I animate in and users can select options from them then I animate them out.. but they need to be able to handle loading in either portrait or landscape and then if while they are loaded they need to be able to handle a rotation from either orientation to the other.
This is how I have done one of the views.
#pragma createAwesomeJumpBar
- (void)jumpBarButtonPosition:(int)changeView
{
// ChangeView is used to check if the this method is being called from a device rotation or from a button press (0, being rotation and 1, being tabbarButton touch
// if tabbar selected
if (changeView == 1) {
if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortrait)
{
if (![jumpBarContainerPortrait superview]) {
// load portrait view
jumpBarContainerPortrait = [[UIView alloc] initWithFrame:CGRectMake(0.0, 480.0, 320, (jumpBarHeightPortrait + 49.0))];
jumpBarContainerPortrait.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];
// add jumpbar container to view
[self.view insertSubview:jumpBarContainerPortrait belowSubview:actionTabBar];
[UIView animateWithDuration:0.6
delay:0.0f
options:UIViewAnimationCurveEaseIn
animations:^{
jumpBarContainerPortrait.frame = CGRectMake(0.0, (367 - jumpBarHeightPortrait), 320.0, (jumpBarHeightPortrait + 49.0)); // display jumpBar
} completion:^(BOOL finished) {
if (finished) {
NSLog(#"YAY!");
}
}];
}
else if ([jumpBarContainerPortrait superview]) {
//unload portrait view
[UIView animateWithDuration:0.6
delay:0.0f
options:UIViewAnimationCurveEaseIn
animations:^{
jumpBarContainerPortrait.frame = CGRectMake(0.0, 480.0, 320.0, (jumpBarHeightPortrait + 49.0)); // display jumpBar
// remove selected tabButton highlight
[actionTabBar setSelectedItem:nil];
} completion:^(BOOL finished) {
if (finished) {
// remove subView for superView
[jumpBarContainerPortrait removeFromSuperview];
}
}];
}
}
else if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight)
{
if (![jumpBarContainerLandscape superview]) {
// load landscape view
jumpBarContainerLandscape = [[UIView alloc] initWithFrame:CGRectMake(0.0, 320, 480.0, (jumpBarHeightLandscape + 49.0))];
jumpBarContainerLandscape.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];
// add jumpbar container to view
[self.view insertSubview:jumpBarContainerLandscape belowSubview:actionTabBar];
[UIView animateWithDuration:0.6
delay:0.0f
options:UIViewAnimationCurveEaseIn
animations:^{
jumpBarContainerLandscape.frame = CGRectMake(0.0, (207 - jumpBarHeightLandscape), 480.0, (jumpBarHeightLandscape + 49.0)); // display jumpBar
} completion:^(BOOL finished) {
if (finished) {
NSLog(#"YAY!");
}
}];
}
else if ([jumpBarContainerLandscape superview]) {
// remove landscape view
[UIView animateWithDuration:0.6
delay:0.0f
options:UIViewAnimationCurveEaseIn
animations:^{
jumpBarContainerLandscape.frame = CGRectMake(0.0, 320, 480.0, (jumpBarHeightLandscape + 49.0)); // display jumpBar
[actionTabBar setSelectedItem:nil];
} completion:^(BOOL finished) {
if (finished) {
// remove subView for superView
[jumpBarContainerLandscape removeFromSuperview];
}
}];
}
}
}
// if device rotated selected
else if (changeView == 0) {
if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortrait)
{
if([jumpBarContainerLandscape superview])
{
// Device is changing from landscape to protrait change views to fit
// load landscape view
jumpBarContainerPortrait = [[UIView alloc] initWithFrame:CGRectMake(0.0, (367 - jumpBarHeightPortrait), 320.0, (jumpBarHeightPortrait + 49.0))];
jumpBarContainerPortrait.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];
jumpBarContainerPortrait.alpha = 1.0;
// add jumpbar container to view
[UIView transitionFromView:jumpBarContainerLandscape
toView:jumpBarContainerPortrait
duration:animationSpeed
options:UIViewAnimationOptionTransitionCrossDissolve
completion:NULL];
[self.view insertSubview:jumpBarContainerPortrait belowSubview:actionTabBar];
}
}
else if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight)
{
if ([jumpBarContainerPortrait superview])
{
// Device is changing from portrait to landscape change views to fit
// load landscape view
jumpBarContainerLandscape = [[UIView alloc] initWithFrame:CGRectMake(0.0, (207 - jumpBarHeightLandscape), 480.0, (jumpBarHeightLandscape + 49.0))];
jumpBarContainerLandscape.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];
jumpBarContainerLandscape.alpha = 1.0;
// add jumpbar container to view
[UIView transitionFromView:jumpBarContainerPortrait
toView:jumpBarContainerLandscape
duration:animationSpeed
options:UIViewAnimationOptionTransitionCrossDissolve
completion:NULL];
[self.view insertSubview:jumpBarContainerLandscape belowSubview:actionTabBar];
}
}
}
}
in this example, I have two views landscape and portrait, obviously as the names go each are for their respective orientations.. the logic above goes along the lines of this
if tabbarselected
if !view visible
if device orientation portrait
animate in portrait view.
if device orientation landscape
animate in landscape view
if view visible
if device orientation portrait
animate out portrait view
clear tabbar
if device orientation landscape
animate out landscape view
clear tabbar
if !tabbarselected //meaning listener has identified orientation of device has changed
if device orientation portrait
unload portrait
load landscape
if device orientation landscape
unload landscape
load portrait
I would like to know if there is an easier way than going through all of this hassle! I am still fairly inexperienced so this was my best attempt.. I am hoping someone out there knows of an easier approach than having to do all of this leg work to get views being added to other views as subviews adjusting for orientation properly
any help would be greatly appreciated! I'm desperate lol :)
See the autoresizingMask documentation. Gives you all the same springs and struts control that you have in Interface Builder. E.g.:
CGRect frame = CGRectMake(margin, margin, self.view.frame.size.width - margin * 2, self.view.frame.size.height - margin * 2);
UIView *mySubview = [[UIView alloc] initWithFrame:frame];
[self.view mySubview];
mySubview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
Also, if you decide that autoresizingMask is not enough (for example, when you're moving objects with respect to each other to really fine tune the portrait versus landscape orientation), I'd suggest you do this layout process in viewWillLayoutSubviews for iOS5, (or willAnimateRotationToInterfaceOrientation in iOS4 or earlier). This way you don't need to animate the change yourself and the animation will be done in conjunction with the rest of the screen rotation animation.

Orientation problem

i want that my textfield should move up in any orientation.
But I am having a problem in landscape right and portrait upside down.
As when i orient it to any one of them my textfield down the view does not go up but the textfield which is on top is going up.
suggest me some solution.
below is the code that i am using for orientation.
please suggest me whole procedure if you can as i am a niwBie to iPhone development.
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
CGRect textFieldRect=[self.view.window convertRect:textField.bounds fromView:textField];
CGRect viewRect=[self.view.window convertRect:self.view.bounds fromView:self.view];
CGFloat midLine=textFieldRect.origin.y+0.5*textFieldRect.size.height;
CGFloat numerator=midLine-viewRect.origin.y-MINIMUM_SCROLL_FRACTION*viewRect.size.height;
CGFloat denominator=(MAXIMUM_SCROLL_FRACTION-MINIMUM_SCROLL_FRACTION)*viewRect.size.height;
CGFloat heightFraction=numerator/denominator;
if(heightFraction < 0.0)
{
heightFraction=0.0;
}
else if(heightFraction > 1.0)
{
heightFraction=1.0;
}
CGRect viewFrame;
UIInterfaceOrientation orientation=[[UIApplication sharedApplication]statusBarOrientation];
//code for text field editing in landscape an portrait mode
if(orientation==UIInterfaceOrientationPortrait||orientation==UIInterfaceOrientationPortraitUpsideDown)
{
animatedDistance=floor(PORTRAIT_KEYBOARD_HEIGHT*heightFraction);
}
else
{
animatedDistance=floor(LANDSCAPE_KEYBOARD_HEIGHT*heightFraction);
}
if (orientation==UIInterfaceOrientationPortrait) {
viewFrame=self.view.frame;
viewFrame.origin.y-=animatedDistance;
}
else if(orientation==UIInterfaceOrientationPortraitUpsideDown){
viewFrame=self.view.frame;
viewFrame.origin.y+=animatedDistance;
}
else if(orientation == UIInterfaceOrientationLandscapeRight){
viewFrame=self.view.frame;
viewFrame.origin.x+=animatedDistance;
}
else if(orientation == UIInterfaceOrientationLandscapeLeft){
viewFrame=self.view.frame;
viewFrame.origin.x-=animatedDistance;
}
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION];
[self.view setFrame:viewFrame];
[UIView commitAnimations];
}
In your ViewController override the - willRotateToInterfaceRotation:duration method. This message is called when the user rotates the device. In this method you can then set all your views to be placed in their proper positions. See Responding to View Rotation Events in the reference: UIViewController reference
you could do something like this:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if(toInterfaceOrientation==UIInterfaceOrientationPortraitUpsideDown){
yourView.frame = CGRectMake(x,y,w,h); //and set the proper coordinate for this view in this orientation
}
and then do it for all your views that are not placed correctly when rotating

How to allow only one view in navigation stack to rotate?

I have ViewControllers A and B on the navigation stack. A does not support landscape orientation, B does. If the user rotates to landscape while viewing B and then taps the Back button, A is now in landscape. How do I prevent this? Is there a good reason the shouldAutorotateToInterfaceOrientation method of A is not respected?
This is really very annoying thing about view controllers. And It seems to be no fix for autorotation. Maybe, the best would be return NO from B's shouldAutorotateToInterfaceOrientation and then perform view rotation manually. Then it won't affect A.
yes, i hate that too...
all i found to solve it was to do it by myself:
- (void)myAutomaticRotation{
if (A.view.frame.size.width > A.view.frame.size.height) {
[UIView beginAnimations:#"View Flip" context:nil];
[UIView setAnimationDuration: 0.5f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(M_PI/2);
self.view.bounds = CGRectMake(0.0f, 0.0f, 320.0f, 480.0f);
A.view.frame = CGRectMake(0,0,320, 480);
[UIView commitAnimations];
}
}
you can call myAutomaticRotation in a main/super UIViewController when you navigate to A.view,
and in that same place you should use:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
}
where you can check the view used (A,B) and allowing landscape mode just for B...
luca

How to create a collapsible UIView

I need to create a expandable and collapsible UIView of some sort and am not able to pay for a third party control.
This is basically how I would like it to behave:
At the top there should be a UIButton (or similar) that allows the user to toggle between expanded and collapsed.
When expanded I want to be able to place other UIView (e.g. calendar)and when collapsed the surrounding controls should move up.
Does anyone have any ideas how to implement this simply - noob here :(
Subclass a ViewController and have two methods that your button can fire like 'collapse' and 'expand', this might get you started: You can assign new selectors to UIButtons dynamically:
[button addTarget:self action:#selector(eventMethod:)
forControlEvents:UIControlEventTouchUpInside];
Code:
#define COLAPSED_HEIGHT 30
-(void)expand
{
CGRect s= [self getScrerenBoundsForCurrentOriantation];
CGRect f = self.view.frame;
f.origin.y =s.size.height-self.view.frame.size.height;
[UIView beginAnimations:#"expand" context:NULL];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationFinished:finished:context:)];
self.view.frame=f;
//CHANGE YOUR EXPAND/COLLAPSE BUTTON HERE
[UIView commitAnimations];
self.thumbScrollerIsExpanded=YES;
}
-(void)collapseView
{
//re-factored so that this method can be called in ViewdidLoad
CGRect s= [self getScrerenBoundsForCurrentOriantation];
CGRect f = self.view.frame;
f.origin.y =(s.size.height-COLAPSED_HEIGHT);
self.view.frame = f;
self.thumbScrollerIsExpanded=NO; //thumbScrollerIsExpanded is a BOOL property
}
- (void)collapse
{
[UIView beginAnimations:#"collapse" context:NULL];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationFinished:finished:context:)];
[self collapseView];
//CHANGE YOUR EXPAND/COLLAPSE BUTTON HERE
[UIView commitAnimations];
}
-(CGRect)getScrerenBoundsForCurrentOriantation
{
return [self getScrerenBoundsForOriantation:[[UIDevice currentDevice] orientation]];
}
-(CGRect)getScrerenBoundsForOriantation:(UIInterfaceOrientation)_orientation
{
UIScreen *screen = [UIScreen mainScreen];
CGRect fullScreenRect = screen.bounds; // always implicitly in Portrait orientation.
if (UIInterfaceOrientationIsLandscape(_orientation))
{
CGRect temp;
temp.size.width = fullScreenRect.size.height;
temp.size.height = fullScreenRect.size.width;
fullScreenRect = temp;
}
return fullScreenRect;
}
- (void)animationFinished:(NSString *)animationID finished:(BOOL)finished context:(void *)context
{
if ([animationID isEqualToString:#"expand"])
{
//example
}
else if ([animationID isEqualToString:#"collapse"])
{
//example
}
}
You could try adding a UIView (either in xib or programmatically) off screen, then use a UIView animation to slide it into your main viewing area.
The button would have an IBAction which would toggle the animation (slide in/slide out). You can slide it off screen then set UIView's hidden property to true. When you recall the IBAction, have it check if the view's hidden property is true. If it is, you know to play the animation of it sliding in, if not, then you play the animation of sliding out.

How to autorotate from portrait to landscape mode?

How can I autorotate an image from portrait to landscape mode on the IPhone?
You have to implement shouldAutorotateToInterfaceOrientation method in your controller, like this
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
Apply proper transformation to the view and adjust its frame bounds
In my app I've done it this way (very likely not the best one):
[UIView beginAnimations:#"View Flip" context:nil];
[UIView setAnimationDuration: 0.5f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(-M_PI/2);
self.view.bounds = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
self.view.center = CGPointMake(160.0f, 240.0f);
[UIView commitAnimations];
If you want to display a new and different view, the simplest and cleanest solution is to push a new view controller (presentModalViewController) that only supports landscape mode (in shouldAutorotateToInterfaceOrientation:).