Tools
Tools: Typewriter text effect in SwiftUI
2026-02-23
0 views
admin
Implementation description This article walks through implementing a typewriter-style text animation in SwiftUI using pure Swift and native SwiftUI APIs, no external dependencies. Minimum supported platforms: iOS 15.0+. You can jump directly to the code. A complete example repository is provided at the end. This article walks through implementing a typewriter-style effect animation in SwiftUI using pure Swift and native SwiftUI APIs — no external dependencies.
Minimum supported iOS version is 15 and till the latest one iOS 26. Many existing implementations rely on UIKit wrappers or just string modifications that affect text appearance and doesn’t provide desired animation that fit’s and doesn’t break layout around. This implementation supports: The key point of the animation effect is to keep a text on same positions aligned to any side and avoid text jumps during animation progress. Main component of the view is an a AttributedString that used with SwiftUI native Text component that allow full text customizations modifier. Animations starts with onAppear event on TypwriterText view. It starts with empty view without the text but the view already fill all space required for final text. The view iterates text by charactes and change .clear text color to initial text color provided for the text component. Until the full text is presented. The component provides letters appearance interval modification. How to use TypewriterText component You can provide any custom or system font and any color that will be animated during typweriter animation. The TypewriterText looks like this It uses AttributedText as main component for dynamic text appearence by changing it’s letters colors one by one. The usage of native comonents provides good level of performance and open to customizations and modifications. For example, you can add haptics on each letter appear by modifying startAnimation() adding haptic trigger in while cycle. You can check example Xcode project by the link. https://github.com/maltsevoff/TypewriterText Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK:
TypewriterText("Hello, world! Let's type it out!") .font(.system(.title2)) .foregroundStyle(Color.brown) TypewriterText("Hello, world! Let's type it out!") .font(.custom("Custom-Font", size: 24)) .foregroundStyle(Color.brown) Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
TypewriterText("Hello, world! Let's type it out!") .font(.system(.title2)) .foregroundStyle(Color.brown) TypewriterText("Hello, world! Let's type it out!") .font(.custom("Custom-Font", size: 24)) .foregroundStyle(Color.brown) CODE_BLOCK:
TypewriterText("Hello, world! Let's type it out!") .font(.system(.title2)) .foregroundStyle(Color.brown) TypewriterText("Hello, world! Let's type it out!") .font(.custom("Custom-Font", size: 24)) .foregroundStyle(Color.brown) CODE_BLOCK:
struct TypewriterText: View { let text: String let interval: TimeInterval @State private var attributedText: AttributedString = "" init(_ text: String, interval: TimeInterval = 0.06) { self.text = text self.interval = interval } var body: some View { Text(attributedText) .onAppear { startAnimation() } } @MainActor private func startAnimation() { Task { @MainActor in var base = AttributedString(text) let initialColor = base.foregroundColor base.foregroundColor = .clear attributedText = base var index = base.startIndex while index < base.endIndex { let nextIndex = base.index(afterCharacter: index) attributedText[index..<nextIndex].foregroundColor = initialColor index = nextIndex try? await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000)) } } }
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
struct TypewriterText: View { let text: String let interval: TimeInterval @State private var attributedText: AttributedString = "" init(_ text: String, interval: TimeInterval = 0.06) { self.text = text self.interval = interval } var body: some View { Text(attributedText) .onAppear { startAnimation() } } @MainActor private func startAnimation() { Task { @MainActor in var base = AttributedString(text) let initialColor = base.foregroundColor base.foregroundColor = .clear attributedText = base var index = base.startIndex while index < base.endIndex { let nextIndex = base.index(afterCharacter: index) attributedText[index..<nextIndex].foregroundColor = initialColor index = nextIndex try? await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000)) } } }
} CODE_BLOCK:
struct TypewriterText: View { let text: String let interval: TimeInterval @State private var attributedText: AttributedString = "" init(_ text: String, interval: TimeInterval = 0.06) { self.text = text self.interval = interval } var body: some View { Text(attributedText) .onAppear { startAnimation() } } @MainActor private func startAnimation() { Task { @MainActor in var base = AttributedString(text) let initialColor = base.foregroundColor base.foregroundColor = .clear attributedText = base var index = base.startIndex while index < base.endIndex { let nextIndex = base.index(afterCharacter: index) attributedText[index..<nextIndex].foregroundColor = initialColor index = nextIndex try? await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000)) } } }
} - Custom fonts
- Any font size
- Any font color
- Stable layout — the final text size is known in advance, so surrounding views remain unaffected
how-totutorialguidedev.toaigitgithub