UIToolbar Rotation/Animation - ios

I have a UIToolbar for an iPad app that is meant to stay at the top of the screen for all orientations. The UIView that my UIToolbar is part of does not rotate, however. The UIToolbar DOES rotate, but I'm having trouble getting the rotations to work the way I want...
I can't figure out how to change the size of the toolbar without stretching the contents of the toolbar (setting a scale transformation) or without messing up the transformations (setting the frame or the bounds).
- (void)updateOrientation:(UIInterfaceOrientation)orientation animated:(BOOL)animated
{
CGAffineTransform toolbarTransform;
switch(orientation) {
case UIDeviceOrientationPortrait:
toolbarTransform = CGAffineTransformIdentity;
break;
case UIDeviceOrientationLandscapeRight:
toolbarTransform = CGAffineTransformMakeTranslation(-768 / 2 + 44 / 2, 768 / 2 - 44 / 2 + 1024 - 768);
toolbarTransform = CGAffineTransformRotate(toolbarTransform, degreesToRadian(-90));
break;
case UIDeviceOrientationLandscapeLeft:
toolbarTransform = CGAffineTransformMakeTranslation(768 / 2 - 44 / 2, 768 / 2 - 44 / 2);
toolbarTransform = CGAffineTransformRotate(toolbarTransform, degreesToRadian(90));
break;
case UIDeviceOrientationPortraitUpsideDown:
toolbarTransform = CGAffineTransformMakeTranslation(0, 1024 - 44);
toolbarTransform = CGAffineTransformRotate(toolbarTransform, degreesToRadian(180));
break;
default:
toolbarTransform = CGAffineTransformIdentity;
break;
}
if(animated)
{
[UIView animateWithDuration:.5 animations:^
{
self.toolbar.transform = toolbarTransform;
}];
}
else
{
self.toolbar.transform = toolbarTransform;
}
}
This code mostly positions the toolbar how I want, but what should I do to resize the toolbar without messing up the transformations or stretching the toolbar's contents? Or maybe I'm approaching this wrong altogether?
edit: Slightly updated my code. Positioning is correct, just need to resize them somehow...

The solution to this issue was to create a special UIView (called rotationView) and performing all rotation/translations to that UIView instead of directly to the UIToolbar. Then, I can change the toolbar.frame safely without "messing up" the transformations.

Related

Using CGAfflineTransformMakeScale/Rotation only does one action

I am trying to make a video rotate and scale larger when the user rotates the screen to landscape.
- (void) orientationChanged:(NSNotification *)note
{
bool switchedLeft;
UIDevice * device = note.object;
switch(device.orientation)
{
case UIDeviceOrientationPortrait:
self.videoView.transform=CGAffineTransformMakeScale(0.5,0.5);
if (switchedLeft) {
self.videoView.transform=CGAffineTransformMakeRotation(-M_PI_2);
}else{
self.videoView.transform=CGAffineTransformMakeRotation(M_PI_2);
}
break;
case UIDeviceOrientationLandscapeLeft:
self.videoView.transform=CGAffineTransformMakeRotation(M_PI_2);
self.videoView.transform=CGAffineTransformMakeScale(2.0, 2.0);
switchedLeft=true;
break;
case UIDeviceOrientationLandscapeRight:
self.videoView.transform=CGAffineTransformMakeRotation(-M_PI_2);
self.videoView.transform=CGAffineTransformMakeScale(2.0, 2.0);
switchedLeft=false;
break;
default:
break;
};
}
There are a number of problems. First when I initially rotate to landscape it only does one transformation, in this configuration it just scales it.
The second problem is when I rotate to portrait it calls for the rotation but it never rotates. However i can go back and forth between landscape left and landscape right and it rotates properly. Any help would be greatly appreciated
You are essentially replacing the rotation transform with scale transform. In order to apply both, you need to use CGAffineTransformConcat().
CGAffineTransform rotate = CGAffineTransformMakeRotation(M_PI_2);
CGAffineTransform scale = CGAffineTransformMakeScale(2.0, 2.0);
self.videoView.transform = CGAffineTransformConcat(rotate, scale);
As for the second part, you don't need to apply another rotation, instead set it to default using CGAffineTransformIdentity.
case UIDeviceOrientationPortrait:
CGAffineTransform scale = CGAffineTransformMakeScale(0.5,0.5);
self.videoView.transform = CGAffineTransformConcat(CGAffineTransformIdentity, scale);
break;
try this
CGAffineTransform transform = CGAffineTransformRotate(self. videoView.transform, M_PI);
self. videoView.transform = transform;

