Last active
December 2, 2025 10:03
-
-
Save simonbs/3ef87ba29e6eca34b919cbdc5f346aae to your computer and use it in GitHub Desktop.
Is there a reliable way in SwiftUI to show a sheet without animation so it is visible immediately when the parent view appears?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| struct ContentView: View { | |
| @State private var isModalPresented = false | |
| var body: some View { | |
| VStack { | |
| Button { | |
| isModalPresented = true | |
| } label: { | |
| Text("Present Modal") | |
| } | |
| } | |
| .padding() | |
| .sheet(isPresented: $isModalPresented) { | |
| ModalView() | |
| } | |
| } | |
| } | |
| struct ModalView: View { | |
| @Environment(\.dismiss) private var dismiss | |
| @State private var isSheetPresented = true | |
| var body: some View { | |
| NavigationView { | |
| VStack { | |
| Text("I'm presented modally.") | |
| Spacer() | |
| } | |
| .padding() | |
| .frame(maxWidth: .infinity, maxHeight: .infinity) | |
| .background(Color(uiColor: .systemGroupedBackground)) | |
| .navigationTitle("Modal") | |
| .navigationBarTitleDisplayMode(.inline) | |
| .toolbar { | |
| ToolbarItem(placement: .topBarLeading) { | |
| Button { | |
| dismiss() | |
| } label: { | |
| Label("Close", systemImage: "xmark") | |
| } | |
| } | |
| } | |
| // How can the sheet be shown without animation, so it's already visible when the modal opens? ππ€ | |
| .sheet(isPresented: $isSheetPresented) { | |
| Text("I am a sheet. How can I be shown without animation, so I am already visible when the modal opens?") | |
| .padding() | |
| .presentationDetents([.medium]) | |
| .presentationBackgroundInteraction(.enabled) | |
| .presentationDragIndicator(.hidden) | |
| .interactiveDismissDisabled(true) | |
| } | |
| } | |
| } | |
| } |
fatbobman
commented
Dec 1, 2025
Combine two sheets: a main modal sheet with animation, and a second (secondary) sheet without animation. However, it needs a delay for the transition to finish; apparently, onAppear executes when the transition of the parent sheet ends.
struct ModalViewDemo: View {
@State private var isModalPresented = false
var body: some View {
VStack {
Button {
isModalPresented = true
} label: {
Text("Present Modal")
}
}
.padding()
.sheet(isPresented: $isModalPresented) {
ModalView()
}
}
}
struct ModalView: View {
@Environment(\.dismiss) private var dismiss
@State private var isSheetPresented = true
@State private var isLoading = true
var body: some View {
if isLoading {
ProgressView()
.task {
try? await Task.sleep(for: .seconds(0.5))
withAnimation(.smooth) {
isLoading = false
}
}
} else {
NavigationView {
VStack {
Text("I'm presented modally.")
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray.quinary)
.navigationTitle("Modal")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(role: .close) {
dismiss()
}
}
}
// How can the sheet be shown without animation, so it's already visible when the modal opens? ππ€
.sheet(isPresented: $isSheetPresented) {
Text("I am a sheet. How can I be shown without animation, so I am already visible when the modal opens?")
.padding()
.presentationDetents([.medium])
.presentationBackgroundInteraction(.enabled)
.presentationDragIndicator(.hidden)
.interactiveDismissDisabled(true)
}
.transaction { transaction in
transaction.disablesAnimations = true
}
}
}
}
}
#Preview {
ModalViewDemo()
}
Only one sheet
// MARK: Utils
fileprivate extension View {
func onFirstAppear(perform action: @escaping () -> Void) -> some View {
modifier(ViewFirstAppearModifier(perform: action))
}
}
fileprivate struct ViewFirstAppearModifier: ViewModifier {
@State private var didAppearBefore = false
private let action: () -> Void
init(perform action: @escaping () -> Void) {
self.action = action
}
func body(content: Content) -> some View {
content.onAppear {
guard !didAppearBefore else { return }
didAppearBefore = true
action()
}
}
}
// MARK: Demo
struct ModalViewDemo: View {
@State private var isModalPresented = false
var body: some View {
NavigationView {
VStack {
Text("I'm presented modally.")
Button("open sheet") {
isModalPresented = true
}
Spacer()
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.gray.quinary)
.navigationTitle("Modal")
.navigationBarTitleDisplayMode(.inline)
.sheet(isPresented: $isModalPresented) {
NavigationStack {
VStack {
Text("this is a modal sheet")
.padding()
.presentationDetents([.medium])
.presentationBackgroundInteraction(.enabled)
.presentationDragIndicator(.hidden)
.interactiveDismissDisabled(true)
}
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(role: .close) {
isModalPresented.toggle()
}
}
}
}
}
.onFirstAppear {
var transaction = Transaction(animation: .none)
transaction.disablesAnimations = true
withTransaction(transaction) {
isModalPresented = true
}
}
}
}
}
#Preview {
ModalViewDemo()
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment