I want to make Text View with dots that increase and decrease over time.
Now it looks like that:

but it looks twitchy and smudged. And also the text itself is shifted every time. How to get rid of it?
Here is my code:
struct TextViewTest: View {
@State var dotsSwitcher = 0
var body: some View {
Text("Loading\(dots)")
.animation(.easeOut(duration: 0.1), value: dotsSwitcher)
.onReceive(Timer.publish(every: 1, on: .main, in: .common)
.autoconnect()) { _ in dotsAnimation() }
.onAppear(perform: dotsAnimation)
}
var dots: String {
switch dotsSwitcher {
case 1: return "."
case 2: return ".."
case 3: return "..."
default: return ""
}
}
func dotsAnimation() {
withAnimation {
dotsSwitcher = 0
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
withAnimation {
dotsSwitcher = 1
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
withAnimation {
dotsSwitcher = 2
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
withAnimation {
dotsSwitcher = 3
}
}
}
}
I modified the view so that the number of dots does not change, I only change their transparency
struct LoadingText: View {
var text: String
var color: Color = .black
@State var dotsCount = 0
var body: some View {
HStack(alignment: .bottom, spacing: 0) {
Text(text)
.foregroundColor(color) +
Text(".")
.foregroundColor(dotsCount > 0 ? color : .clear) +
Text(".")
.foregroundColor(dotsCount > 1 ? color : .clear) +
Text(".")
.foregroundColor(dotsCount > 2 ? color : .clear)
}
.animation(.easeOut(duration: 0.2), value: dotsCount)
.onReceive(Timer.publish(every: 1, on: .main, in: .common)
.autoconnect()) { _ in dotsAnimation() }
.onAppear(perform: dotsAnimation)
}
func dotsAnimation() {
withAnimation {
dotsCount = 0
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
withAnimation {
dotsCount = 1
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
withAnimation {
dotsCount = 2
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.9) {
withAnimation {
dotsCount = 3
}
}
}
}
Try sliding transition on text.
Also, you can do it without using GCD, here is a complete solution for text based animated loading indicator with some options:
struct AnimatedTextLoadingIndicatorView: View {
@State private var text: String = ""
@State private var dots: String = ""
private let now: Date = .now
private static let every: TimeInterval = 0.2
private let timer = Timer.publish(every: Self.every, on: .main, in: .common).autoconnect()
private let characters = Array("⣾⣽⣻⢿⡿⣟⣯⣷")
var body: some View {
VStack {
Text(text)
.font(.largeTitle)
.transition(.slide)
Text("Loading" + dots)
.font(.footnote)
.transition(.slide)
}
.onReceive(timer) { time in
let tick: Int = .init(time.timeIntervalSince(now) / Self.every) - 1
let index: Int = tick % characters.count
text = "\(characters[index])"
let count: Int = tick % 4
dots = String(Array(repeating: ".", count: count))
}
.onDisappear {
timer.upstream.connect().cancel()
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With