How to maintain the transformations made to a UIview

I have a view, inside that view I have an UIImageview but inside this UIImageview I have 3 more UIImageview, this 3 UIImageview I can drag them and resize them, the problem is that when i resize one of them all of them return to the original position, why is this happening ?
UPDATE:
I use this for dragging the image
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:_photo];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:_photo];}
This for resizing, using an slider
- (IBAction)sizePhoto:(UISlider *)sender {
switch (cambiaSize) {
case 1:
[_imagen1 setTransform:CGAffineTransformMakeScale(sender.value, sender.value)];
break;
case 2:
[_imagen2 setTransform:CGAffineTransformMakeScale(sender.value, sender.value)];
break;
case 3:
[_imagen3 setTransform:CGAffineTransformMakeScale(sender.value, sender.value)];
break;}}
I have also used this for resizing
_imagen1.transform = CGAffineTransformScale(self.view.transform,sender.value, sender.value);
and I also flip horizontally the image using this
_imagen2.transform = CGAffineTransformScale(_imagen2.transform,1.0, 1.0);
I would like to keep the transformation while doing any of this actions...
This could be a consequence of using auto layout. If you directly change the frame of an object, it will revert to the frame defined by the constraints when some other action causes the view to redraw. You either need to turn off auto layout, or change the position of your views by modifying their constraints.
Use CGAffineTransformScale instead of CGAffineTransformMakeScale. 'MakeScale' creates a whole new identity transform with applied scale whereas the former method simply applies a scale to the pre-existing transform.
UIImageView *imageView = [[UIImageView alloc] init];
CGFloat exampleValueA = 90.0f, exampleValueB = 90.0f;
imageView.transform = CGAffineTransformScale(imageView.transform, exampleValueA, exampleValueB);

CGAffineTransformMakeRotation UIView corruption

I'm a bit stuck with a problem that i've got when rotating a UIView.
I have a method called 'updatePositions' that sets the view's current frame to either the left or the right of the superview, and then calls a rotation function to rotate the entire UIView. However when I call this, the frame is set fine, but the rotation corrupts the view completely.
In my layout subviews, I create a label and bezier path, each of which are only created if the label doesn't exist and are laid out using the bounds property of the view.
Am I calling CGAffineTransformMakeRotation in the wrong place?
- (void)updatePosition
{
// If the join overview orientates to north set position
if (self.joinOverview.doesOrientateToNorth)
// If the approach track is not south west place the north indicator in the lower left of the diagram
// if approach track is south west place north indicator in lower right
if (self.joinOverview.dataItem.approachTrack != VDApproachTrackSouthWest)
[self setFrame:CGRectMake(-self.superview.frame.origin.x, self.superview.frame.size.height - VIEW_SIZE, VIEW_SIZE, VIEW_SIZE)];
else
[self setFrame:CGRectMake(self.superview.frame.size.width - VIEW_SIZE, self.superview.frame.size.height - VIEW_SIZE, VIEW_SIZE, VIEW_SIZE)];
}
else // If the join overview orientates to the approach track set rotation and position
{
// Set the position to the lower left of the view
[self setFrame:CGRectMake(-self.superview.frame.origin.x, self.superview.frame.size.height - VIEW_SIZE, VIEW_SIZE, VIEW_SIZE)];
// Rotate
self.transform = CGAffineTransformMakeRotation(DegreesToRadians(50));
}
}
- (void)layoutSubviews
{
// Super
[super layoutSubviews];
// set background colour to transparent
self.backgroundColor = [UIColor clearColor];
if (!self.northSymbol) {
// Add 'N' symbol label
self.northSymbol = [[UILabel alloc]initWithFrame:CGRectMake(0, self.bounds.size.height / 2, self.bounds.size.width, self.bounds.size.height / 2)];
[self.northSymbol setText:#"N"];
[self.northSymbol setTextAlignment:NSTextAlignmentCenter];
[self.northSymbol setFont:[UIFont boldSystemFontOfSize:18]];
[self addSubview:self.northSymbol];
self.arrow = [UIBezierPath bezierPathWithThreePointArrowFromPoint:CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2)
toPoint:CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 20)
headWidth:self.bounds.size.width / 2.5];
}
}
Either you're using auto layout which doesn't like transforms, or you're calling updatePosition more than once, and thus you're changing frame of already transformed view, which will cause you a lot of pain. Add
self.transform = CGAffineTransformIdentity;
as the first line of updatePosition.

