Reputation: 1039
I want to transition between two views in SwiftUI using a horizontal sliding transition. The problem is, that I also want to update the target view once the data is fetched from the network.
Down below is a minimal example of the transition. When the button on the first view is pressed, the transition and the (placeholder) background work is started. For better visibility, the transition is slowed down. In the second view, I have a ProgressView
which should be replaced with the actual view (here a Text
view) once the data is available.
@main
struct MyApp: App {
@StateObject private var viewModel = ViewModel()
@State private var push = false
private let transition = AnyTransition.asymmetric(insertion: .move(edge: .trailing),
removal: .move(edge: .leading))
private let transitionAnimation = Animation.easeOut(duration: 3)
var body: some Scene {
WindowGroup {
if !push {
VStack(alignment: .center) {
HStack { Spacer() }
Spacer()
Button(action: {
push.toggle()
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.2) {
DispatchQueue.main.sync {
self.viewModel.someText = "Test ### Test ### Test ### Test ### Test ### Test ###"
}
}
}){
Text("Go")
}
Spacer()
}
.background(Color.green)
.transition(transition)
.animation(transitionAnimation)
} else {
SecondView()
.transition(transition)
.animation(transitionAnimation)
.environmentObject(viewModel)
}
}
}
}
final class ViewModel: NSObject, ObservableObject {
@Published var someText: String = ""
}
struct SecondView: View {
@EnvironmentObject var viewModel: ViewModel
var body: some View {
VStack(alignment: .center) {
HStack { Spacer() }
Spacer()
if(viewModel.someText.isEmpty) {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
} else {
Text(viewModel.someText)
}
Spacer()
}.background(Color.red)
}
}
The problem now is that the Text
view is not included in the view transition. I would expect that it is moving along with the transition (= the red area), but instead, it just appears at the location where it would be after the transition. The following animation shows this effect.
Is it possible to achieve the animation of the Text
view? To be clear: I know that in this case I could just always display the Text
view because the string is empty at the beginning. As I stated earlier, this is a massively simplified version of my actual view hierarchy. I don't see a way of leaving out the if-else
statement or use the hidden
modifier.
Upvotes: 3
Views: 1235
Reputation: 523
Few things have to be done to make it work properly.
First, Text
should exist in the views hierarchy even when the someText
is empty. You can wrap it and the progress indicator into ZStack
and control the text visibility with .opacity
instead of the if/else
statement.
Second, the animations can be applied conditionally depending on which value has changed. You should apply transitionAnimation
only when the push
variable is changed:
.animation(transitionAnimation, value: push)
There is a catch though: it won't work from the if/else
branches on the same variable, because each of the .animation
statements will exist only for one value of the push
variable, and the changes in it won't be noticed. To fix that if/else
should be wrapped into a Group
, and animation should be applied to it.
Here is a full solution:
@main
struct MyApp: App {
@StateObject private var viewModel = ViewModel()
@State private var push = false
private let transition = AnyTransition.asymmetric(insertion: .move(edge: .trailing),
removal: .move(edge: .leading))
private let transitionAnimation = Animation.easeOut(duration: 3)
var body: some Scene {
WindowGroup {
Group {
if !push {
VStack(alignment: .center) {
HStack { Spacer() }
Spacer()
Button(action: {
push.toggle()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.viewModel.someText = "Test ### Test ### Test ### Test ### Test ### Test ###"
}
}){
Text("Go")
}
Spacer()
}
.background(Color.green)
.transition(transition)
} else {
SecondView()
.transition(transition)
.environmentObject(viewModel)
}
}
.animation(transitionAnimation, value: push)
}
}
}
final class ViewModel: NSObject, ObservableObject {
@Published var someText: String = ""
}
struct SecondView: View {
@EnvironmentObject var viewModel: ViewModel
var body: some View {
VStack(alignment: .center) {
HStack { Spacer() }
Spacer()
ZStack {
if(viewModel.someText.isEmpty) {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
}
Text(viewModel.someText)
.opacity(viewModel.someText.isEmpty ? 0.0 : 1.0)
}
Spacer()
}.background(Color.red)
}
}
Upvotes: 2