Animate UILabel resizing from center? - ios

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)

Related

Swift image appear animation from bottom of its frame to top (wipe animation)

I just want to get some ideas on how to approach the situation I have in hand.
I have created some images which I want to animate, but the animation I want is not default given, so its giving me some hard time.
The animation I want is like this: https://i.stack.imgur.com/byGMC.gif (not from bottom of the screen, from the bottom of images' own frame)
When a button is pressed I want the button/image start appear from bottom of its frame to the top, like the wipe animation in powerpoint :)
So naturaly it will default as hidden and when a button is pressed it will animate in.
I did try a few methods but non of them is actually doing the job I want.
Like this one:
extension UIView{
func animShow(){
UIView.animate(withDuration: 2, delay: 5, options: [.transitionFlipFromBottom],
animations: {
self.center.y -= self.bounds.height
self.layoutIfNeeded()
}, completion: nil)
self.isHidden = false
}
func animHide(){
UIView.animate(withDuration: 1, delay: 0, options: [.curveLinear],
animations: {
self.center.y += self.bounds.height
self.layoutIfNeeded()
}, completion: {(_ completed: Bool) -> Void in
self.isHidden = true
})
}
}
so it slides in the image but this is not what I want, you may try the code, just add this and write image.animShow() in viewDidLoad, button is the button which I want to animate.
I appreciate every bit of help, and I am a newbie in swift programming
Thank you.
There are various ways to do a "soft wipe" animation... here is one.
We can add a gradient layer mask and then animate the gradient.
When using a layer mask, clear (alpha 0) areas hide what's under the mask, and opaque (alpha 1) areas show what's under the mask. This includes parts of the mask that are translucent - alpha 0.5 for example - so the content becomes "translucent".
To get a "soft wipe" we want the gradient to use only a portion of the frame, so if we set the starting Y point to 0.5 (halfway down), for example, we can set the ending Y point to 0.6 ... this would give us a horizontal "gradient band".
So, a yellow-to-red gradient at 0.5 to 0.6 would look like this:
If we set the starting Y to 1.0 and the ending Y to 1.1, the gradient band will start "off the bottom of the view" ... and we can animate it up until it's "off the top of the view" (note that converted to gif loses some of the smooth gradient property):
Now, if we instead use clear-to-red and set it as a layer mask, using the same animation it will "soft wipe" the view, it will look something like this:
Can't get the animated gif small enough to post here, so here's a link to the animation: https://i.imgur.com/jo2DH3Z.mp4
Here's some sample code for you to play with. Lots of comments in there:
class AnimatedGradientImageView: UIImageView {
let maskLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
// vertical gradient
// start at bottom
maskLayer.startPoint = CGPoint(x: 0.5, y: 1.0)
// to bottom + 1/10th
maskLayer.endPoint = CGPoint(x: 0.5, y: 1.1)
// use "if true" to see the layer itself
// use "if false" to see the image reveal
if false {
// yellow to red
maskLayer.colors = [UIColor.yellow.cgColor, UIColor.red.cgColor]
// add it as a sublayer
layer.addSublayer(maskLayer)
} else {
// clear to red
maskLayer.colors = [UIColor.clear.cgColor, UIColor.red.cgColor]
// set the mask
layer.mask = maskLayer
}
}
override func layoutSubviews() {
super.layoutSubviews()
// set mask layer frame
maskLayer.frame = bounds
}
func reveal() -> Void {
let anim1: CABasicAnimation = CABasicAnimation(keyPath: "startPoint.y")
// anim1 animates the gradient start point Y
// to -0.1 (1/10th above the top of the view)
anim1.toValue = -0.1
anim1.duration = 1.0
anim1.isRemovedOnCompletion = false
anim1.fillMode = .forwards
let anim2: CABasicAnimation = CABasicAnimation(keyPath: "endPoint.y")
// anim2 animates the gradient end point Y
// to 0.0 (the top of the view)
anim2.toValue = 0.0
anim2.duration = 1.0
anim2.isRemovedOnCompletion = false
anim2.fillMode = .forwards
maskLayer.add(anim1, forKey: nil)
maskLayer.add(anim2, forKey: nil)
}
}
class AnimatedGradientViewController: UIViewController {
let testImageView = AnimatedGradientImageView(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
// replace with your image name
guard let img = UIImage(named: "sampleImage") else {
fatalError("Could not load image!!!")
}
// set the image
testImageView.image = img
testImageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(testImageView)
// respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// size: 240 x 240
testImageView.widthAnchor.constraint(equalToConstant: 240.0),
testImageView.heightAnchor.constraint(equalToConstant: 240.0),
// centered
testImageView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
testImageView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
// tap anywhere in the view
let t = UITapGestureRecognizer(target: self, action: #selector(gotTap(_:)))
view.addGestureRecognizer(t)
}
#objc func gotTap(_ g: UITapGestureRecognizer) -> Void {
testImageView.reveal()
}
}
First, you will have to give the height constraint of the image from the storyboard and set the height to 0
and take the image height constraint into your file like below
#IBOutlet weak var imageHeight: NSLayoutConstraint!
Here is the animation code.
UIView.animate(withDuration: 2, delay: 2, options: [.transitionFlipFromBottom],
animations: {
self.image.frame = CGRect(x: self.image.frame.origin.x, y: self.image.frame.origin.y - 100, width: self.image.frame.size.width, height: 100)
self.imageHeight.constant = 100
self.image.layoutIfNeeded()
}, completion: nil)
Click here to see animation

iOS/Swift: Use UIView.animate to animate text getting added/removed from UITextView

I am adding and removing attributed text from a UITextView. I wish to use UIView.animate to add an animation to when text is appended to the text view and when that appended text is removed from the text view. So far, I have this, but it does not cause any noticeable animation on the text view:
UIView.animate(withDuration: 1, delay: 0, options: .CurveLinear, animations: {
self.view.layoutIfNeeded()
self.textView.attributedText = newAttributedText
}, completion: { finished in
print("Animation completed")
}
// prints "Animation completed", but no animation occurs
You cannot animate changing of text in that manner. There is a list of animatable properties of CALayer class and UIView class.
Set text cannot be animated by UIView.animate. Only changes like transparency, colors, shape, location, etc can be animated by the UIView.animate.
Here is an example of animating origin and transparency change with code looks like
self.textView.text = "test Animation"
self.textView.alpha = 0
UIView.animate(withDuration: 3, delay: 0, options: .curveLinear, animations: {
var frame = self.textView.frame
frame.origin.x = frame.origin.x + 50
frame.origin.y = frame.origin.y + 50
self.textView.frame = frame
self.textView.alpha = 1
}, completion: { finished in
print("Animation completed")
})
And animation looks like
Here is my solution (swift 3 ) :
let animator = UIViewPropertyAnimator(duration: 0.2, curve: .easeOut, animations: {
self.textView2.frame = self.textView2.frame.offsetBy(dx: 20 , dy: 25)
} )
animator.startAnimation()

Animate rectangle by change width and height

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;
}];
}

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
})
}

Is there a way to animate changing a UILabel's textAlignment?

I am using iOS 7 and I am trying to move a label that is centered off to the left of my view. Currently I am trying to do this by changing how my UILabel is aligned, and I am trying to animate it in the process. I am currently calling the following:
[UIView animateWithDuration:0.5
animations:^{
self.monthLabel.textAlignment = NSTextAlignmentLeft;
} completion:nil];
But this just jumps the label to the new alignment. Is there a way to animate this, or a better way to adjust the label to make this happen?
Set the UILabel frame size to exactly contain the text and center the UILabel in your view.
self.monthLabel.text = #"February";
[self.monthLabel sizeToFit];
self.monthLabel.center = parentView.center; // You may need to adjust the y position
Then set the alignment which should not affect the layout since there will be no extra space.
self.monthLabel.textAlignment = NSTextAlignmentLeft;
Next, animate the UILabel frame size so it slides over where you want it.
[UIView animateWithDuration:0.5
animations:^{
CGRect frame = self.monthLabel.frame;
frame.origin.x = 10;
self.monthLabel.frame = frame;
} completion:nil];
Is your label multiline? An animation like this: http://img62.imageshack.us/img62/9693/t7hx.png?
If so, then there's a couple of alternatives.
Multiline label
Option 1:
Instead of a traditional UIView animation, try a UIView transition. The text wont slide to the left, but instead it will fade nicely to the new position.
[UIView transitionWithView:self.monthLabel
duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.monthLabel.textAlignment = NSTextAlignmentLeft;
} completion:NO];
Option 2:
You can manually work out where the new lines will appear, then create a separate UILabel for each line of text then animate the frames. This is obviously more work, but will give that desired slide animation.
Single line label
Instead of animating the textAlignment, make the label the same size of the string it contains with [self.monthLabel sizeToFit], then manually work out the framing and the centering. Then just animate the frame, the same as option 2 on a multiline label.
you can animate the FRAME, not the textAlignment.
Do a [UILabel sizeToFit] on your label if you want to get rid of any "padding" on the frame, then you can animate the frame in your animation block to move it around as you desire
CGRect frameLabel = self.monthLabel.frame;
[UIView animateWithDuration:0.5
animations:^{
frameLabel.origin.x -= 100; // e.g. move it left by 100
self.monthLabel.frame = frameLabel;
} completion:nil];
you just need to animate your label frame.
As here completion block is there in it you should change the frame again. and make the loop going.
[UIView animateWithDuration:0.5
animations:^{
// Change the frame
} completion:^{[UIView animateWithDuration:0.5
animations:^{
// Change the frame
} completion:^{
// repeat the proccess
}]
}];
This has served me well
import Foundation
import UIKit
class AnimatableMultilineLabel: UIView {
enum Alignment {
case left
case right
}
public var textAlignment: Alignment = .left {
didSet {
layout()
}
}
public var font: UIFont? = nil {
didSet {
setNeedsLayout()
}
}
public var textColor: UIColor? = nil {
didSet {
setNeedsLayout()
}
}
public var lines: [String] = [] {
didSet {
for label in labels {
label.removeFromSuperview()
}
labels = []
setNeedsLayout()
}
}
private var labels: [UILabel] = []
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
isUserInteractionEnabled = false
}
override func layoutSubviews() {
super.layoutSubviews()
layout()
}
private func layout() {
autocreateLabels()
var yPosition: CGFloat = 0.0
for label in labels {
let size = label.sizeThatFits(bounds.size)
let minX = textAlignment == .left ? 0.0 : bounds.width - size.width
let frame = CGRect(x: minX, y: yPosition, width: size.width, height: size.height)
label.frame = frame
yPosition = frame.maxY
}
}
private func autocreateLabels() {
if labels.count != lines.count {
for text in lines {
let label = UILabel()
label.font = font
label.textColor = textColor
label.text = text
addSubview(label)
labels.append(label)
}
}
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
autocreateLabels()
var height: CGFloat = 0.0
for label in labels {
height = label.sizeThatFits(size).height
}
return CGSize(width: size.width, height: height)
}
}
Then you should be able to
UIView.animate(withDuration: 0.5, animations: {
multilineLabel.textAlignment = .right
})

Resources