iOS/UIKit: Keep size during rotation - ios

I am trying to have a UIView centered at the middle of the screen that is 320x240 when the device is in the landscape orientation and 240x320 when in the portrait orientation.
For example:
If I let UIKit's layout system do it on its own, I get this when rotating landscape:
And if I try to pin the width and height, I get this:
Basically, is there any way to keep the dimensions of an object during a rotation (but still rotate the object)? Preferably using only Interface Builder, and/or a minimal amount of code if that is not possible.

I think JeffCompton is right: there is no way for you to change the size of your view given the orientation using just the XIB / Autolayouts / Autoresizing masks.
I think the simplest way to do that is:
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[UIView animateWithDuration:0.3 animations:^(void) {
if(UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
self.yourView.frame = CGRectMake([UIScreen mainScreen].frame.size.width-320/2,[UIScreen mainScreen].frame.size.height-240/2,320,240);
} else {
self.yourView.frame = CGRectMake([UIScreen mainScreen].frame.size.width-240/2,[UIScreen mainScreen].frame.size.height-320/2,240,320);
}
}];
}

Related

Animation of CGAffineTransform in iOS8 looks different than in iOS7

I'm trying to find a reason why animation of UIView transform property looks different in iOS 8 than iOS 6/7.
For a simple example, prior to iOS 8:
myView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, 1.57);
[UIView animateWithDuration:5 animations:^{
myView.transform = CGAffineTransformTranslate(plane.transform, 100, 0);
}];
gives expected result, "myView" is rotated 90 degrees and moves down, but in iOS8 when translation is animated it starts at a point that I couldn't find explanation for (which breaks the animation).
Does anyone know the explanation for it? Thanks in advance!
CGAffineTransformIdentity behaves differently on ios7 and ios8. This has to do with auto-layout and size classes. The solution is to remove constraints that conflict with the animation on ios7.
// solve the constraint-animation problem
if(NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1) {
// iOS7 remove constraints that conflict with animation
if (self.centerYAlignment != nil) {
self.view.removeConstraint(self.centerYAlignment) //is an IBOutlet
}
} else {
// iOS8 constraint animations are fine
}
I think the reason is just iOS8 bug, but I use CAAnimation instead, and it works as expected on iOS8.
I had problems with jerky rotation transform in iOS7 as well. Solved this by nesting my rotated view inside a container and centering the rotated view inside.
I'm also experiencing the same issue with scaling. I guess it could be the same with rotation. Could you try this?
myView.transform = CGAffineTransformConcat(myView.transform , CGAffineTransformMakeRotate(1.57));
[UIView animateWithDuration:5 animations:^{
myView.transform = CGAffineTransformTranslate(plane.transform, 100, 0);
}];
Maybe it's also necessary to use CGAffineTransformMakeTranslate and CGAffineTransformConcat that as well, I'm not sure.
The worst part about this is: You would have to do if/else on iOS versions, because this would look weird on iOS 7. I hope this is getting fixed by Apple before or with iOS 8 release.
I agree with Pbk that it has to do with size classes in io8. uiviewcontrollers need to be resized with uitraitcollections depending on the device orientation. Otherwise, you get a uiviewcontroller in portrait mode, while the phone is in landscape mode, when you try to rotate it. So the correct steps are to rotate AND override uitraitcollections
This isn't entirely related, but I was struggling with CGAffineTransformScale not working at all on iOS7 in a fairly complicated animation. It turns out my problem was iOS7 cannot calculate CGAffineTransformScale with CGAffineTransformRotate at the same time. In iOS7, the last animation call you make is the only one that gets animated, so only the rotation was occurring. This bug is fixed in iOS8.
My solution is to simplify my animation for iOS7, only turning on the fancy stuff in iOS8:
//Pre-animation setup:
CGFloat radians = (M_PI/180) * (-15); //Get a human-readable number in degrees
self.badgeImage.alpha = 0; //Start the image as invisible
self.badgeImage.transform = CGAffineTransformScale(self.badgeImage.transform, 1.5, 1.5); //Start the image as scaled bigger than normal
if(NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1) { //See below. We will not be rotating the image in iOS7
self.badgeImage.transform = CGAffineTransformRotate(self.badgeImage.transform, radians); //Rotate the image if iOS8
}
//Animation Pieces:
//Fade in
[UIView animateWithDuration: 0.5
delay:0
options:0
animations:^{
self.badgeImage.alpha = 1.0f; //Return image to opaque
}
completion:NULL];
//Scale with bounce
[UIView animateWithDuration: 1.1
delay:0
usingSpringWithDamping:0.3 //Not as good as Android's bounce interpolator, but I'll take it
initialSpringVelocity:-1.0f //A negative velocity here makes the animation appear more like gravity than spring
options:0
animations:^{
self.badgeImage.transform = CGAffineTransformScale(self.badgeImage.transform, 0.67, 0.67); //Return image to its original size. These arguments are relative to its current scale.
}
completion:NULL];
//Rotation
if(NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1) { //This second animation call negates the first one on iOS7, so remove it.
[UIView animateWithDuration: 0.9
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.badgeImage.transform = CGAffineTransformRotate(self.badgeImage.transform, (radians * -1)); //Rotate the image back to its original orientation if iOS8
}
completion:NULL];
}
Of course, you can still combine multiple effects in iOS7 if you use the confusingly-named CGAffineTransformMakeScale() function. For instance, in the pre-animation setup, you can set both a rotation AND a scale, then set call CGAffineTransformMakeScale(1,1) to reset the image to its original metrics (MakeScale's arguments are specific, not relative - even more confusing!). This isn't always preferable, such as my example above where "bouncing" the animation would also bounce the rotation.

why self.view.frame and self.view.bounds are different when the devices rotate?

I want to do some layout change when the devices rotate. So I implement - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration method to do the work. But I realize when this method is called the self.view.frame and self.view.bounds are different. The self.view.bounds.size is correct and the self.view.frame.size seems still not rotate.
For example, I created an empty singleView Project and implemented the method like follows:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
NSLog(#"willAnimateRotationToInterfaceOrientation");
NSLog(#"self.view.bounds.size width:%f height:%f ",self.view.bounds.size.width,self.view.bounds.size.height);
NSLog(#"self.view.frame.size width:%f height:%f",self.view.frame.size.width,self.view.frame.size.height);
}
when the device rotates from portrait to landscape. the output is as follows:
2013-06-11 11:57:46.959 viewDemo[95658:707] willAnimateRotationToInterfaceOrientation
2013-06-11 11:57:46.961 viewDemo[95658:707] self.view.bounds.size width:1024.000000 height:748.000000
2013-06-11 11:57:46.961 viewDemo[95658:707] self.view.frame.size width:748.000000 height:1024.000000
I wonder why these sizes are different? They shouldn't be always the same? And when to choose which one to use?
Any help will be appreciated.
Look at the answer by "tc.", which is accepted as of now. Copying few lines from that answer to here.
So, the "frame" is relative to the parent view, which in this case is the UIWindow. When you rotate device, the window doesn't rotate; the view controller's view does. From the window's perspective, everything is effectively in portrait mode. This is why even after device is rotated to landscape, self.view.frame will not change.
You should use self.view.bounds to perform any calculation as it gives you the correct values independent of your device orientation.
I wonder why these values are different? They shouldn't be always the same?
The bounds of an UIView is the rectangle, expressed as a location (x, y) and size (width, height) relative to its own coordinate system (0,0).
The frame of an UIView is the rectangle, expressed as a location (x, y) and size (width, height) relative to the superview it is contained within.
In the case of the bounds, the x and y coordinates are at 0,0 as these coordinates are relative to the view itself. However, the frame x and y coordinates are relative to the position of the view within the parent view
And when to choose which one to use?
Hopefully this helps clarify the circumstances where each property might get used.
UIView's frame, bounds, center, origin, when to use what?
please note that frame.size is not equal to bounds.size when the simulator is rotated
refer to this one UIView frame, bounds and center
Just use method did(The size is new) not will(The size is same as was in parent controller)
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
[halfView setFrame:CGRectMake(0, 0, self.view.bounds.size.width, 120)];
NSLog(#"willAnimateRotationToInterfaceOrientation");
NSLog(#"self.view.bounds.size width:%f height:%f ",self.view.bounds.size.width,self.view.bounds.size.height);
NSLog(#"self.view.frame.size width:%f height:%f",self.view.frame.size.width,self.view.frame.size.height);
}

How to do a custom layout of a UIView so it works with both iPhone 5 screen dimensions as well as regular screen dimensions

Here is the desired outcome. The blue area is the UIView of interest. The UIView is not a UIImageView.
I've tried all sorts of arrangements with auto-resizing masks to no avail
This can only be done programmatically. One option is what #user2223761 suggests with subclassing. If you don't want to subclass UIView, then you need to set the frames on orientation changes and set yourView.center to be the center of the center.
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
if (UIInterfaceOrientationIsLandscape(interfaceOrientation)) {
// Make sure that the frame is centered in the screen
NSInteger paddingLeftSide = (self.view.bounds.size.width - 480) / 2;
self.view.frame = CGRectMake(paddingLeftSide, 0, 480, 320);
} else {
self.view.frame = CGRectMake(0, 0, 320, 320);
}
}
Dealing with different screen sizes can be tricky. In your case it is not :)
since you want to center the view in the screen what ever size it is, all you need to do is set the center of the view to be the center of the screen.
CGRect screenBounds = [[UIScreen mainScreen] bounds];
view.center = CGPointMake(screenBounds.size.width/2,screenBounds.size.height/2);
This code assumes the view's superView's bounds is the same size as the screenBounds..
First: Subclass UIView (create a MYUIView).
Second: override the method
- (void)layoutSubviews {
[super layoutSubviews];
// .. put your code...
}
and perform the frame update manually inside that method by reading the screen size.
auto-resize mask must be set to UIViewAutoresizingNone.

rotating UIbutton, while rotating parentview they reside with in

I have one UIView that I rotate manually when orientation changes, this UIView hosts a number of buttons, I want to rotate them too based on the change in orientation.
I am doing this :
CGAffineTransform rotation = CGAffineTransformMakeRotation(M_PI / -4);
[UIView animateWithDuration:0.25 animations:^
{
self.parentView.firstButton.transform = CGAffineTransformConcat(self.parentView.firstButton.transform, rotation);
self.parentView.secondButton.transform = CGAffineTransformConcat(self.parentView.secondButton.transform, rotation);
self.parentView.thirdButton.transform = CGAffineTransformConcat(self.parentView.thirdButton.transform, rotation);
self.parentView.transform = CGAffineTransformConcat(self.parentView.transform, rotation);
}
completion:^(BOOL finished)
{
}];
parentView does rotate properly, but the other buttons do not !!
do I need to perform the rotation in another call? as in I cannot rotate a button and its parentview in the same block?
Thanks
It should be enough to rotate the parentView. Make sure that you assign self.parentView.transform first. Try directly assigning a proper transform with CGAffineTransformMakeRotation() and do not rely on a current state of the transform property (like using CGAffineTransformConcat()). Don't forget to make sure the subviews are really subviews of the parentView - a [self.parentView addSubview:...]should occur somewhere. If you use IB to set up subviews this can be tricky; check the view hierarchy tree of IB!

How to set a rotation transform that doesn't distort on device rotation?

I am developing an iPad application that has a set of UIButton instances where I do a slight CGAffineTransform rotation in viewWillAppear: so that they are not perfectly aligned on the screen. A problem occurs, though, when the device is rotated in any direction and the background image of the button becomes more and more skewed the more it is rotated:
Correct:
After a few device rotations:
Here is the code that I am using to animate the view on viewDidAppear::
/*
* Animate video button
*/
CGPoint videoCenter = self.videoButton.center;
self.videoButton.center = CGPointMake(self.videoButton.center.x + 25.0f, self.videoButton.center.y + 25.0f);
[UIView animateWithDuration:0.5f
delay:0.0f
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
self.videoButton.center = videoCenter;
self.videoButton.transform = CGAffineTransformRotate(CGAffineTransformIdentity, [self convertDegreesToRadians:4.0f]);
}
completion:^(BOOL finished) {
// Do Nothing
}];
I have tried resetting the transform to what I need it to be in willRotateToInterfaceOrientation:, and though it lessens the degree to which it happens, after a number of rotations it produces a similar result.
I have tried changing autoresizesSubviews of the main view to be NO:
self.view.autoresizesSubviews = NO;
But then I have to layout my landscape orientation completely by hand, which I want to avoid.
How do I set the rotation transform so that it does not distort the background images of my UIButton on device orientation?
UPDATE:
I added some log statements in viewWillRotateToInterfaceOrientation: and noticed that the button's width is growing. This explains the image distortion, but why is it even changing? That seems odd to me:
width height
-------------------------------
268.574524 149.81514
286.267822 150
304.092621 150
321.91745 150
339.742279 150
357.567078 150
374.401642 150
391.236206 150
409.061066 150
425.895599 150
442.730164 150
459.564728 150
476.399292 150
493.233826 150
509.078125 150
524.922424 150
540.766663 150
556.611023 150
The problem that you are experiencing may be due to autoresizing done by UIViewController upon interface rotation. My suggestion is that you keep track of the original frame of the image view, and restore that original frame in didRotateFromInterfaceOrientation:. So, in -viewDidLoad, do something like this to save the frame of the view:
videoFrame = self.videoButton.frame;
In the header for this view controller, you should also declare CGRect videoFrame in order for the code above to work. Now, we can use this saved frame when the interface orientation is finished changing:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orientation {
CGPoint oldCenter = [self.videoButton center];
[self.videoButton setTransform:CGAffineTransformIdentity];
[self.videoButton setFrame:videoFrame];
// restore previous center and rotation
[self.videoButton setCenter:oldCenter];
[self.videoButton setTransform:CGAffineTransformRotate(CGAffineTransformIdentity, [self convertDegreesToRadians:4.0f])];
}

Resources