Animating Strings Fading In/Out in Swift

Animating strings fading in/out in Swift

You can use transition(with:...) to do an animation. In this case, fading the word ipsum into green. E.g. in Swift 3 and later:

let range = (textView.text as NSString).range(of: "ipsum")
if range.location == NSNotFound { return }

let string = textView.attributedText.mutableCopy() as! NSMutableAttributedString
string.addAttribute(.foregroundColor, value: UIColor.green, range: range)

UIView.transition(with: textView, duration: 1.0, options: .transitionCrossDissolve, animations: {
self.textView.attributedText = string
})

Originally, you also asked about having the text grow and shrink during this animation and that’s more complicated. But you can search for the text, find the selectionRects, take snapshots of these views, and animate their transform. For example:

func growAndShrink(_ searchText: String) {
let beginning = textView.beginningOfDocument

guard
let string = textView.text,
let range = string.range(of: searchText),
let start = textView.position(from: beginning, offset: string.distance(from: string.startIndex, to: range.lowerBound)),
let end = textView.position(from: beginning, offset: string.distance(from: string.startIndex, to: range.upperBound)),
let textRange = textView.textRange(from: start, to: end)
else {
return
}

textView.selectionRects(for: textRange)
.forEach { selectionRect in
guard let snapshotView = textView.resizableSnapshotView(from: selectionRect.rect, afterScreenUpdates: false, withCapInsets: .zero) else { return }

snapshotView.frame = view.convert(selectionRect.rect, from: textView)
view.addSubview(snapshotView)

UIView.animate(withDuration: 1, delay: 0, options: .autoreverse, animations: {
snapshotView.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}, completion: { _ in
snapshotView.removeFromSuperview()
})
}
}

And

growAndShrink("consectetaur cillium”)

Will result in:

grow

If you are animating just the color of the text, you may want to fade it to clear before fading it to the desired color (making it "pop" a little more), you could use the completion block:

func animateColor(of searchText: String) {
let range = (textView.text as NSString).range(of: searchText)
if range.location == NSNotFound { return }

let string = textView.attributedText.mutableCopy() as! NSMutableAttributedString
string.addAttribute(.foregroundColor, value: UIColor.clear, range: range)

UIView.transition(with: textView, duration: 0.25, options: .transitionCrossDissolve, animations: {
self.textView.attributedText = string
}, completion: { _ in
string.addAttribute(.foregroundColor, value: UIColor.red, range: range)
UIView.transition(with: self.textView, duration: 0.25, options: .transitionCrossDissolve, animations: {
self.textView.attributedText = string
})
})
}

Resulting in:

red

For previous versions of Swift, see prior revision of this answer.

Fade in/out label with strings from array

The issue is caused by the for loop you are using there. You can't get the desired behavior in that way. for loop won't wait for your label animation to complete before starting the next iteration. Instead of that, you can make a recursive call from the method itself after each animation cycle completes.

You need to declare a variable in your class and implement the method like below:

Update for Swift 5 and Above:

var iterator = 1;

override func viewDidLoad() {

super.viewDidLoad()
self.lblAnime!.text = "Welcome"
setOverlayTitle()
}

func setOverlayTitle() {
let hello: [String] = ["Bon Jour", "GUTEN\nMORGEN", "BONJOUR", "HOLA", "안녕하세요", "BUENOS DÍAS", "BUONGIORNO", "早安", "おはよう", "गुड मॉर्निंग"]

UIView.animate(withDuration: 1.0, delay: 2.0, options: UIView.AnimationOptions.curveEaseOut, animations: {

self.lblAnime!.alpha = 0.0
}, completion: { (finished: Bool) -> Void in

self.lblAnime!.text = hello[self.iterator]

// Fade in
UIView.animate(withDuration: 1.0, delay: 0.0, options: UIView.AnimationOptions.curveEaseIn, animations: {

self.lblAnime!.alpha = 1.0
}, completion: { (finished: Bool) -> Void in

self.iterator += 1
if self.iterator < hello.count {
self.setOverlayTitle();
}
})
})
}

Original Answer (Older versions of Swift):

var iterator = 1;

override func viewDidLoad()
{

super.viewDidLoad()
self.lblAnime!.text = "Welcome"
setOverlayTitle()
}

func setOverlayTitle()
{

var hello: [String] = ["Bon Jour", "GUTEN\nMORGEN", "BONJOUR", "HOLA", "안녕하세요", "BUENOS DÍAS", "BUONGIORNO", "早安", "おはよう", "गुड मॉर्निंग"]

UIView.animateWithDuration(1.0, delay: 2.0, options: UIViewAnimationOptions.CurveEaseOut, animations:
{
self.lblAnime!.alpha = 0.0
},
completion:
{(finished: Bool) -> Void in
println(self.iterator)
self.lblAnime!.text = hello[self.iterator]

// Fade in
UIView.animateWithDuration(1.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseIn, animations:
{
self.lblAnime!.alpha = 1.0
},
completion:
{(finished: Bool) -> Void in
self.iterator++

if self.iterator < hello.count
{
self.setOverlayTitle();
}
})
})
}

Make a simple fade in animation in Swift?

The problem is that you're trying start the animation too early in the view controller's lifecycle. In viewDidLoad, the view has just been created, and hasn't yet been added to the view hierarchy, so attempting to animate one of its subviews at this point produces bad results.

What you really should be doing is continuing to set the alpha of the view in viewDidLoad (or where you create your views), and then waiting for the viewDidAppear: method to be called. At this point, you can start your animations without any issue.

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

UIView.animate(withDuration: 1.5) {
self.myFirstLabel.alpha = 1.0
self.myFirstButton.alpha = 1.0
self.mySecondButton.alpha = 1.0
}
}

