How to change a anchor point of uibutton in [UIVIEW animate]? - objective-c

I'm trying to change a anchor point to (0,0) (it's not center.) of uibutton and rotate.
How to change a anchor point of uibutton?
mybutton.anchorPoint? = CGPointMake(0, 0); <= How to change?
[UIView animateWithDuration:5.0 animations:^{
mybutton.transform = CGAffineTransformMakeRotation(180 * M_PI / 180);
}];

Each CALayer has an anchorPoint property. You can do one of the following:
You can either animate the layer (the anchor is by default the center of the bounds)
You can combine a translation with a rotation transform (translate button center to origin, rotate and translate back)
EDIT:
Example: [button.layer setAffineTransform:CGAffineTransformMakeRotation(180 * M_PI / 180)]

I think you would set it by the following
[button.layer setAnchorPoint:CGPointMake(button.frame.origin.x, button.frame.origin.y)];
But I'm only just playing with it myself. So not positive if it will work yet.
A

Related

Obj-C: It changes center before rotating

It's my code:
[UIView animateWithDuration:0.4f
delay:0.1f
options:UIViewAnimationOptionCurveEaseOut
animations:^{
CGAffineTransform rotate = CGAffineTransformMakeRotation( 30.0 / 180.0 * 3.14 );
[needleBig setTransform:rotate];
}
completion:^(BOOL finished){
}];
And it's the pictures before and after rotating:
It seems it changes center before rotating, but I don't want it.
A rotation created with CGAffineTransformMakeRotation(angle) makes a rotation of the coordinate system around its origin. The docs say "angle = The angle, in radians, by which this matrix rotates the coordinate system axes."
From your pictures it looks as if the origin of your coordinate system is somewhere at the top left corner of the square enclosing your clock.
You can rotate about any other point if you set the view's layer's anchorPoint property accordingly.
This worked:
needleBig.layer.anchorPoint=CGPointMake(0.5f, 0.5f);
[UIView animateWithDuration:0.4f
delay:0.01f
options:UIViewAnimationOptionCurveEaseOut
animations:^{
CGAffineTransform rotate = CGAffineTransformMakeRotation(180.0f * (M_PI / 180.0f));
[needleBig setTransform:rotate];
}
completion:^(BOOL finished){
}];

Scale UIView with the top center as the anchor point?