Aligning arrow with content view with custom iPad popover

I am customizing the appearance of my popovers by subclassing UIPopoverBackgroundView. Everything is working fine, except there are some instances where there is a gap between the arrow and the content view. If I keep rotating the device to dismiss the popover then bring it back up, sometimes they line up and others they do not. I'm logging the frame sizes and they are consistently the same, so I have no idea what's going on. I'm attaching screenshots and my layoutSubviews code below.
Where you see me subtracting 4 in the case of arrow direction right this was based on trial & error of getting it to work at least some of the time. I hate coding like this because I'm not sure what this space of 4 points is and why I don't need it in all cases of arrow direction, for example.
In the images the 1st shot shows the problem. This happened on a random triggering of the popover. The next image shows it randomly working when triggered later within the same execution of the app.
Also worth mentioning, I found some sample code at http://blog.teamtreehouse.com/customizing-the-design-of-uipopovercontroller and tried this version of layoutSubviews, even though it's already very similar to what I have. Even with this code I was still seeing the same issue.
EDIT: Worth mentioning it might be obvious from my screenshots but I am not including a border for my popover:
+ (UIEdgeInsets)contentViewInsets
{
return UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f);
}
Possibly also worth mentioning I am not using an image file for my arrow image but rather am drawing it custom. Again, even when I comment out the drawing I'm still having the issue, so I don't think it's related, but wanted to mention it for full disclosure.
Any help greatly appreciated. Thanks.
CGSize arrowSize = CGSizeMake(kCMAPopoverBackgroundViewArrowBase, kCMAPopoverBackgroundViewArrowHeight);
// Leaving code out for drawArrowImage - even when I leave the arrowImageView as
// an empty image view and set a non-clear background color this gap is still
// sporadically showing up
self.arrowImageView.image = [self drawArrowImage:arrowSize];
float arrowXCoordinate = 0.0f;
float arrowYCoordinate = 0.0f;
CGAffineTransform arrowRotation = CGAffineTransformIdentity;
float borderMargin = 2.0f;
switch (self.arrowDirection) {
case UIPopoverArrowDirectionUp:
arrowXCoordinate = ((self.bounds.size.width / 2.0f) - (arrowSize.width / 2.0f));
arrowYCoordinate = 0.0f;
break;
case UIPopoverArrowDirectionDown:
arrowXCoordinate = ((self.bounds.size.width / 2.0f) - (arrowSize.width / 2.0f));
arrowYCoordinate = self.bounds.size.height - arrowSize.height;
arrowRotation = CGAffineTransformMakeRotation(M_PI);
break;
case UIPopoverArrowDirectionLeft:
arrowXCoordinate = 0.0f - (arrowSize.height / 2.0f) + borderMargin;
arrowYCoordinate = ((self.bounds.size.height / 2.0f) - (arrowSize.height / 2.0f));
arrowRotation = CGAffineTransformMakeRotation(-M_PI_2);
break;
case UIPopoverArrowDirectionRight:
arrowXCoordinate = self.bounds.size.width - arrowSize.height - 4.0f;
arrowYCoordinate = ((self.bounds.size.height / 2.0f) - (arrowSize.height / 2.0f));
arrowRotation = CGAffineTransformMakeRotation(M_PI_2);
break;
default:
break;
}
self.arrowImageView.frame = CGRectMake(arrowXCoordinate, arrowYCoordinate, arrowSize.width, arrowSize.height);
[self.arrowImageView setTransform:arrowRotation];
Is your arrow image square? If not, I'm guessing that the transform is affecting its frame slightly and this is generating the unexpected gap.
Possibly you are getting subpixel values in your calculations (most likely 0.5)
Try using floorf or ceilf to make sure your values are landing exactly on pixel integral values.
For example...
arrowXCoordinate = floorf(((self.bounds.size.width / 2.0f) - (arrowSize.width / 2.0f)));
See floor reference.

