Search code examples
swiftuiasync-awaitswift-concurrency

SwiftUI: NavigationLink doesn't show animation if setting after background task


I am trying to use the new async/await concurrency code with a SwiftUI view to do some basic asynchronous data loading after a button click, to show a spinner view, and on completion, transition to a new page. I use an enum to mark the state of the view, and use that with NavigationLink to move forward to a new view. This works, and shows the 'loading' text view for a second before 'pushing' to the next view, but there is no animation when the new view is pushed. This is the code I am using:

import SwiftUI

enum ContinueActionState: Int {
    case ready = 0
    case showProgressView = 1
    case pushToNextPage = 2
    case readingError = 3
    case error = 4
}


struct ContentView: View {
    
    @State var actionState: ContinueActionState? = .ready
    
    var body: some View {
        
        NavigationView {
            VStack {
            
                Text("Test the Button!")
                    
                if (actionState == .showProgressView) {
                    ProgressView("Loading Data")
                    
                } else if (actionState == .error || actionState == .readingError) {
                    Text("Error in loading something")
                }
                else {
                    NavigationLink(destination: DetailPageView(), tag: .pushToNextPage, selection: $actionState) {
                        
                        Button(action: {
                            
                            Task {
                                print("on buttonClick isMain =\(Thread.isMainThread)")
                                self.actionState = .showProgressView
                                await self.startProcessingData()
                                //self.actionState = .pushToNextPage // animation works if only this is used
                            }
                        }) {
                            Text("Continue")
                        }
                        .tint(.blue)
                        .buttonStyle(.borderedProminent)
                        .buttonBorderShape(.roundedRectangle(radius: 5))
                        .controlSize(.large)
                         
                    }
                }
            }.navigationTitle("Test Async")
        }
    }
    
    func startProcessingData() async {
        
        Task.detached {
            print("startProcessingData isMain =\(Thread.isMainThread)")
            try await Task.sleep(nanoseconds: 1_000_000_000)

            //await MainActor.run {
                self.actionState = .pushToNextPage
            //}
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct DetailPageView: View {
    var body: some View {
        Text("Detail page")
    }
}

IF I just forego the async call and just set to .pushToNextPage state immediately on button click, the animation works fine.

Is there any way to get this to work with a smooth animation, after processing stuff in a background queue task is complete?


Solution

  • if you move the NavigationLink out of the if statements it works fine for me:

            NavigationView {
                VStack {
                    
                    Text("Test the Button!")
                    
                    NavigationLink(destination: DetailPageView(), tag: .pushToNextPage, selection: $actionState) { Text("") } // here
                    
                    if (actionState == .showProgressView) {
                        ProgressView("Loading Data")
                        
                    } else if (actionState == .error || actionState == .readingError) {
                        Text("Error in loading something")
                    }
                    else {
                        
                        Button(action: {
                            Task {
                                print("on buttonClick isMain =\(Thread.isMainThread)")
                                self.actionState = .showProgressView
                                await self.startProcessingData()
                            }
                        }) {
                            Text("Continue")
                        }
                        .tint(.blue)
                        .buttonStyle(.borderedProminent)
                        .buttonBorderShape(.roundedRectangle(radius: 5))
                        .controlSize(.large)
                        
                    }
                }.navigationTitle("Test Async")
            }