12

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?

lehn0058
  • 19,977
  • 15
  • 69
  • 109

5 Answers5

12

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];
bbarnhart
  • 6,620
  • 1
  • 40
  • 60
5

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.

SomeGuy
  • 9,670
  • 3
  • 32
  • 35
1

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];
CSmith
  • 13,318
  • 3
  • 39
  • 42
  • I like this approach! I do need to have my label centered (right now it is showing the name of a month above a calendar collection view) so I will play with this and initially set the center of the label instead of trying to use textAlignment – lehn0058 Oct 08 '13 at 15:32
  • if you want to set variable "frameLabel" in the block animation you need set variable with access prefix "__block CGRect frameLabel" ..., because you will change value variable frameLabel in the block. – horkavlna Feb 16 '14 at 18:11
0

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

}];
user2826529
  • 179
  • 2
  • I would, but I have padding on either side of the label as my text will change depending on the month a user has selected. – lehn0058 Oct 08 '13 at 15:29
0

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
})
hfossli
  • 22,616
  • 10
  • 116
  • 130
  • Interesting. How do you decide which lines to break up? – agibson007 Feb 28 '18 at 23:58
  • All lines were non-breaking so I knew up front I could declare it this way. A smarter solution would take an attributed string and properly animate the line breaks automatically. – hfossli Mar 01 '18 at 11:13