Crafting a Typewriter Text Animation in SwiftUI
Peter Yaacoub •
Introduction
Text animations can significantly enhance the user experience of an app, adding a touch of dynamism and interactivity. One particularly engaging effect is the typewriter animation, where text appears character by character as if being typed in real-time. For instance, this animation is particularly in vogue with AI chat platforms. In fact, I’ve implemented this very animation in my app Catzumi’s onboarding process, creating a retro engaging and memorable first impression for new users.
In this article, we’ll explore how to implement this effect in SwiftUI, creating a reusable AnimatedText view that brings your text to life.
Understanding the Concept
Before diving into the code, let’s break down the core idea behind the typewriter animation:
- We start with an empty string and gradually reveal each character.
- We use AttributedString to control the visibility of characters.
- We employ a recursive function with a delay to create the typing effect.
Using AttributeString instead of adding each character one by one helps avoid layout issues.
Code Implementation
Let’s walk through the implementation of our AnimatedText view.
import SwiftUI
struct AnimatedText: View {
// MARK: - Inits
init(_ text: Binding<String>) {
self._text = text
var attributedText = AttributedString(text.wrappedValue)
attributedText.foregroundColor = .clear
self._attributedText = State(initialValue: attributedText)
}
// MARK: - Properties (Private)
@Binding private var text: String
@State private var attributedText: AttributedString
// MARK: - Properties (View)
var body: some View {
Text(attributedText)
.onAppear { animateText() }
.onChange(of: text) { animateText() }
}
// MARK: - Methods (Private)
private func animateText(at position: Int = 0) {
if position <= text.count {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
let stringStart = String(text.prefix(position))
let stringEnd = String(text.suffix(text.count - position))
let attributedTextStart = AttributedString(stringStart)
var attributedTextEnd = AttributedString(stringEnd)
attributedTextEnd.foregroundColor = .clear
attributedText = attributedTextStart + attributedTextEnd
animateText(at: position + 1)
}
} else {
attributedText = AttributedString(text)
}
}
}
Initialization
init(_ text: Binding<String>) {
self._text = text
var attributedText = AttributedString(text.wrappedValue)
attributedText.foregroundColor = .clear
self._attributedText = State(initialValue: attributedText)
}
We initialize our view with a binding to a String
. We create an AttributedString
from this text and initially set its color to clear, making it invisible.
View Body
var body: some View {
Text(attributedText)
.onAppear { animateText() }
.onChange(of: text) { animateText() }
}
The view displays the attributedText
. We start the animation when the view appears and whenever the text changes.
Animation Logic
private func animateText(at position: Int = 0) {
if position <= text.count {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.075) {
let stringStart = String(text.prefix(position))
let stringEnd = String(text.suffix(text.count - position))
let attributedTextStart = AttributedString(stringStart)
var attributedTextEnd = AttributedString(stringEnd)
attributedTextEnd.foregroundColor = .clear
attributedText = attributedTextStart + attributedTextEnd
animateText(at: position + 1)
}
} else {
attributedText = AttributedString(text)
}
}
This recursive function is the heart of our animation:
- We split the text into two parts: visible and invisible.
- We create attributed strings for both parts, setting the color of the end part to clear.
- We combine these parts and update our attributedText.
- We call the function again with the next position after a short delay of
0.075
seconds. - When we reach the end of the text, we make the entire text visible.
Using the AnimatedText View
To use this view in your SwiftUI app, you can simply do:
struct ContentView: View {
// MARK: - Properties (Private)
@State private var text = "Hello, SwiftUI!"
// MARK: - Properties (View)
var body: some View {
AnimatedText($text)
}
}
Benefits
Implementing this typewriter animation in Catzumi’s onboarding process proved to have several benefits.
- Improved Engagement: The dynamic text keeps users interested and encourages them to read through the entire onboarding process.
- Paced Information Delivery: By revealing text gradually, we ensure users have time to absorb each piece of information before moving on to the next.
- Personality: The typing effect adds a touch of personality to the app, making it feel more interactive and alive.
- Reduced Overwhelm: For apps with complex features, this technique encourages the developer to break down information into digestible chunks, reducing the risk of overwhelming new users.
Conclusion
The AnimatedText
view provides a sleek, customizable typewriter animation for your SwiftUI apps. By leveraging SwiftUI’s declarative syntax and the power of AttributedString
, we’ve created a reusable component that can add a touch of dynamism to any text in your app.
Remember, while animations can enhance user experience, they should be used judiciously. Consider the context and frequency of use to ensure they add value rather than distraction. For instance, using the typewriter effect only in the onboarding process can serve a clear purpose in introducing new users to the app’s concept and features.