简体   繁体   中英

SwiftUI DatePicker breaks sheet dismiss?

Scenario:

  • RootScreen presents DateScreen modally though .sheet
  • DateScreen has a DatePicker with CompactDatePickerStyle() and a button to dismiss the modal
  • User opens the DatePicker
  • User taps the DatePicker to bring up the NumPad for manual keyboard input
  • User presses the button to dismiss the modal

SwiftUI will think the .sheet got dismissed, but in reality, only the DatePicker's modal got dismissed.

Minimum code example:

struct DateScreen: View {
    @Binding var isPresented: Bool
    @State var date: Date = Date()

    var body: some View {
        NavigationView {
            VStack {
                DatePicker("", selection: $date, displayedComponents: [.hourAndMinute])
                    .datePickerStyle(CompactDatePickerStyle())
            }
            .navigationBarItems(leading: Button("Dismiss") {
                isPresented = false
            })
        }
    }
}

@main
struct Main: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    @State var isPresenting: Bool = false

    var body: some Scene {
        WindowGroup {
            Button("Present modal", action: {
                isPresenting = true
            })
                .sheet(isPresented: $isPresenting, content: {
                    DateScreen(isPresented: $isPresenting)
                })
        }
    }
}

Gif showing the broken behavior:

Note, if the user doesn't open the NumPad, it seems to work well.

表现出破坏行为的 Gif

The only workaround I found is to ignore SwiftUI and go back to UIKit to do the dismissal.

Instead of isPresented = false I have to do UIApplication.shared.windows.first?.rootViewController?.dismiss(animated: true) .

For iOS 15 this works to dismiss the sheet and doesn't generate the warning:

'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead

code:

UIApplication.shared.connectedScenes
    .filter({$0.activationState == .foregroundActive})
    .compactMap({$0 as? UIWindowScene})
    .first?
    .windows
    .first { $0.isKeyWindow }?
    .rootViewController?
    .dismiss(animated: true)

This is problem of provided code - the State is in Scene instead of view - state is not designed to update scene. The correct SwiftUI solution is to move everything from scene to a view and have only one root view there, ie.

Tested with Xcode 13.4 / iOS 15.5

演示

@main
struct Main: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
           ContentView()    // << window root view, the one !!
        }
    }
}


struct ContentView: View {
    @State var isPresenting: Bool = false

    var body: some View {
        Button("Present modal", action: {
            isPresenting = true
        })
        .sheet(isPresented: $isPresenting, content: {
            DateScreen(isPresented: $isPresenting)
        })

    }
}

// no more changes needed

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM