Animate rectangle by change width and height - ios

I have ImageView in my UIViewController:
And i want to animate this Image, periodically change width and height. The same animation you can see at Apple Wallet app on Iphone, if you click "Scan code". I tried a lot, but my code working incorrect or app begin lagging after 3-5 min...
This is my last code:
class ViewController: UIViewController {
#IBOutlet weak var focusImage: UIImageView!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.resizeFocusImageUp()
}
func resizeFocusImageUp() {
UIView.animateWithDuration(2.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.focusImage.frame = CGRectMake(
self.focusImage.frame.origin.x+50,
self.focusImage.frame.origin.y-50,
self.focusImage.frame.size.width-50,
self.focusImage.frame.size.height+50
)
self.view.layoutIfNeeded()
self.view.bringSubviewToFront(self.focusImage)
}, completion: {(Bool) in
self.resizeFocusImageDown()
})
}
func resizeFocusImageDown() {
UIView.animateWithDuration(2.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
self.focusImage.frame = CGRectMake(
self.focusImage.frame.origin.x-50,
self.focusImage.frame.origin.y+50,
self.focusImage.frame.size.width+50,
self.focusImage.frame.size.height-50
)
self.view.layoutIfNeeded()
self.view.bringSubviewToFront(self.focusImage)
}, completion: {(Bool) in
self.resizeFocusImageUp()
})
}
}
Also, for my ImageView i use constraints:

As the other poster said, if you're using AutoLayout then you need to animate the constraints, not the frame.
Create constraints for the height & width of your view, and position if you need to move it in your animation. Then control-drag from your CONSTRAINTS into the header of your view controller to create outlets.
Then in your animation block change the constant value of each constraint, then call layoutIfNeeded to trigger the animation. (The critical part is that the call to layoutIfNeeded needs to be inside the animation block. It seems that what actually causes the constraint to change the size/position of the view(s).)
All that being said, for a line drawing like you show in your post, you'd be much better off using a CAShapeLayer and animating the path of the layer. You'd get much crisper-looking shapes, and the animations are very smooth.

//Below Objective-C code working correctly in my app. Do your stuff as per your requirements.
Note: If you are using autoalyout for that object(Image view) then add below two line code in view did load method.
imageView.translatesAutoresizingMaskIntoConstraints = YES;
imageView.frame = CGRectMake(x, y, width, height);
//Method For Zoom IN Animations Of View
-(void)ZoomInAnimationOfView{
imageView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.01, 0.01);
[UIView animateWithDuration:1.0 animations:^{
imageView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.0, 1.0);
} completion:^(BOOL finished) {
imageView.transform = CGAffineTransformIdentity;
}];
}
//Method For Zoom Out Animations
-(void)ZoomOutAnimationOfView{
imageView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.0, 1.0);
[UIView animateWithDuration:1.0 animations:^{
imageView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.001, 0.001);
} completion:^(BOOL finished) {
imageView.transform = CGAffineTransformIdentity;
}];
}

Related

UIView animation with autolayout and child views

I have a weird problem that seems to be fairly easy to solve. I think I could use some workaround to be able to have the behavior that I want but I want to know if there is a better way to do it.
I have a view called contentView (blue in the image) that it will expand its height using a UIView.animation, this is no problem at all and works as expected.
The problem here is that this view has a child component (a button) that has an autolayout constraint to its bottom equal to 22 like this:
This is the autolayout constraint:
If I do the height resize without the animation it works fine, the view height change and the button are always 22 points of the bottom of the contentView. But when I use an animation to make this change more smoothy and user-friendly the button moves to the end position before the animation even start.
I want to know how I could achieve a smooth animation here but with the button moving along its parent view
The part of the code that handles the animation is pretty straightforward but I'll post it in here:
#IBAction func openDetail(_ sender: ExpandCourseDetail) {
let rotation = sender.getOpen() ? CGAffineTransform.identity : CGAffineTransform(rotationAngle: CGFloat.pi)
UIView.animate(withDuration: 0.5, delay: 0.1, options: [.curveEaseInOut], animations: {
sender.transform = rotation
}, completion: {
success in
sender.setOpen(!sender.getOpen())
})
UIView.animate(withDuration: 1.0, delay: 0.5, options: [.curveEaseInOut], animations: {
self.contentView.frame.size.height = sender.getOpen() ? self.contentView.frame.height - 300 : self.contentView.frame.height + 300
}, completion: nil)
}
As a side note, the button itself has an animation that rotates the button 180 degrees to show the user that the view is expanding.
Thank you so much for your help.
It's super easy with constraints, just create a superView height constraint IBOutlet and change its constant value.
#IBAction func btnPressed(_ sender: UIButton) {
self.toggleButton.isSelected = !sender.isSelected
//Animation starts here
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.7) {
if self.toggleButton.isSelected {
//transform button
self.toggleButton.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
//change your ParentView height to desired one
self.constContentViewHeight.constant = self.view.frame.size.height - 220
self.view.layoutIfNeeded()
} else {
self.toggleButton.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi*2))
// Set height constraint to original value
self.constContentViewHeight.constant = 250
self.view.layoutIfNeeded()
}
}
}
I have created a demo, check it out.
The issue you are facing is due to two animation blocks. So I have changed some lines and put both button transformation and height animation into one animation block.
func openDetail(_ sender: ExpandCourseDetail) {
let isOpen = sender.getOpen() ? CGAffineTransform.identity : CGAffineTransform(rotationAngle: CGFloat.pi)
UIView.animate(withDuration: 1.0, delay: 0.5, options: [.curveEaseInOut], animations: {
sender.transform = rotation
self.contentView.frame.size.height = self.contentView.frame.height + (isOpen ? 300 : -300)
}, completion: { (success) in
sender.setOpen(!isOpen)
})
}

Animate UILabel resizing from center?

I want to resize my label from center and animate that process. However, the only option I can use there is CGAffineTransformMakeScale with default anchor point of corresponding CALayer.
[UIView animateWithDuration:0.5 animations:^{
self.title.transform = CGAffineTransformMakeScale(2.0,1.0);
}];
But it squished the text inside. I considered this answer and it looks like the problem is solved in Swift.
However, I can't modify only size part of the frame. How to approach this in Obj C?
This will work for you. You just need to give scale for width and height (default 1, which will have no effect). And this will animate your view's frame and set it back to the original.
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:(void (^)(void)) ^{
self.textLabel.transform=CGAffineTransformMakeScale(3, 1);
}
completion:^(BOOL finished){
self.textLabel.transform=CGAffineTransformIdentity;
}];
If you dont want revert effect than you can do like this
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:(void (^)(void)) ^{
self.textLabel.transform=CGAffineTransformMakeScale(3, 1);
}
completion:nil];
From what I understood you want to change the size of UILabel not the text via animation. This can be achieved by grabbing a copy of the current label's frame and then modify it and set it back.
i.e
var frame = self.myLabel.frame
// frame modification ...
self.myLabel.frame = frame
Solution
Code
For the sizeTranformAction: you can a simple button and connect it to test the behavior.
class SizeTransformViewController: UIViewController {
var myLabel: UILabel! = nil
// MARK: - View lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.setupLabel()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Setups
func setupLabel() {
self.myLabel = UILabel()
self.myLabel.center = self.view.center
self.myLabel.text = "Text"
self.myLabel.sizeToFit()
self.myLabel.textAlignment = NSTextAlignment.Center
self.myLabel.backgroundColor = UIColor.redColor()
self.view.addSubview(self.myLabel)
}
// MARK: - Actions
#IBAction func sizeTranformAction(sender: AnyObject) {
var frame = self.myLabel.frame
let size = frame.size
frame.size = CGSize(width: 2*size.width, height: size.height)
frame.origin.x = self.view.center.x - size.width
UIView.animateWithDuration(0.5, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.myLabel.frame = frame
}, completion: nil)
}
}
Output
I actually ended up with 2 labels. Animation sequence is following:
Create a temporary label with no text in it, attributes like bg
color, font, font size, etc should be the same as of your main label.
Place the temporary label above your main label. Set main label's text property to nil
Animate the frame of main label to a desired value
When animation finishes, set back main label's text property to original value (presumably, in completion handler of animateWithDuration:)
Hide temporary label / remove it at all
You can change your constant to compensate the new width after scaling. Would this help?
layoutIfNeeded()
let scale: CGFloat = 0.72
let consLeft = NSLayoutConstraint(
item: titleLabel,
attribute: .left,
relatedBy: .equal,
toItem: self,
attribute: .left,
multiplier: 1,
constant: titleLabel.frameWidth * -(1-scale)/2
)
consLeft.isActive = true
let animations = {
self.titleLabel.transform = CGAffineTransform(scaleX: scale, y: scale)
self.layoutIfNeeded()
}
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseOut],
animations: animations,
completion: nil)

Flip image to reveal back iOS

Basic newbie question.
I am looking to place several images into a collection view. How can I have the image flip over to reveal the back of the image in Xcode using swift? Thank you all for your help.
I'm not sure about what exactly are you trying to achieve, but I'm using this function to rotate image horizontally and to change the image during the transition. Hope it helps.
private func flipImageView(imageView: UIImageView, toImage: UIImage, duration: NSTimeInterval, delay: NSTimeInterval = 0)
{
let t = duration / 2
UIView.animateWithDuration(t, delay: delay, options: .CurveEaseIn, animations: { () -> Void in
// Rotate view by 90 degrees
let p = CATransform3DMakeRotation(CGFloat(GLKMathDegreesToRadians(90)), 0.0, 1.0, 0.0)
imageView.layer.transform = p
}, completion: { (Bool) -> Void in
// New image
imageView.image = toImage
// Rotate view to initial position
// We have to start from 270 degrees otherwise the image will be flipped (mirrored) around Y axis
let p = CATransform3DMakeRotation(CGFloat(GLKMathDegreesToRadians(270)), 0.0, 1.0, 0.0)
imageView.layer.transform = p
UIView.animateWithDuration(t, delay: 0, options: .CurveEaseOut, animations: { () -> Void in
// Back to initial position
let p = CATransform3DMakeRotation(CGFloat(GLKMathDegreesToRadians(0)), 0.0, 1.0, 0.0)
imageView.layer.transform = p
}, completion: { (Bool) -> Void in
})
})
}
Don't forget to import GLKit.
-(void)showButtton
{
self.userInteractionEnabled = false;
[UIView transitionWithView:self duration:0.3 options:UIViewAnimationOptionTransitionFlipFromRight animations:^{
//code to change the image of UIButton
} completion:^(BOOL finished) {
self.userInteractionEnabled = true;
}];
}
Here is a code to do so.

