My code works, but I'm not sure if this is the proper way of doing MVVM. I have some lines of text which I want to appear line by line, with a sliding animation. One line per second.
This is my ViewModel:
@MainActor
class MyViewModel: ObservableObject {
@Published var lines: [String] = []
init() {
let myLines = [ "This", "is", "just", "a", "test" ]
for (index, line) in myLines.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index)) {
withAnimation {
self.lines.append(line)
}
}
}
}
}
And this is my ContentView:
import SwiftUI
struct ContentView: View {
@StateObject var vm = MyViewModel()
var body: some View {
VStack {
ForEach(vm.lines, id: \.self) { line in
Text(line)
.transition(.move(edge: .leading))
}
}
.padding()
}
}
#Preview {
ContentView()
}
Somehow I'm not liking the idea of delaying the population of lines in the ViewModel. Maybe I want to use this array for something else that doesn't require them to appear one by one. At the same time if I move the animation to the ContentView I have to write some ugly code in .onAppear to populate a @State-var with a delay, instead of using vm.lines in my ForEach-loop.
Thoughts on this please?
One way to stagger the appearance of the text would be to apply an offset which is animated with a delay.
In order that the text container (the
VStack
) occupies the minimum possible width and the text slides in from its side, the animated part can be shown in an overlay and aGeometryReader
can be used to measure the width of the container. The footprint for the container is established by showing the same text, but hidden.When doing it this way, the hardest part is actually controlling the height, because you (presumably) don't want to reserve space for text that is not yet visible. One way to solve this is to apply negative padding to the content that follows. A
GeometryReader
can be used here too, to get the height of the text block.The following example shows the text appearing in much the same way as your original post, but it does not use transitions and the view model no longer has to append the lines one by one. The
.onAppear
updates are for demo purposes only, in real use I would expect the model to be updated in some other way:If the text were to slide in from the side of the screen, instead of from the side of the container, then the code can be simplified. A hidden footprint is not needed and the animation can be performed on the lines of text without needing to use an overlay:
EDIT Another variation would be to reveal each line of text by changing the frame width from 0 to the required width, with animation. The footprint is determined by showing the text hidden, then the visible text is shown as an overlay. It works best if the speed of reveal is dependent on the length of the text: