Determining SwiftUI Sheet Presentation Animation Completion

108 Views Asked by At

I would like to find out when the sheet presentation animation concludes in SwiftUI. While in UIKit, this is easily achieved with the present(_:animated:completion:) function. The completion block in the following SwiftUI code seems to trigger only when the sheet is dismissed. Is there a method to determine when the animation for presenting the sheet is completed in SwiftUI?

import SwiftUI

struct SwiftUIView29: View {
    @State var show = false
    
    var body: some View {
        Button("show") {
            withAnimation {
                show.toggle()
            } completion: {
                print("completion!")
            }
        }
        .sheet(isPresented: $show) {
            Text("SheetView")
        }
    }
}

#Preview {
    SwiftUIView29()
}
2

There are 2 best solutions below

0
son On

There is an official way, it's:

@Environment(.isPresented) private var isPresented

However, I've tried, and it does not work as expected. The property does not change at all.

I think there is a workaround for this circumstance, you could try this:

@State private var isPresented: Bool = false

var body: some View {
    Button("show") {
        isPresented = true
        show.toggle()
    }
    .sheet(isPresented: $show) {
        Text("SheetView")
            .onAppear {
                isPresented = false
            }
    }
    .onChange(of: isPresented) {
        print($0)
    }
}
0
devPau On

Using property wrapper you can achieve what you desire adding a new State var that tracks when the text has appeared, like @State var textDidAppear = false

Then you can create a View that holds your Text, with a Binding var to update the values once the view has appeared, as described below:

struct CustomTextView: View {
    
    @Binding var textDidAppear: Bool
    
    var body: some View {
        Text("SheetView")
            .onAppear {
                textDidAppear = true
            }
        
            .onDisappear {
                textDidAppear = false
            }
    }
}

And finally, to present the sheet you can pass this State variable as a parameter, and add a onChange to your main View, just in case you want to react to those changes and perform some work:

.sheet(isPresented: $show) {
    CustomTextView(textDidAppear: $textDidAppear)
}

.onChange(of: textDidAppear) {
    print(textDidAppear)
}

All the code below:

struct ContentView: View {

    @State var show = false
    @State var textDidAppear = false
    
    var body: some View {
        Button("show") {
            withAnimation {
                show.toggle()
            } completion: {
                print("completion!")
            }
        }
        .sheet(isPresented: $show) {
            CustomTextView(textDidAppear: $textDidAppear)
        }
        
        .onChange(of: textDidAppear) {
            print(textDidAppear)
        }
    }
}

struct CustomTextView: View {
    
    @Binding var textDidAppear: Bool
    
    var body: some View {
        Text("SheetView")
            .onAppear {
                textDidAppear = true
            }
        
            .onDisappear {
                textDidAppear = false
            }
    }
}