Scale a UIView with animation but without Transform

I try to scale a UIView with a circle shape to make it double in size when I click on it.
my final target is to reproduce the apple music behavior:
-When you click on a circle, it grows and pushes other circles around.
I m trying to achieve this with UIKit dynamics but the first problem is that animating a scale without CATransform (it doesn't work with collisions) fails.
at the end of the animation, even if width and height are 100,
the final shape is a rectangle...
here is my code
func ballTapped(sender:UITapGestureRecognizer) {
if sender.state == .Ended {
collision.removeItem(sender.view!)
let item = sender.view!
var theFrame = item.frame
theFrame.size.height = 100
theFrame.size.width = 100
UIView.animateWithDuration(0.5, animations: { () -> Void in
item.frame = theFrame
}, completion: { (Bool) -> Void in
self.collision.addItem(sender.view!)
}
)
}
}
[UIView animateWithDuration:0.5
animations:^{
[UIView animateWithDuration: 0.5 delay: 0.0 options:UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveEaseInOut animations:^{
_iCarouselCarVW.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.001, 0.001);
_iCarBackView.hidden = YES;
NSLog(#"%f",self.view.frame.size.width);
_iCarouselCenterConstant.constant = self.view.frame.size.width/2 + 50;
[self.iCarouselCarVW setCurrentItemIndex:0];
} completion:^(BOOL finished)
{
}];
[self.view layoutIfNeeded]; // Called on parent view
}];

iOS: UIView animation ignores delay

I have a problem with very simple UIView animation:
- (void)showView:(CGFloat)delay
{
[UIView animateWithDuration:1.0 delay:delay options:0 animations:^{
// self.alpha = 0.5; // Works OK with delay
self.frame = CGRectMake(100, 100, 100, 30); // delay is ignored
} completion:nil];
}
delay could be set to 1000 and still view is animated immediately. But somehow it works fine with alpha (without setting frame).
The frame is probably being set for you, perhaps by layout being performed by the superview, or this view itself. You haven't said how you're adding this view or what it does so its difficult to give specific advice, but in general terms:
Don't let a view be in control of its own frame - this is the superview's or view controllers responsibility. A view can have a preferred size, but the rest is outside.
it's usually better to animate bounds and / or centre instead of frame, particularly if you're ignoring the first point.
For presentation type animations, it may be better to animate the transform instead - either translation or scale, depending what your animation is.
[self performSelector:#selector(animateMe) withObject:nil afterDelay:1000];
If self is viewController then you should use self.view.frame instead of self.frame. If self is view then you should use self.frame.
- (void)animateMe {
[UIView animateWithDuration:1.0f animations:^{
// self.alpha = 0.5; // Works OK with delay
self.frame = CGRectMake(100, 100, 100, 30); // delay is ignored
} completion:^(BOOL finished) {
NSLog(#"Done");
}];
}
This answer uses Swift, specifically version 4.2, and is tested in Xcode 10 playgrounds. This is actually my first try animating, and I'm very green with iOS in general, but this is how I solved the problem of delay not being observed.
import UIKit
import PlaygroundSupport
// Set up the main view
let liveViewFrame = CGRect(x: 0, y: 0, width: 500, height: 500)
let liveView = UIView(frame: liveViewFrame)
liveView.backgroundColor = .white
// Show the live view using the main view
PlaygroundPage.current.liveView = liveView
// Set up a smaller view for animation
let smallFrame = CGRect(x: 0, y: 0, width: 100, height: 100)
let square = UIView(frame: smallFrame)
square.backgroundColor = .purple
// Add the square to the live view ...
liveView.addSubview(square)
// ... and create an alias for it to allow delays to work.
// Calling square directly won't allow delays,
// giving it or the liveView subview an alias directly fails,
// and unwrapping (? or !) appears to fail as well.
// So, just call like `alias?.backgroundColor` ...
let squareView = square.superview?.subviews[0]
UIView.animate(withDuration: 1.5, delay: 1.0, options: [.repeat], animations: {
//square.backgroundColor = .orange // this doesn't respect the delay arg
squareView?.backgroundColor = .orange
squareView?.frame = CGRect(x: 150, y: 150, width: 200, height: 200)
squareView?.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
}, completion: nil)
This may not answer the question directly, but I believe similar logic could be applied for a workaround in related cases.
Instead of
UIView.animate(
withDuration: 0.2,
delay: 1,
options: .curveLinear,
animations: {
self.someButton.isHidden = false
})
}
Do just:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
UIView.animate(withDuration: 0.2,
delay: 0,
options: .curveLinear,
animations: {
self.someButton.isHidden = false
})
}

Resources