How to track successive CGAffineTransforms so as to arrive at a specific point on screen?

How do you execute multiple CGAffineTransform operations (in animation blocks) without keeping track of every operation executed?
The translation operation doesn't take x,y coordinates but instead values to shift by. So unless you know where you are currently translated to, say at "location 2," how do you know what values to shift by to get to "location 3?"
For example:
A UIView's frame is at (0, 0) - Position 1. I set the transform to translate to (768, 0) and rotate -90 degrees - Position 2. Some time passes and now I want to move to (768, 1024) and rotate another -90 degrees - Position 3.
How do I know what to translate by to move from Position 2 to Position 3?
In context, I'm trying to achieve an iPad view like the following:
a UIView that takes up the entire screen
a UIToolbar that takes up the top edge and is on top of the UIView
when the iPad rotates, the UIView stays with the device, but the toolbar will rotate so that it is always on the top edge of the screen.
I am using CGAffineTransform translate and rotate to move the toolbar. Works great, except when I rotate the iPad multiple times. The first translate/rotate will work perfect. The following transforms will be off because I don't know the correct values to shift by.
UPDATE:
It looks like if I take the current translation (tx, ty) values in the UIView.transform struct and use the difference between them and the new location, it works. However, if the view has been rotated, this does not work. The tx and ty values can be flipped because of the previous rotation. I'm not sure how to handle that.
UPDATE 2:
Doing some research, I've found that I can get the original, unrotated points from tx, ty by getting the abs value of the points and possibly swapping x and y if the UIView is perpendicular. Now I am stuck figuring out how to correctly apply the next set of transforms in the right order. It seems no matter how I concat them, the UIView ends up in the wrong place. It just seems like this is all too complicated and there must be an easier way.
The answer is, apparently, you don't track the transforms.
So the way to rotate the toolbar around the screen is by not concatenating a rotate and translate transform. Instead, create a rotate transform and set the frame in the animation block. Further, based on the new UIInterfaceOrientation, set the degrees to rotate based on the compass values of 0, -90, -180, -270. Also, set the frame size base on the same locations.
So:
CGPoint portrait = CGPointMake(0, 0);
CGPoint landscapeLeft = CGPointMake(768 - 44, 0);
CGPoint landscapeRight = CGPointMake(0, 0);
CGPoint upsideDown = CGPointMake(0, 1024 - 44);
CGSize portraitSize = CGSizeMake(768, 44);
CGSize landscapeLeftSize = CGSizeMake(44, 1024);
CGSize landscapeRightSize = CGSizeMake(44, 1024);
CGSize upsideDownSize = CGSizeMake(768, 44);
CGFloat rotation;
CGRect newLocation;
switch(orientation) {
case UIDeviceOrientationPortrait:
NSLog(#"Changing to Portrait");
newLocation.origin = portrait;
newLocation.size = portraitSize;
rotation = 0.0;
break;
case UIDeviceOrientationLandscapeRight:
NSLog(#"Changing to Landscape Right");
newLocation.origin = landscapeRight;
newLocation.size = landscapeRightSize;
rotation = -90.0;
break;
case UIDeviceOrientationLandscapeLeft:
NSLog(#"Changing to Landscape Left");
newLocation.origin = landscapeLeft;
newLocation.size = landscapeLeftSize;
rotation = -270.0;
break;
case UIDeviceOrientationPortraitUpsideDown:
NSLog(#"Changing to Upside Down");
newLocation.origin = upsideDown;
newLocation.size = upsideDownSize;
rotation = -180.0;
break;
default:
NSLog(#"Unknown orientation: %d", orientation);
newLocation.origin = portrait;
newLocation.size = portraitSize;
rotation = 0.0;
break;
}
CGRect frame = newLocation;
CGAffineTransform transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(rotation));
if(lastOrientation) {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.3];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationBeginsFromCurrentState:YES];
}
toolbar.transform = transform;
toolbar.frame = frame;
// Commit the changes
if(lastOrientation) {
[UIView commitAnimations];
}
lastOrientation = orientation;
This works beautifully. However, an unexpected problem is that UI elements that iOS shows on your behalf are not oriented correctly. I.e., modal windows and popovers all keep the same orientation as the underlying UIView. That problem renders this whole thing moot.

Resources