0

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?

Z S
  • 7,039
  • 12
  • 53
  • 105

1 Answers1

1

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")
        }
ChrisR
  • 9,523
  • 1
  • 8
  • 26
  • Works great now! Any idea why it made the difference? – Z S May 26 '22 at 00:11
  • 1
    The `NavigationLink` itself creates its own "tap action" from its label (even if using the selection init). So if the label is a button itself, you end up with 2 buttons somehow, which creates problems. look here for more details: https://stackoverflow.com/questions/57130866/how-to-show-navigationlink-as-a-button-in-swiftui – ChrisR May 26 '22 at 08:14