(Scale) Zoom into a UIView at a point - iphone

Something I don't understand about transformations. I want to zoom in to say the top right corner of a UIView (the main view). I use CGAffineTransformScale and have tried both setting the center/anchorPoint as well as CGAffineTransformMakeTranslation to no avail.
I can't figure out how to set the translation properly so that it will zoom in on this point.
CGAffineTransform tr = CGAffineTransformScale(self.view.transform, 2, 2);
[UIView animateWithDuration:2.5 delay:0 options:0 animations:^{
self.view.transform = tr;
self.view.center = CGPointMake(480,0);
} completion:^(BOOL finished) {}];

You are setting the center of the view to the top right corner. That means you're zooming in on somewhere to the lower left of the center.
Try this
CGAffineTransform tr = CGAffineTransformScale(self.view.transform, 2, 2);
CGFloat h = self.view.frame.size.height;
[UIView animateWithDuration:2.5 delay:0 options:0 animations:^{
self.view.transform = tr;
self.view.center = CGPointMake(0,h);
} completion:^(BOOL finished) {}];
This will set the center of the blown-up view to the left bottom corner, effectively zooming in with the top right corner fixed.
This code doesn't have the height hardcoded, which is a little more portable (to an iPad of iPhone 5).
You need to first save h before setting the transform property, because after that you should not rely on the value of frame.
edit
To make it work for any scale s, use this:
CGFloat s = 3;
CGAffineTransform tr = CGAffineTransformScale(self.view.transform, s, s);
CGFloat h = self.view.frame.size.height;
CGFloat w = self.view.frame.size.width;
[UIView animateWithDuration:2.5 delay:0 options:0 animations:^{
self.view.transform = tr;
self.view.center = CGPointMake(w-w*s/2,h*s/2);
} completion:^(BOOL finished) {}];
To make it work for bottom left, use this:
CGFloat s = 3;
CGAffineTransform tr = CGAffineTransformScale(self.view.transform, s, s);
CGFloat h = self.view.frame.size.height;
CGFloat w = self.view.frame.size.width;
[UIView animateWithDuration:2.5 delay:0 options:0 animations:^{
self.view.transform = tr;
self.view.center = CGPointMake(w*s/2,h-h*s/2);
} completion:^(BOOL finished) {}];
To make it work for bottom right, use this:
CGFloat s = 3;
CGAffineTransform tr = CGAffineTransformScale(self.view.transform, s, s);
CGFloat h = self.view.frame.size.height;
CGFloat w = self.view.frame.size.width;
[UIView animateWithDuration:2.5 delay:0 options:0 animations:^{
self.view.transform = tr;
self.view.center = CGPointMake(w-w*s/2,h-h*s/2);
} completion:^(BOOL finished) {}];
See also: How to scale (zoom) a UIView to a given CGPoint

In case anyone is still interested, here is the Swift 3.0 solution, to which I added the possibility to fade out correctly.
(I know there are two enums missing, but the code is still readable and I think you will get the concept)
func animateZoom(direction: ZoomDirection, type: ZoomType, duration: Double = 0.3, scale: CGFloat = 1.4) {
var cx, cy: CGFloat
var tr: CGAffineTransform = CGAffineTransform.identity
if (type == .IN) { tr = self.transform.scaledBy(x: scale, y: scale) }
let h: CGFloat = self.frame.size.height;
let w: CGFloat = self.frame.size.width;
if (type == .IN) {
switch direction {
case .LEFT_DOWN:
cx = w*scale/2
cy = h-h*scale/2
break
case .LEFT_UP:
cx = w*scale/2
cy = h*scale/2
break
case .RIGHT_UP:
cx = w-w*scale/2
cy = h*scale/2
break
case .RIGHT_DOWN:
cx = w-w*scale/2
cy = h-h*scale/2
break
}
} else {
cx = w/scale/2
cy = h/scale/2
}
UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseInOut], animations: {
self.transform = tr
self.center = CGPoint(x: cx, y: cy)
}, completion: { finish in })
}

Related

How to change for 3D rotation on UIPanGesture?

I have done animation opening a page on tapping on it but now I have to change it for Pan Gesture. How should I start?
[UIView setAnimationDelegate:self];
[UIView commitAnimations];
CALayer *layer = ges.view.layer;
CATransform3D initialTransform = ges.view.layer.transform;
initialTransform.m34 = 1.0 / -1100;
layer.transform = initialTransform;
layer.anchorPoint = CGPointMake(-0.0, 0.5);
[UIView beginAnimations:#"Scale" context:nil];
[UIView setAnimationDuration:2];
[UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];
CATransform3D rotationAndPerspectiveTransform = ges.view.layer.transform;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, -M_PI , 0 , -ges.view.bounds.size.height/2, 0);
ges.view.transform = CGAffineTransformRotate(ges.view.transform, 0);
layer.transform = rotationAndPerspectiveTransform;
[UIView setAnimationDelegate:self];
[UIView commitAnimations];
UIPanGestureRecognizer myPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(imagePanned:)];
[_panGesture setMaximumNumberOfTouches:1];
[yourView addGestureRecognizer:myPanGesture];
1) if you want page to turn along with the pan action as user moves the view then you have to do this way.
- (void)imagePanned:(UIPanGestureRecognizer *)iRecognizer {
CGPoint translation = [recognizer translationInView:self.view];
//Calculate transformation based on the translation value of pan gesture, this will be a tricky part
[UIView animateWithDuration:0.5 delay: 0 options: 0 animations:
^(void) {
//Apply transformation
}
completion: nil];
}
Hope this will help you.
Edit : Extended Answer
*This goes with the first idea*
You just want to rotate the view right ?
then use CATransform3D, here you will need to calculate the iAngle that we are applying to view.
iAngle = //Calculate based on translation
where translation is
CGPoint translation = [recognizer translationInView:self.view];
Then apply transformation to view
CATransform3D myTransform = CATransform3DIdentity;
myTransform.m34 = 1.0 / -500;
myTransform = CATransform3DRotate(myTransform, DEGREES_TO_RADIANS(-iAngle), 0.0f, 1.0f, 0.0f); //#define DEGREES_TO_RADIANS(d) (d * M_PI / 180)
yourView.layer.transform = myTransform;
I'll give you the math behind doing it as I don't have code right now to show. Converting finger movement to rotation value is simply a matter of setting up ratios.
For angle value (used in transform):
track_spread rotation_angle
____________ = ______________
movement_x angle = ?
angle = (rotation_angle * movement_x)/track_spread;
WHERE:
track_spread = width_of_your_view (or less e.g. 70% x width_of_your_view)
rotation_angle = the final rotation you want for your view (a constant e.g. 45 degrees)
CGPoint point = [recognizer translationInView:self];
movement_x = abs(point.x);
angle = the value (in degrees) you are interested in and will input into transform after converting to radians
If someone cant figure out and still need code snippet please leave a comment.

iPhone SDK: Flip-and-scale animation between view controllers using blocks?

I'm trying to create a flip-and-scale animation between two view controllers. This seems possible using animation blocks available in iOS 4.0, but I'm still unsure how to implement it. I found this SO question which shows a flip animation.
Using this code, flipping between two views works fine, but scaling doesn't -- the flip animation completes and then the new view jumps to the correct size. How would I flip the view and scale it at the same time?
UIView *tempContainer = myView.contentView ;
[UIView transitionWithView:tempContainer
duration:2
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
[[[tempContainer subviews] objectAtIndex:0] removeFromSuperview];
[tempContainer addSubview:myOtherViewController.view];
CGAffineTransform transform = CGAffineTransformMakeScale(4.0, 4.0);
tempContainer.transform = transform;
}
completion:^(BOOL finished){
[tempContainer release];
}];
I suppose this happens because the option UIViewAnimationOptionTransitionFlipFromRight somehow overrides everything else. Try using nesting animations or link them together
Here's how I flip and scale between two views of different sizes. I break the animation into a few parts. First I put the back view at the same location as the front view, but make it hidden. I then flip and scale the front view halfway. The back view is given the same transform as the front view, then rotated and scaled the rest of the way. Flipping back is basically the reverse.
I suppose you could use a different view controllers view property as the back view, but I haven't tried that.
// flip and scale frontView to reveal backView to the center of the screen
// uses a containerView to mark the end of the animation
// parameterizing the destination is an exercise for the reader
- (void)flipFromFront:(UIView*)frontView toBack:(UIView*)backView destination:(CGRect)destRect
{
float duration = 0.5;
// distance from center of screen from frontView
float dx = destRect.origin.x + CGRectGetWidth(destRect) / 2 - frontView.center.x;
float dy = destRect.origin.y + CGRectGetHeight(destRect) / 2 - frontView.center.y;
float scale = CGRectGetWidth(destRect) / CGRectGetWidth(frontView.bounds);
// this prevents any tearing
backView.layer.zPosition = 200.0;
// hide the backView and position where frontView is
backView.hidden = NO;
backView.alpha = 0.0;
backView.frame = frontView.frame;
// start the animation
[UIView animateKeyframesWithDuration:duration
delay:0.25
options:UIViewKeyframeAnimationOptionCalculationModeCubic
animations:^{
// part 1. Rotate and scale frontView halfWay.
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:0.5
animations:^{
// get the transform for the blue layer
CATransform3D xform = frontView.layer.transform;
// translate half way
xform = CATransform3DTranslate(xform, dx/2, dy/2, 0);
// rotate half way
xform = CATransform3DRotate(xform, M_PI_2, 0, 1, 0);
// scale half way
xform = CATransform3DScale(xform, scale/2, scale/2, 1);
// apply the transform
frontView.layer.transform = xform;
}];
// part 2. set the backView transform to frontView so they are in the same
// position.
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.0
animations:^{
backView.layer.transform = frontView.layer.transform;
backView.alpha = 1.0;
}];
// part 3. rotate and scale backView into center of container
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.5
animations:^{
// undo previous transforms with animation
backView.layer.transform = CATransform3DIdentity;
// animate backView into new location
backView.frame = destRect;
}];
} completion:^(BOOL finished) {
self.displayingFront = !self.displayingFront;
}];
}
// flip from back to front
- (void) flipFromBack:(UIView*)backView toFront:(UIView*)frontView
{
float duration = 0.5;
// get distance from center of screen to destination
float dx = backView.center.x - frontView.center.x;
float dy = backView.center.y - frontView.center.y;
backView.layer.zPosition = 200.0;
frontView.hidden = YES;
// this is basically the reverse of the previous animation
[UIView animateKeyframesWithDuration:duration
delay:0
options:UIViewKeyframeAnimationOptionCalculationModeCubic
animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:0.5
animations:^{
CATransform3D xform = backView.layer.transform;
xform = CATransform3DTranslate(xform, -dx/2, -dy/2, 0);
xform = CATransform3DRotate(xform, M_PI_2, 0, 1, 0);
xform = CATransform3DScale(xform, 0.75, 0.75, 1);
backView.layer.transform = xform;
}];
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.0
animations:^{
backView.alpha = 0.0;
frontView.hidden = NO;
}];
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.5
animations:^{
self.hiddenView.alpha = 0.0;
frontView.layer.transform = CATransform3DIdentity;
}];
} completion:^(BOOL finished) {
self.displayingFront = !self.displayingFront;
}];
}

Moving an object randomly around the screen