Fading an array of strings in and out

You need a pair of nested animations, one to handle the fade in and the other to handle the fade out. You can call the second in the completion block of the first.

Then you need to change the message and then animate the new message, which is simply calling the animation pair again. Here is one way:

class ViewController: UIViewController {

@IBOutlet weak var label: UILabel!

let theMessages=["Message 1","Message 2","The third message","The final message"]
var messageIndex=0;

override func viewDidLoad() {
super.viewDidLoad()

self.label.text=""
self.label.alpha=0;
self.animateMessage()
}

func animateMessage() {
self.label.text=self.theMessages[self.messageIndex];
if (++self.messageIndex == self.theMessages.count) {
self.messageIndex=0;
}

UIView.animateWithDuration(2.0, delay: 0.5, options: .CurveEaseInOut, animations: { () -> Void in
self.label.alpha=1.0
}, completion: { (success) -> Void in
UIView.animateWithDuration(2.0, delay: 0.5, options: .CurveEaseInOut, animations: { () -> Void in
self.label.alpha=0
}, completion: { (success) -> Void in
self.animateMessage()
})
})
}
}

how can I fade in and out different texts on the same UILabel in Swift?

You can animate setting text on label like this

UIView.transition(with: label,
duration: 0.25,
options: [.transitionCrossDissolve],
animations: {
label.text = "Your Text"
}, completion: nil)

Fading in/out a UILabel

You should animate the alpha of the label.

UIView.animate(withDuration: 1, animations: {
disclaimerLabel.alpha = 0
}

SwiftUI - Change Text with fade animation

Okay, so I think I've found the best way to handle this myself: watching for changes via a binding's publisher, then manually fading out, changing text, and fading back.

The view looks like this:

struct FadingTextView: View {

@Binding var source: String
var transitionTime: Double

@State private var currentText: String? = nil
@State private var visible: Bool = false
private var publisher: AnyPublisher<[String.Element], Never> {
source
.publisher
.collect()
.eraseToAnyPublisher()
}

init(text: Binding<String>, totalTransitionTime: Double) {
self._source = text
self.transitionTime = totalTransitionTime / 3
}

private func update(_: Any) {
guard currentText != nil else {
currentText = source
DispatchQueue.main.asyncAfter(deadline: .now() + (transitionTime)) {
self.visible = true
}
return
}
guard source != currentText else { return }
self.visible = false
DispatchQueue.main.asyncAfter(deadline: .now() + (transitionTime)) {
self.currentText = source
DispatchQueue.main.asyncAfter(deadline: .now() + (transitionTime)) {
self.visible = true
}
}
}

var body: some View {
Text(currentText ?? "")
.opacity(visible ? 1 : 0)
.animation(.linear(duration: transitionTime))
.onReceive(publisher, perform: update(_:))
}

}

Then it can be used for arbitrary text changes like this:

struct TestView: View {

@State private var text: String = "Alpha"

private var values: [String] = ["Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf", "Hotel"]

private func updateText() {
self.text = values.randomElement()!
}

var body: some View {
VStack {
FadingTextView(text: $text, totalTransitionTime: 1.0)
Button("Change", action: updateText).padding()
}
}
}

And looks like this:

Result screen recording



Related Topics



Leave a reply



Submit