In the next version of Streaks, we're adding the ability to record notes to tasks. As part of this update we wanted to include this in our "what's new" screen.
To give it a more realistic feel, I wanted this information to be typed in the same way notes are recorded in the app:
This particular screen uses SwiftUI, but off the top of my head, I wasn't sure of the easiest to create this typing effect. Instead of using Google or Stack Overflow, I thought this might be a good chance to try ChatGPT:
Write SwiftUI code that animates a string of text to look type it is being typed in real time
To which ChatGPT replied:
Here's an example of how to animate a string of text to look like it's being typed in real time using SwiftUI:
import SwiftUI
struct TypingText: View {
@State private var text = ""
let fullText: String
let typingInterval: Double
init(text: String, typingInterval: Double = 0.1) {
self.fullText = text
self.typingInterval = typingInterval
}
var body: some View {
Text(text)
.onAppear {
self.typeText()
}
}
func typeText() {
var currentIndex = text.endIndex
Timer.scheduledTimer(withTimeInterval: typingInterval, repeats: true) { timer in
if currentIndex < self.fullText.endIndex {
currentIndex = self.fullText.index(after: currentIndex)
self.text = String(self.fullText[..<currentIndex])
} else {
timer.invalidate()
}
}
}
}
In this example,TypingText
takes in atext
parameter, which is the full text that you want to animate, and an optionaltypingInterval
parameter, which determines the interval at which the next character is added. ThetypeText()
function uses aTimer
to incrementally add characters to thetext
state property and update the view.
Great! That saved me a bunch of time. This is what it looks like when run:
I wanted it to feel more realistic though, which would be by using different intervals between each letter.
Change it so the typing interval is randomised so it feels more real.
Right away, the response:
Here's an example of how to randomize the typing interval so that the animation feels more like someone typing the text in real time:
import SwiftUI
struct TypingText: View {
@State private var text = ""
let fullText: String
let typingIntervalRange: ClosedRange<Double>
init(text: String, typingIntervalRange: ClosedRange<Double> = 0.05...0.15) {
self.fullText = text
self.typingIntervalRange = typingIntervalRange
}
var body: some View {
Text(text)
.onAppear {
self.typeText()
}
}
func typeText() {
var currentIndex = text.endIndex
Timer.scheduledTimer(withTimeInterval: randomTypingInterval(), repeats: true) { timer in
if currentIndex < self.fullText.endIndex {
currentIndex = self.fullText.index(after: currentIndex)
self.text = String(self.fullText[..<currentIndex])
timer.invalidate()
self.typeText()
} else {
timer.invalidate()
}
}
}
func randomTypingInterval() -> Double {
let interval = Double.random(in: typingIntervalRange)
return interval
}
}
In this example,TypingText
takes in atext
parameter, which is the full text that you want to animate, and an optionaltypingIntervalRange
parameter, which determines the range of possible typing intervals. TherandomTypingInterval()
function generates a random value within this range to use as the typing interval for each character. ThetypeText()
function uses aTimer
to incrementally add characters to thetext
state property and update the view. Each time the timer is invalidated and restarted, it uses a new random typing interval.
Here's what it looks like:
The change is subtle, but it feels a bit more realistic. You can easily adjust the range so the effect is more pronounced.
I didn't love how it generated the timers - seemed a bit wasteful to create a repeating timer, only to cancel it every time, so I made a few changes.
Here's the final version I wrote. This version includes a typingDelay
parameter, so the typing doesn't begin until after that initial delay.
public struct TypingText: View {
@State private var typedText = ""
var text: String
var typingSpeed: ClosedRange<TimeInterval>
var typingDelay: TimeInterval
public init(
text: String,
typingSpeed: ClosedRange<TimeInterval> = 0.1...0.2,
typingDelay: TimeInterval = 0
) {
self.text = text
self.typingSpeed = typingSpeed
self.typingDelay = typingDelay
}
public var body: some View {
Text(typedText)
.onAppear {
if typingDelay > 0 {
Timer.scheduledTimer(withTimeInterval: typingDelay, repeats: false) { _ in
self.startTyping(0)
}
}
else {
self.startTyping(0)
}
}
}
private func startTyping(_ index: Int) {
Timer.scheduledTimer(
withTimeInterval: TimeInterval.random(in: typingSpeed),
repeats: false
) { timer in
guard index < self.text.count else {
return
}
self.typedText.append(
self.text[self.text.index(self.text.startIndex, offsetBy: index)]
)
startTyping(index + 1)
}
}
}
This version of the code can be seen in the first animation in this article.
On balance, using ChatGPT was a very useful way to mock up some working code. While some tweaks were needed for my specific application, it was ultimately a huge time saver.