I'm trying to animate a UIButton to move randomly around the screen in different directions. The code below is kind of working. The button will begin moving along a random path, however, it then just continues to move back and forth between point A and point B.
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationRepeatCount:1000];
[UIView setAnimationRepeatAutoreverses:YES];
CGFloat x = (CGFloat) (arc4random() % (int) self.view.bounds.size.width);
CGFloat y = (CGFloat) (arc4random() % (int) self.view.bounds.size.height);
CGPoint squarePostion = CGPointMake(x, y);
button.center = squarePostion;
[UIView commitAnimations];
How can I get it to keep moving to a new random point every time it changes directions, instead of simply moving back and forth?
Thanks!
try this:
-(void)animationLoop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1];
// remove:
// [UIView setAnimationRepeatCount:1000];
// [UIView setAnimationRepeatAutoreverses:YES];
CGFloat x = (CGFloat) (arc4random() % (int) self.view.bounds.size.width);
CGFloat y = (CGFloat) (arc4random() % (int) self.view.bounds.size.height);
CGPoint squarePostion = CGPointMake(x, y);
button.center = squarePostion;
// add:
[UIView setAnimationDelegate:self]; // as suggested by #Carl Veazey in a comment
[UIView setAnimationDidStopSelector:#selector(animationLoop:finished:context:)];
[UIView commitAnimations];
}
and just add a counter (int) inside the method to check if it's executed more than 1000 times, if wanna stop it...
Swift 5 example of making one animation
#objc func didBecomeActive() {
DispatchQueue.main.async {
self.startAnimationCalm()
}
}
func startAnimation() {
let animatedShadow = UIView(frame: CGRect(origin: CGPoint(x: 10, y: 10), size: CGSize(width: 20, height: 20)))
animatedShadow.clipsToBounds = true
animatedShadow.layer.cornerRadius = 20/2
animatedShadow.backgroundColor = UIColor.green
animatedShadow.layer.borderWidth = 0
self.view.addSubview(animatedShadow)
UIView.animate(withDuration: 5, delay: TimeInterval(0), options: [.repeat, .curveEaseIn], animations: { () -> Void in
let randomX = CGFloat.random(in: 14 ... 200)
let randomY = CGFloat.random(in: 20 ... 600)
animatedShadow.center = CGPoint(x: animatedShadow.frame.origin.x + randomX, y: animatedShadow.frame.origin.y + randomY)
self.view.layoutIfNeeded()
}, completion: { finished in
})
}

Strange bug with View interaction

I have to views. One on top of the other.
But i cannot click the subviews of the top view until I set the alpha of the bottom view to 0.0.
Why would that be? Is there some kind of work around?
Code involved
-(void)setUpOpponentsCardStartingPosition
{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.0];
CGRect newF = deckCard.view.frame;
newF.origin.x -= CGRectGetWidth(newF);
deckCard.view.layer.transform = CATransform3DIdentity;
deckCard.view.frame = newF;
deckCard.view.alpha = 0.0;
[UIView commitAnimations];
playersCard.view.layer.transform = CATransform3DScale(CATransform3DIdentity, 0.97,
0.97, 1.0);
opponentsCard.view.layer.transform = CATransform3DRotate(CATransform3DIdentity, -
35*M_PI/180, 0.0, 1.0, 0.0);
opponentsCard.view.layer.transform =
CATransform3DScale(opponentsCard.view.layer.transform, 0.65, 0.675, 1.0);
opponentsCard.view.layer.transform =
CATransform3DTranslate(opponentsCard.view.layer.transform, 400, 0, 0);
opponentsCard.view.hidden = NO;
}
if i comment out the deckCard.view.alpha = 0.0; it wont let me interact with playersCard. This makes no sense to me since playersCard is on top.
Here is setup code for deckCard
-(void)setupDeckInBackground
{
deckCard.view.alpha = 0.0;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.75];
deckCard.view.frame = playersCard.view.frame;
deckCard.view.alpha = 1.0;
deckCard.view.layer.zPosition = -1;
deckCard.view.layer.transform = CATransform3DMakeRotation(M_PI/180*5, 0, 0, 1);
deckCard.view.layer.transform = CATransform3DScale(deckCard.view.layer.transform,
1.0, 1.0, 1.0);
deckCard.view.layer.transform =
CATransform3DTranslate(deckCard.view.layer.transform, 0, 0, -25);
[UIView commitAnimations];
}
Thanks
-Code
I found that the solution was to use
deckCard.view.userInteractionEnabled = NO;
Thanks,
-Code

Animate Rotating UIImageView

