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:
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:
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:
Related Topics
Checkbox Uitableview with Different Sections
Change Uipopoverview Background + Arrow Color
Iad Interstitials Not Showing Consistently? and Not at All on the Simulator
Navigationlink Inside Lazyvgrid Cycles All Entries on Back, Swiftui
iOS Swift Avplayer Inside Uiview How to Make It Work
Swiftui Animation and Subsequent Reverse Animation to Original State
Swift Save Viewcontroller State Using Coder Beyond App Close
Nsexceptionallowsinsecurehttploads Not Working for Ip Addresses
Swift Calling Setnavigationbarhidden But View Wont Move to Top
Non-Main Bundle File as Alert Sound
Using Avaudioplayer to Play Remote Mp3 File in Swift
Passing Data Between Interface Controllers in Watchkit
Why Does It Take Such a Long Time for UI to Be Updated from Background Thread
Changing Constraint When Keyboard Appears - Swift
Writing JSON File Programmatically Swift