I'm scaling a UIView with CGAffineTransformMakeScale but I want to keep it anchored to it's current top center point as it's scaled.
I've looked into setting view.layer.anchorPoint but I believe I need to use this in conjunction with setting view.layer.position to account for the change in the anchor point.
If anyone could point me in the right direction I'd be very grateful!
Take a look at my answer here to understand why your view is moving when you change the anchor point: https://stackoverflow.com/a/12208587/77567
So, start with your view's transform set to identity. When the view is untransformed, find the center of its top edge:
CGRect frame = view.frame;
CGPoint topCenter = CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame));
Then the anchor point to the middle of the top edge:
view.layer.anchorPoint = CGPointMake(0.5, 0);
Now the layer's position (or the view's center) is the position of the center of the layer's top edge. If you stop here, the layer will move so that the center of its top edge is where its true center was before changing the anchor point. So change the layer's position to the center of its top edge from a moment ago:
view.layer.position = topCenter;
Now the layer stays in the same place on screen, but its anchor point is the center of its top edge, so scales and rotations will leave that point fixed.
By using an extension
Scaling an UIView with a specific anchor point while avoiding misplacement:
extension UIView {
func applyTransform(withScale scale: CGFloat, anchorPoint: CGPoint) {
layer.anchorPoint = anchorPoint
let scale = scale != 0 ? scale : CGFloat.leastNonzeroMagnitude
let xPadding = 1/scale * (anchorPoint.x - 0.5)*bounds.width
let yPadding = 1/scale * (anchorPoint.y - 0.5)*bounds.height
transform = CGAffineTransform(scaleX: scale, y: scale).translatedBy(x: xPadding, y: yPadding)
}
}
So to shrink a view to half of its size with its top center as anchor point:
view.applyTransform(withScale: 0.5, anchorPoint: CGPoint(x: 0.5, y: 0))
It's an old question, but I faced the same problem and took me a lot of time to get to the desired behavior, expanding a little bit the answer from #Rob Mayoff (I found the solution thanks to his explanation).
I had to show a floating-view scaling from a point touched by the user. Depending on screen coords, It could scale down, right, left, up or from the center.
The first thing to do is to set the center of our floating view in the touch point (It has to be the center, 'cause as Rob explanation):
-(void)onTouchInView:(CGPoint)theTouchPosition
{
self.floatingView.center = theTouchPosition;
....
}
The next issue is to setup the anchor point of the layer, depending on what we want to do (I measured the point relative to the screen frame, and determined where to scale it):
switch (desiredScallingDirection) {
case kScaleFromCenter:
{
//SCALE FROM THE CENTER, SO
//SET ANCHOR POINT IN THE VIEW'S CENTER
self.floatingView.layer.anchorPoint=CGPointMake(.5, .5);
}
break;
case kScaleFromLeft:
//SCALE FROM THE LEFT, SO
//SET ANCHOR POINT IN THE VIEW'S LEFT-CENTER
self.floatingView.layer.anchorPoint=CGPointMake(0, .5);
break;
case kScaleFromBottom:
//SCALE FROM BOTTOM, SO
//SET ANCHOR POINT IN THE VIEW'S CENTER-BOTTOM
self.floatingView.layer.anchorPoint=CGPointMake(.5, 1);
break;
case kScaleFromRight:
//SCALE FROM THE RIGHT, SO
//SET ANCHOR POINT IN THE VIEW'S RIGHT-CENTER
self.floatingView.layer.anchorPoint=CGPointMake(1, .5);
break;
case kScallingFromTop:
//SCALE FROM TOP, SO
//SET ANCHOR POINT IN THE VIEW'S CENTER-TOP
self.floatingView.layer.anchorPoint=CGPointMake(.5, 0);
break;
default:
break;
}
Now, all it's easy. Depending on the effect you want to give to the animation (The idea was a bouncing scaling from 0 to 100%):
//SETUP INITIAL SCALING TO (ALMOST) 0%
CGAffineTransform t=CGAffineTransformIdentity;
t=CGAffineTransformScale(t, .001, .001);
self.floatingView.transform=t;
[UIView animateWithDuration:.2
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
//BOUNCE A LITLE UP!
CGAffineTransform t = CGAffineTransformMakeScale(1.1,1.1);
self.floatingView.transform=t;
} completion:^(BOOL finished) {
[UIView animateWithDuration:.1
animations:^{
//JUST A LITTLE DOWN
CGAffineTransform t = CGAffineTransformMakeScale(.9,.9);
self.floatingView.transform = t;
} completion:^(BOOL finished) {
[UIView animateWithDuration:.05
animations:^{
//AND ALL SET
self.floatingView.transform = CGAffineTransformIdentity;
}
completion:^(BOOL finished) {
//ALL SET
}];
}];
}];
That's it. As you can see, the main thing is to setup correctly the view's position and anchor point. Then, it's all piece of cake!
Regards, and let the code be with you!

Animate Rotation/Scale/Translate of UIImage at the same time

I am new to objective-c and I want to add an image to the screen, tweening it like in AS3, moving it from one end to the other of the screen while rotating around its own center point.
I tried with
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
// TRANSFORM SCREENSHOT
screenShotView.transform = CGAffineTransformRotate(screenShotView.transform, -M_PI * 0.05);
screenShotView.transform = CGAffineTransformScale(screenShotView.transform, 0.6, 0.6);
screenShotView.transform = CGAffineTransformTranslate(screenShotView.transform,
self.webView.frame.origin.x,
self.webView.frame.origin.y - self.webView.frame.size.height * 0.3
);
but with this code the image rotates around the center of the TransformIdentity. So while rotating and moving the rotation gets out of controll and the image isn't exactly at the position I loved it to be.
What is the right way to rotate and translate at the same time, translating the rotation center with the image?
and at least after transformation I want to add a close button to the right upper corner of the image. for that I need the new coordinates of the corner, too.
thnx!
I now ended with the following code, but I still don't know if this is the state of the art solution.
CGAffineTransform translate = CGAffineTransformMakeTranslation(self.webView.frame.origin.x,self.webView.frame.origin.y - self.webView.frame.size.height * 0.25);
CGAffineTransform scale = CGAffineTransformMakeScale(0.6, 0.6);
CGAffineTransform transform = CGAffineTransformConcat(translate, scale);
transform = CGAffineTransformRotate(transform, degreesToRadians(-10));
[UIView beginAnimations:#"MoveAndRotateAnimation" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:2.0];
screenShotView.transform = transform;
[UIView commitAnimations];
IOS 7's preferred way to do this would be using a block object. It has several advantages compared to the 'older' way of animating. Especially that it can make use of multi-core and video co-processing. Also the 'built-in' call back part (the completion part) is very useful, as it keeps any necessary state information and links to objects as needed.
CGAffineTransform translate = CGAffineTransformMakeTranslation(self.webView.frame.origin.x,self.webView.frame.origin.y - self.webView.frame.size.height * 0.25);
CGAffineTransform scale = CGAffineTransformMakeScale(0.6, 0.6);
CGAffineTransform transform = CGAffineTransformConcat(translate, scale);
transform = CGAffineTransformRotate(transform, degreesToRadians(-10));
// animation using block code
[UIView animateWithDuration:2.0
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
screenShotView.transform = transform;
}completion:^(BOOL finished){
// do something if needed
}];

How to rotate an object around a arbitrary point?

I want to rotate an UILabel around an arbitrary point in a circular manner, not a straight line. This is my code.The final point is perfect but it goes through a straight line between the initial and the end points.
- (void)rotateText:(UILabel *)label duration:(NSTimeInterval)duration degrees:(CGFloat)degrees {
/* Setup the animation */
[UILabel beginAnimations:nil context:NULL];
[UILabel setAnimationDuration:duration];
CGPoint rotationPoint = CGPointMake(160, 236);
CGPoint transportPoint = CGPointMake(rotationPoint.x - label.center.x, rotationPoint.y - label.center.y);
CGAffineTransform t1 = CGAffineTransformTranslate(label.transform, transportPoint.x, -transportPoint.y);
CGAffineTransform t2 = CGAffineTransformRotate(label.transform,DEGREES_TO_RADIANS(degrees));
CGAffineTransform t3 = CGAffineTransformTranslate(label.transform, -transportPoint.x, +transportPoint.y);
CGAffineTransform t4 = CGAffineTransformConcat(CGAffineTransformConcat(t1, t2), t3);
label.transform = t4;
/* Commit the changes */
[UILabel commitAnimations];
}
You should set your own anchorPoint
Its very much overkill to use a keyframe animation for what really is a change of the anchor point.
The anchor point is the point where all transforms are applied from, the default anchor point is the center. By moving the anchor point to (0,0) you can instead make the layer rotate from the bottom most corner. By setting the anchor point to something where x or y is outside the range 0.0 - 1.0 you can have the layer rotate around a point that lies outside of its bounds.
Please read the section about Layer Geometry and Transforms in the Core Animation Programming Guide for more information. It goes through this in detail with images to help you understand.
EDIT: One thing to remember
The frame of your layer (which is also the frame of your view) is calculated using the position, bounds and anchor point. Changing the anchorPoint will change where your view appears on screen. You can counter this by re-setting the frame after changing the anchor point (this will set the position for you). Otherwise you can set the position to the point you are rotating to yourself. The documentation (linked to above) also mentions this.
Applied to you code
The point you called "transportPoint" should be updated to calculate the difference between the rotation point and the lower left corner of the label divided by the width and height.
// Pseudocode for the correct anchor point
transportPoint = ( (rotationX - labelMinX)/labelWidth,
(rotationX - labelMinY)/labelHeight )
I also made the rotation point an argument to your method. The full updated code is below:
#define DEGREES_TO_RADIANS(angle) (angle/180.0*M_PI)
- (void)rotateText:(UILabel *)label
aroundPoint:(CGPoint)rotationPoint
duration:(NSTimeInterval)duration
degrees:(CGFloat)degrees {
/* Setup the animation */
[UILabel beginAnimations:nil context:NULL];
[UILabel setAnimationDuration:duration];
// The anchor point is expressed in the unit coordinate
// system ((0,0) to (1,1)) of the label. Therefore the
// x and y difference must be divided by the width and
// height of the label (divide x difference by width and
// y difference by height).
CGPoint transportPoint = CGPointMake((rotationPoint.x - CGRectGetMinX(label.frame))/CGRectGetWidth(label.bounds),
(rotationPoint.y - CGRectGetMinY(label.frame))/CGRectGetHeight(label.bounds));
[label.layer setAnchorPoint:transportPoint];
[label.layer setPosition:rotationPoint]; // change the position here to keep the frame
[label.layer setTransform:CATransform3DMakeRotation(DEGREES_TO_RADIANS(degrees), 0, 0, 1)];
/* Commit the changes */
[UILabel commitAnimations];
}
I decided to post my solution as an answer. It works fine accept it doesn't have the old solutions's curve animations (UIViewAnimationCurveEaseOut), but I can sort that out.
#define DEGREES_TO_RADIANS(angle) (angle / 180.0 * M_PI)
- (void)rotateText:(UILabel *)label duration:(NSTimeInterval)duration degrees:(CGFloat)degrees {
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path,nil, 160, 236, 100, DEGREES_TO_RADIANS(0), DEGREES_TO_RADIANS(degrees), YES);
CAKeyframeAnimation *theAnimation;
// animation object for the key path
theAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
theAnimation.path=path;
CGPathRelease(path);
// set the animation properties
theAnimation.duration=duration;
theAnimation.removedOnCompletion = NO;
theAnimation.autoreverses = NO;
theAnimation.rotationMode = kCAAnimationRotateAutoReverse;
theAnimation.fillMode = kCAFillModeForwards;
[label.layer addAnimation:theAnimation forKey:#"position"];
}
CAKeyframeAnimation is the right tool for this job. Most UIKit animations are between start and end points. The middle points are not considered. CAKeyframeAnimation allows you to define those middle points to provide a non-linear animation. You will have to provide the appropriate bezier path for your animation. You should look at this example and the one's provided in the Apple documentation to see how it works.
translate, rotate around center, translate back.

Obj C: How to scale only to up?

Hey everbody. I have a little issue here. I just want to scale my view, but only to up. I mean, i dont want it move out from original coords, i just want to scale in one direction, without affecting the position.
[UIView beginAnimations: #"myViewAnimation" context: nil];
[UIView setAnimationDuration:1.0];
redView.transform = CGAffineTransformMakeScale(1.0, 3.0);
[UIView commitAnimations];
Thanks!
Origin of all UIView transforms is its layer's anchorPoint, which is in the center of the view by default. Set anchorPoint to CGPointZero so view will be scaled to the right and upwards and its current position will be fixed:
redView.layer.anchorPoint = CGPointZero;
...
UIView beginAnimations: #"myViewAnimation" context: nil];
[UIView setAnimationDuration:1.0];
redView.transform = CGAffineTransformMakeScale(1.0, 3.0);
[UIView commitAnimations];
Note also that view's frame is calculated using its layer's anchorPoint so you may need to also adjust your view's frame accordingly
To access view's layer you will need to add QuartzCore.framework to link with your project and import <QuartzCore/QuartzCore.h> header file.