I want to rotate a UIImageView by roughly 10 degrees left/right but have a smooth animation, rather than a sudden turn which I see using:
player.transform = CGAffineTransformMakeRotation(angle)
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.25]; // Set how long your animation goes for
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
player.transform = CGAffineTransformMakeRotation(angle); // if angle is in radians
// if you want to use degrees instead of radians add the following above your #implementation
// #define degreesToRadians(x)(x * M_PI / 180)
// and change the above code to: player.transform = CGAffineTransformMakeRotation(degreesToRadians(angle));
[UIView commitAnimations];
// The rotation code above will rotate your object to the angle and not rotate beyond that.
// If you want to rotate the object again but continue from the current angle, use this instead:
// player.transform = CGAffineTransformRotate(player.transform, degreesToRadians(angle));
I use some code like this to achieve a similar effect (though I rotate it by 360 degrees):
CABasicAnimation *rotate;
rotate = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotate.fromValue = [NSNumber numberWithFloat:0];
rotate.toValue = [NSNumber numberWithFloat:deg2rad(10)];
rotate.duration = 0.25;
rotate.repeatCount = 1;
[self.view.layer addAnimation:rotate forKey:#"10"];
I use code very similar to this to spin an image view.
Converted for Swift 3/4:
let animation = CABasicAnimation(keyPath: "transform.rotation")
animation.fromValue = 0
animation.toValue = Double.pi * 2.0
animation.duration = 2
animation.repeatCount = .infinity
animation.isRemovedOnCompletion = false
imageView.layer.add(animation, forKey: "spin")
i have some code rotate 360 degrees:
- (void) runSpinAnimationOnView:(UIView*)view duration:(CGFloat)duration rotations:(CGFloat)rotations repeat:(float)repeat;
{
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: rotations * 2.0 /* full rotation*/ * rotations * duration ];
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = repeat;
[view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
call it: [self runSpinAnimationOnView:imgView duration:0.1 rotations:M_PI_4 repeat:10];
And animation rotate some degrees and again:
- (void)rotateImage:(UIImageView *)image duration:(NSTimeInterval)duration delay:(NSTimeInterval)delay
curve:(int)curve rotations:(CGFloat)rotations
{
[UIView animateWithDuration:duration
delay:delay
options:0
animations:^{
[UIView setAnimationCurve:curve];
image.transform = CGAffineTransformMakeRotation(rotations);
}
completion:^(BOOL finished){
[self rotateImage2:image duration:duration delay:delay curve:curve rotations:rotations];
;
}];
[UIView commitAnimations];
}
- (void)rotateImage2:(UIImageView *)image duration:(NSTimeInterval)duration delay:(NSTimeInterval)delay
curve:(int)curve rotations:(CGFloat)rotations
{
[UIView animateWithDuration:duration
delay:delay
options:0
animations:^{
[UIView setAnimationCurve:curve];
image.transform = CGAffineTransformMakeRotation(-rotations);
}
completion:^(BOOL finished){
[self rotateImage:image duration:duration delay:delay curve:curve rotations:rotations];
;
}];
[UIView commitAnimations];
}
If you want to rotate a uiimageview by roughly 10 degrees left/right
call : [self rotateImage:imgView duration:0.7 delay:0.0 curve:UIViewAnimationCurveEaseIn rotations:(M_PI/18)];
similar, some degress
A modern Swift solution, using NSTimer and CGAffineTransformMakeRotation:
class Rotation: UIViewController {
var timer = NSTimer()
var rotAngle: CGFloat = 0.0
#IBOutlet weak var rotationImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
activateTimer()
}
func activateTimer() {
//(1)
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target:self, selector:#selector(Rotation.updateCounter), userInfo: nil, repeats: true)
}
func updateCounter() {
//(2)
var rotateLeft: Bool = true
if rotateLeft {
rotAngle -= 30.0
} else {
rotAngle += 30.0
}
//(3)
UIView.animateWithDuration(2.0, animations: {
self.rotationImage.transform = CGAffineTransformMakeRotation((self.rotAngle * CGFloat(M_PI)) / 180.0)
})
}
}
Things to notice:
Connect the outlet the the UIImageView
Play with the (1)timer pace, (2)animation pace and (3)rotation angle to get the desired results. This set of variables worked for me.