-
Notifications
You must be signed in to change notification settings - Fork 87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Broken behavior when the view is changing during animation #55
Comments
Hi @ln-12, the problem here is due to the struct SecondView: View {
@EnvironmentObject var viewModel: ViewModel
var body: some View {
VStack(alignment: .center) {
HStack { Spacer() }
Spacer()
if(viewModel.someText.isEmpty) {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
}
Text(viewModel.someText)
Spacer()
}
.background(Color.red)
}
} |
Yeah, I already noticed that. Unfortunately, my real UI is much complexer (I have multiple else branches), so that this won't work. So you think this is a SwiftUI problem and not specific to your library, right? |
Well... basically yes. It depends on how SwiftUI manages the @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)
}
} As you can see the bug still occurs: We should try to fix the issue in this example (without the navigation stack) and then see if we can improve the navigation stack by integrating the fix directly into it. |
I couldn't figure it out after some hours of debugging so I posted a question on StackOverflow. Hopefully, someone can figure it out as it would make your library perfect for my use case :) |
I got the following working by using the @main
struct TransitionTestApp: App {
@StateObject private var viewModel = ViewModel()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(viewModel)
}
}
}
struct ContentView: View {
@EnvironmentObject var viewModel: ViewModel
private let transition = AnyTransition.asymmetric(insertion: .move(edge: .top),
removal: .move(edge: .bottom))
private let transitionAnimation = Animation.easeOut(duration: 3)
var body: some View {
VStack {
TabView(selection: $viewModel.selectedTab) {
FirstView()
.tag(0)
.transition(transition)
.animation(transitionAnimation)
SecondView()
.tag(1)
.transition(transition)
.animation(transitionAnimation)
}
.onAppear(perform: {
UIScrollView.appearance().bounces = false
})
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.transition(transition)
.animation(transitionAnimation)
}
}
}
struct FirstView: View {
@EnvironmentObject var viewModel: ViewModel
var body: some View {
VStack(alignment: .center) {
HStack { Spacer() }
Spacer()
Button(action: {
withAnimation {
viewModel.selectedTab += 1
}
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)
}
}
final class ViewModel: NSObject, ObservableObject {
@Published var someText: String = ""
@Published var selectedTab = 0
}
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)
}
} |
Using the SwiftUIPager library, it works as intended! I don't know why the content is looking correct in this library, but not in yours. Maybe you can have a look what they are doing different. import SwiftUI
import SwiftUIPager
@main
struct TransitionTestApp: App {
@StateObject private var viewModel = ViewModel()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(viewModel)
}
}
}
struct ContentView: View {
@EnvironmentObject var viewModel: ViewModel
var items = Array(0..<2)
var body: some View {
Pager(
page: viewModel.page,
data: items,
id: \.self
) { index in
if(index == 0) {
FirstView()
} else if(index == 1) {
SecondView()
}
}
.bounces(false)
.draggingAnimation(.standard)
}
}
struct FirstView: View {
@EnvironmentObject var viewModel: ViewModel
private let transitionAnimation = Animation.easeOut(duration: 3)
var body: some View {
VStack(alignment: .center) {
HStack { Spacer() }
Spacer()
Button(action: {
withAnimation(transitionAnimation) {
viewModel.page.update(.next)
}
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)
}
}
final class ViewModel: NSObject, ObservableObject {
@Published var someText: String = ""
@Published var page: Page = .first()
}
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)
}
} With this approach you also get the "swipe to go back" behavior discussed in issue #8 |
I did a quick implementation with your library here: https://github.com/ln-12/swiftui-navigation-stack/tree/animation-fix With this approach, you have to set your NavigationStackView(navigationStack: self.navigationStack) {}
.onAppear(perform: {
self.navigationStack.push(RootView())
}) With that, I get the right push animations in all cases and also have the "swipe to go back" gesture. It's far from perfect, other animations might not work, but it is exactly want I was looking for. Maybe you find a way to properly integrate it or it might just help someone else who is looking for this functionality. You can also tell me how you would integrate it so that I can create a pull request :) |
First of all: thanks for that awesome library! It's so much better that what is currently available in SwiftUI!
Now to describe the actual problem: when I am navigating between static views, everything works just fine. But as soon as a view is changing, it is not animated anymore. This does not happen when the text of a
Text()
view is changing for example. It only occurs when a view is replaced during the transition, like when showing aProgressView()
and switching to theText()
once the content is available. The error looks like this during the animation:The
Text()
should be fully contained within the red area, but as you can see, it is located where it should be after the transition. I setup a minimal working example below. The transition is slowed down to visualize the error. TheasyncAfter
operation stands for some network request.Is it possible to fix this? I can only load the content, after the user taps the button which starts the transition. If not, the only idea which comes to my mind is that any view updates could be delayed until the transition is finished.
The text was updated successfully, but these errors were encountered: