Rotating a view around a fixed point - ObjectiveC - ios

I need to rotate a UIView around a fixed point.
bubbleTail = [[BubbleTail alloc] initWithFrame:bubbleTailFrame];
[self addSubview:bubbleTail];
bubbleTail.layer.anchorPoint = triangle_top_left;
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI * 180 / 180.0);
bubbleTail.transform = transform;
This works only if I comment the .anchorPoint line.
How do I rotate the view about a point inside the view?

The below should help. The tricky part is that setting the anchorpoint after adding a view with a frame then moves the view. I've gotten around this by initiating the UIView without a frame, then setting the position, bounds and anchorpoint of its layer. I then use the rotation to rotate around the anchor point.
CABasicAnimation *rotation;
rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotation.fromValue = [NSNumber numberWithFloat:0];
rotation.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
rotation.duration = 60.0;
rotation.repeatCount = HUGE_VALF;
UIView *myview = [[UIView alloc] init];
[self.view addSubview:myview];
[myview setBackgroundColor:[UIColor orangeColor]];
[myview.layer setPosition:CGPointMake(10, 10)];
[myview.layer setBounds:CGRectMake(0, 0, 100, 100)];
[myview.layer setAnchorPoint:CGPointMake(0, 0)];
[myview.layer addAnimation:rotation forKey:#"myAnimation"];
Someone please correct me if this is not the best method or if there are mistakes - I only know what I currently know - e.g. when I should use a . or f after a float.

So it seems CGAffineTransformMakeRotation rotates it around the center of the view?
Say you have point P (px,py) in view, you want view to be rotated around that.
Let's A(ax,ay) = P - center = (px - centerx, py - centery).
And you have angle alpha.
Then you could make a translate matrix with - A, then multipy it with rotation matrix with alpha, then multipy it with translate matrix with + A.
The functions you'll need are: CGAffineTransformMakeTranslation, CGAffineTransformMakeRotation, CGAffineTransformConcat.
P.S. Not sure if this will work (also not sure how view rect is represented, maybe it first assumption about rotation around center is wrong, then you shoud assume A = P), but that's how it's done with affine transforms.

Related

How to get perfect X , Y position of rotating UIView in iOS

I am using CABasicanimation for rotate an UIView. I am using this code:
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
NSNumber *currentAngle = [CircleView.layer.presentationLayer valueForKeyPath:#"transform.rotation"];
rotationAnimation.fromValue = currentAngle;
rotationAnimation.toValue = #(50*M_PI);
rotationAnimation.duration = 50.0f; // this might be too fast
rotationAnimation.repeatCount = HUGE_VALF; // HUGE_VALF is defined in math.h so import it
[CircleView.layer addAnimation:rotationAnimation forKey:#"rotationAnimationleft"];
in this I want perfect X,Y position while "UIView" is rotating. How can I achieve this?
I Got A Answer Of This Question Please Go To This Link For Answer
Go to This Link For Answer
So, if I understand you correctly, you just want to get the X and Y coordinates of your view during rotation?
You can do this by logging the frame of the presentationLayer, like this:
- (IBAction)buttonAction:(UIButton *)sender {
CGRect position = [[CircleView.layer presentationLayer] frame];
NSLog(#"%#", NSStringFromCGRect(position));
}

iOS: How to animate an image along a sine curve?

As in the title stated, I want to to animate some bubbles that travel along a sine curve from the bottom to the top within an UIView using CG/CA or even OpenGL if necessary.
Here is my CA code snippet that works fine, but it's a straight line which is animated. How can I build in the sine curve behaviour ?
- (void)animateBubble:(UIImageView *)imgView {
[UIView beginAnimations:[NSString stringWithFormat:#"%i",imgView.tag] context:nil];
[UIView setAnimationDuration:6];
[UIView setAnimationDelegate:self];
imgView.frame = CGRectMake(imgView.frame.origin.x, 0, imgView.frame.size.width, imgView.frame.size.height);
[UIView commitAnimations];
}
I've already achieved the wanted result with SpriteKit (have a look: http://youtu.be/Gnj3UAD3gQI). The bubbles travel along a sine curve from bottom to the top where the amplitude is within a random range. Moreover, I use the gyroscope sensors to additionally influence the path.
Is this behaviour somehow reproducible with UIKit, CG, CA (if absolutely necessary also with OpenGL) ? Code samples would be wonderful, but any other ideas are also highly appreciated.
To animate along a path, you first need to define that path. If you really need a true sine curve, we can show you how to do that, but it's probably easiest to define something that approximates a sine curve using two quadratic bezier curves:
CGFloat width = ...
CGFloat height = ...
CGPoint startPoint = ...
CGPoint point = startPoint;
CGPoint controlPoint = CGPointMake(point.x + width / 4.0, point.y - height / 4.0);
CGPoint nextPoint = CGPointMake(point.x + width / 2.0, point.y);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:point];
[path addQuadCurveToPoint:nextPoint controlPoint:controlPoint];
point = nextPoint;
controlPoint = CGPointMake(point.x + width / 4.0, point.y + height / 4.0);
nextPoint = CGPointMake(point.x + width / 2.0, point.y);
[path addQuadCurveToPoint:nextPoint controlPoint:controlPoint];
That renders path like so:
Obviously, change startPoint, width, and height to be whatever you want. Or repeat that process of adding more bezier paths if you need more iterations.
Anyway, having defined a path, rather than rendering the path itself, you can create a CAKeyframeAnimation that animates the position of the UIView along that path:
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.duration = 1.0;
animation.path = path.CGPath;
[view.layer addAnimation:animation forKey:#"myPathAnimation"];

How to find the center of a UIView after superviews have been transformed?

NOTE: code written in browser; probably not perfectly accurate, but it should give the general idea.
I've got a stack of views, something like this:
CGRect theFrame = CGRectMake(0, 0, 10, 10);
UIView *v1 = [UIView alloc] initWithFrame: theFrame];
UIView *v2 = [UIView alloc] initWithFrame: theFrame];
UIView *v3 = [UIView alloc] initWithFrame: theFrame];
// Set all the background colors, so I can see them: snip
// set all the clipsToBounds = NO, so I can place however I want: snip
v2.center = CGPointMake(100, 80);
[v1 addSubview: v2];
v3.center = CGPointMake (57, 42);
[v2 addSubview: v3];
v1.center = CGPointMake (193, 44);
[self.view addSubview: v1];
// etc., time passes, user presses TEST button
CGAffineTransform sXfrm = CGAffineTransformMakeScale(3.5, 4.7);
CGAffineTransform rXfrm = CGAffineTransformMakeRotation(M_PI / 3.);
v1.transform = CGAffineTransformConcat(sXfrm, rXfrm);
// etc., rotate & scale v2, while we're at it. snip
// Leave v3 unrotated & unscaled.
Ok, at this point, everything is displaying on the screen exactly as desired. My question is:
Where (that is: where, in self.view's coordinate space) is v3.center?
Here's some code that gives the CLOSE answer, but not-quite right, and I can't seem to figure out what's wrong with it:
CGRect testRect = CGRectMake(0, 0, 20, 20);
UIView *testView = [[UIView alloc] initWithFrame: testRect];
testView.backgroundColor = [UIColor greebColor];
[self.view addSubview: testView];
CGPoint center = v1.center;
#if 1 // Apply Transform
CGPoint ctr2 = CGPointApplyAffineTransform(v2.center, v1.transform);
#else // use layer
CGPoint ctr2 = CGPointMake ((v2.layer.frame.origin.x + (v2.layer.frame.size.width / 2.)),
(v2.layer.frame.origin.y + (v2.layer.frame.size.height / 2.)) );
ctr2 = CGPointApplyAffineTransform(ctr2, v1.transform);
#endif
center.x += ctr2.x;
center.y += ctr2.y;
CGPoint ctr3 = CGPointApplyAffineTransform(v3.center, CGAffineTransformConcat(v2.transform, v1.transform));
center.x += ctr3.x;
center.y += ctr3.y;
testView.center = center; // I want this to lay on top of v3
[self.view addSubview: testView];
NOTE: In my actual code, I put in-between test-views at all the intermediate centers. What I get is: testView1 (center) is correct, testView2 (ctr2) is off by a little, testView3 (ctr3) is off by a little more.
The #if is because I was experimenting with using ApplyAffine vs layer. Turns out they both give the same result, but it's a tad off.
Hopefully clarifying image:
You can use the UIView's convert points methods:
convertPoint:toView:
convertPoint:fromView:
convertRect:toView:
convertRect:fromView:
However once you apply a transformation, you should stop using frames and use center + bounds instead (which might be the reason your code is not working), from apple docs (for frame):
Warning: If the transform property is not the identity transform, the
value of this property is undefined and therefore should be ignored.
The only other thing you have to be conscious about is, which view invoques the convert to what other view since the results change. (source coordinate system -> target coordinate system.)
EDIT:
Thanks to this answer and Chiquis' comments in the question, my (Olie's) final (working) code looks like this:
CGRect testRect = CGRectMake(0, 0, 20, 20);
UIView *testView[3];
for (int ii = 0 ; ii < 3 ; ++ii)
{
testView[ii] = [[UIView alloc] initWithFrame: testRect];
testView[ii].backgroundColor = [UIColor redColor];
[self.view addSubview: testView[ii]];
}
testView[0].center = v0.center;
testView[1].center = [v0 convertPoint: v1.center toView: self.view];
testView[2].center = [v1 convertPoint: v2.center toView: self.view];
To clarify some, v1 is a subview of v0, and v2 is a subview of v1; this dictates the receivers in the covertPoint: calls.
I try to let iOS do the heavy lifting for me when I need to transform points between different layers.
CGPoint point = v3.center;
CGPoint ctr3 = [v3.layer.presentationLayer convertPoint:point toLayer:self.layer.presentationLayer];
The presentation layer object represents the state of the layer as it currently appears onscreen. This can help avoid timing issues if animations are involved.
Just noticed the comment that came in while I was composing: yes, I use this to account for rotation transforms similar to what you describe.

CAKeyframeAnimation stops for some angle values of CGPathAddArc

i am trying to create an animation,
in which a circle rotates around the center of the view.
I want the circle to appear under the location of the tap gesture, therefore i calculate distance and angle between the center of the view and the circle.
I then set these values inside the CGPathAddArc for the animation path.
This seem to work rather well, but once in a while, after having performed a whole rotation, the circle will stop for a short while, before restarting.
By playing around, i found out that this must be related to the start and stop angles of the CGPathAddArc function, as setting standard values like -M_PI to M_PI will not cause the issue.
My code is:
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.fillMode = kCAFillModeRemoved;
pathAnimation.removedOnCompletion = YES;
pathAnimation.duration = 4.0;
pathAnimation.repeatCount = HUGE_VALF;
CGMutablePathRef curvedPath = CGPathCreateMutable();
//get the angle and dinstance of location in relation to center of view (mathematics)
CGFloat angle = atan2f(location.y -self.view.center.y, location.x - self.view.center.x);
CGFloat distance = hypotf(location.x - self.view.center.x, location.y - self.view.center.y);
NSLog(#"angle: %f", angle);
CGPathAddArc(curvedPath, NULL, self.view.center.x, self.view.center.y, distance, angle, angle + 2 * M_PI, NO);
//Now we have the path, we tell the animation we want to use this path - then we release the path
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
//Add the animation to the circleView - once you add the animation to the layer, the animation starts
[self.view addSubview:circle];
[circle.layer addAnimation:pathAnimation forKey:#"moveTheSquare"];
location holds the coordinates of the touch event, the circle object is created before this code snippet. Any help would be greatly appreciated.
Adding this would smooth the animation out evenly:
pathAnimation.calculationMode = kCAAnimationPaced;
Just tested it.
Nice calculation, by the way.
Hope this helps.

How to rotate a flat object around its center in perspective view?

What I'm trying to do seems like it should be easy enough: I created a 2D top-down view of an old phonograph record. I want to rotate it (lay it back) in its X axis and then spin it around its Z axis.
I've read every question here that has CATransform3D in the body, I've read Steve Baker's "Matrices can be your friends" article as well as Bill Dudney's book "Core Animation for Mac OS X and the iPhone" I think Brad Larson's "3-D Rotation without a trackball" has all the right code, but since he's permitting the user to adjust all three axis, I'm having a hard time shrinking his code into what I perceive to be just one dimension (a rotated z axis).
Here's the image I'm testing with, not that the particulars are important to the problem:
I bring that onscreen the usual way: (in a subclass of UIView)
- (void)awakeFromNib
{
UIImage *recordImage = [UIImage imageNamed:#"3DgoldRecord"];
if (recordImage) {
recordLayer = [CALayer layer];
[recordLayer setFrame:CGRectMake(0.0, 0.0, 1024, 1024)];
[recordLayer setContents:(id)[[UIImage imageNamed:#"3DgoldRecord"] CGImage]];
[self.layer addSublayer:recordLayer];
}
}
That's the first test, just getting it on the screen ;-)
Then, to "lay it back" I apply a transform to rotate about the layer's X axis, inserting this code after setting the contents of the layer to the image and before adding the sublayer:
CATransform3D myRotationTransform =
CATransform3DRotate(recordLayer.transform,
(M_PI_2 * 0.85), //experiment with flatness
1.0, // rotate only across the x-axis
0.0, // no y-axis transform
0.0); // no z-axis transform
recordLayer.transform = myRotationTransform;
That worked as expected: The record is laying back nicely.
And for the next step, causing the record to spin, I tied this animation to the touchesEnded event, although once out of the testing/learning phase this rotation won't be under user control:
CATransform3D currentTransform = recordLayer.transform; // to come back to at the end
CATransform3D myRotationTransform =
CATransform3DRotate(currentTransform,
1.0, // go all the way around once
(M_PI_2 * 0.85), // x-axis change
1.00, // y-axis change ?
0.0); // z-axis change ?
recordLayer.transform = myRotationTransform;
CABasicAnimation *myAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
myAnimation.duration = 5.0;
myAnimation.fromValue = [NSNumber numberWithFloat:0.0];
myAnimation.toValue = [NSNumber numberWithFloat:M_PI * 2.0];
myAnimation.delegate = self;
[recordLayer addAnimation:myAnimation forKey:#"transform.rotation"];
So I'm pretty sure what I'm hung up on is the vector in the CATransform3DRotate call (trust me: I've been trying simple changes in that vector to watch the change... what's listed there now is simply the last change I tried). As I understand it, the values for x, y, and z in the transform are, in essence, the percentage of the value passed in during the animation ranging from fromValue to toValue.
If I'm on the right track understanding this, is it possible to express this in a single transform? Or must I, for each effective frame of animation, rotate the original upright image slightly around the z axis and then lay the result down with an x axis rotation? I saw a question/answer that talked about combining transforms, but that was a scale transform followed by a rotation transform. I have messed around with transforming the transform, but isn't doing what I think I should be getting (i.e. passing the result of one transform into the transform argument of the next seemed to just execute one completely and then animate the other).
This is easiest if you use a CATransformLayer as the parent of the image view's layer. So you'll need a custom view subclass that uses CATransformLayer as its layer. This is trivial:
#interface TransformView : UIView
#end
#implementation TransformView
+ (Class)layerClass {
return [CATransformLayer class];
}
#end
Put a TransformView in your nib, and add a UIImageView as a subview of the TransformView. Connect these views to outlets in your view controller called transformView and discView.
In your view controller, set the transform of transformView to apply perspective (by setting m34) and the X-axis tilt:
- (void)viewDidLoad
{
[super viewDidLoad];
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1 / 500.0;
transform = CATransform3DRotate(transform, .85 * M_PI_2, 1, 0, 0);
self.transformView.layer.transform = transform;
}
Add an animation for key path transform.rotation.z to discView in viewWillAppear: and remove it in viewDidDisappear::
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.fromValue = [NSNumber numberWithFloat:0];
animation.toValue = [NSNumber numberWithFloat:2 * M_PI];
animation.duration = 1.0;
animation.repeatCount = HUGE_VALF;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[self.discView.layer addAnimation:animation forKey:#"transform.rotation.z"];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.discView.layer removeAllAnimations];
}
Result:
UPDATE
Here's a Swift playground demonstration:
import UIKit
import XCPlayground
class TransformView: UIView {
override class func layerClass() -> AnyClass {
return CATransformLayer.self
}
}
let view = UIView(frame: CGRectMake(0, 0, 300, 150))
view.backgroundColor = UIColor.groupTableViewBackgroundColor()
XCPlaygroundPage.currentPage.liveView = view
let transformView = TransformView(frame: view.bounds)
view.addSubview(transformView)
var transform = CATransform3DIdentity
transform.m34 = CGFloat(-1) / transformView.bounds.width
transform = CATransform3DRotate(transform, 0.85 * CGFloat(M_PI_2), 1, 0, 0)
transformView.layer.transform = transform
let image = UIImage(named: "3DgoldRecord")!
let imageView = UIImageView(image: image)
imageView.bounds = CGRectMake(0, 0, 200, 200)
imageView.center = CGPointMake(transformView.bounds.midX, transformView.bounds.midY)
transformView.addSubview(imageView)
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.fromValue = 0
animation.toValue = 2 * M_PI
animation.duration = 1
animation.repeatCount = Float.infinity
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
imageView.layer.addAnimation(animation, forKey: animation.keyPath)
Copy the image from the question into the playground Resources folder and name it 3DgoldRecord.png. Result:
You may consider wrapping the recordLayer with a superlayer. Apply the x-axis rotation to the superlayer and add the z-axis rotation animation to recordLayer.

Resources