SwiftUI DatePicker breaks sheet dismiss? - ios

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.

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

Related

A popover in NavigationLink does not work properly

When you tap Button2 below, the popover closes immediately for the first time only.
Here is the reproducible code.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink("Button1", destination: ChildView())
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct ChildView: View {
#State var isPresented = false
var body: some View {
Button("Button2") {
isPresented = true
}
.popover(isPresented: $isPresented) {
Text("Sheet")
}
}
}
Xcode12.4
iPad Pro 11inch / iOS14.0
Screen Capture Video
https://gist.github.com/shtnkgm/d8ba240f588d3e73ea13c1ff2e6cb1b5#gistcomment-3640543

How it is possible to dismiss a view from a subtracted subview in SwiftUI

Whenever my code gets too big, SwiftUI starts acting weird and generates an error:
"The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions"
So I started breaking up my code into Extracted Subviews, one of the problems I came across is how to dismiss a view from a subtracted subview.
Example: we have here LoginContentView this view contains a button when the button is clicked it will show the next view UsersOnlineView.
struct LoginContentView: View {
#State var showUsersOnlineView = false
var body: some View {
Button(action: {
self.showUsersOnlineView = true
}) {
Text("Show the next view")
}
.fullScreenCover(isPresented: $showUsersOnlineView, content: {
UsersOnlineView()
})
}
On the other hand, we have a button that is extracted to subview, to dismiss the modal and go back to the original view:
import SwiftUI
struct UsersOnlineView: View {
var body: some View {
ZStack {
VStack {
CloseViewButton()
}
}
}
}
struct CloseViewButton: View {
var body: some View {
Button(action: {
// Close the Modal
}) {
Text("Close the view")
}
}
}
Give the sbview the state property that defines if the view is shown.
struct CloseViewButton: View {
#Binding var showView: Bool
var body: some View {
Button(
ShowView = false
}) {
Text("Close the view")
}
}
}
When you use the sub view give it the property
CloseButtonView(showView: $showOnlineView)
To allow the sub view to change the isShown property it needs to get a binding.
On the presentation mode. I think this only works with Swiftui presentations like sheet and alert.
The simplest solution for this scenario is to use presentationMode environment variable:
struct CloseViewButton: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Text("Close the view")
}
}
}
Tested with Xcode 12.1 / iOS 14.1

SwiftUI Rotate screen make modal don’t longer dismiss itself

I have a bug on SwiftUI, when I rotate my device the modal don't longer dismiss, the problem here is that only happen on the device on the simulator works well also on my iPad.
import SwiftUI
struct modalView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button(action:{
self.presentationMode.wrappedValue.dismiss()
}){
Text("close")
}
}
}
struct ContentView: View {
#State var showModal = false
var body: some View {
Button(action: {
showModal.toggle()
}){
Text("modal")
}
.sheet(isPresented: self.$showModal, content: {
modalView()
})
}
}
[Bug on my device][1]
i have this problem since iOS 13
im currently on iOS 14.2 beta
and Xcode 12 GM
[1]: https://twitter.com/MisaelLandero/status/1306953785651142656?s=20
Try to use something like this:
struct ContentView: View {
#State private var showModal = false
// If you are getting the "can only present once" issue, add this here.
// Fixes the problem, but not sure why; feel free to edit/explain below.
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button(action: {
self.showModal = true
}) {
Text("Show modal")
}.sheet(isPresented: self.$showModal) {
ModalView()
}
}
}
struct ModalView: View {
#Environment(\.presentationMode) private var presentationMode
var body: some View {
Group {
Text("Modal view")
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Dismiss")
}
}
}
}
I Found the problem, I was using a condition to show two different navigations views, that break the dismiss modal
if self.sizeClass == .compact{
NavigationViewForiPhone()
} else {
NavigationViewForiPad()
}
looks like thats the problem cuz my view is reload

SWIFTUI: Can't dismiss Sheet after changing ScreenSize classes

I toggle a sheet in SwiftUI with the following Button
Button(action: {
self.statusPopoverIsShown.toggle()
})
So the following sheet appears
.sheet(isPresented: self.$popoverIsShown) {
RandomSheet(popoverIsShown: self.$popoverIsShown)
}
Then I have a button inside the RandomSheetto dismiss the sheet (sets the popoverIsShown to false). Everything works fine.
But when I start using the app in splitscreen or somehow change the sizeclass SwiftUI transforms the sheet to a fullscreen iPhone-like sheet and the dismiss button/the binding does not work anymore.
Is there any solution to avoid this and keep the binding stable?
The following works with any size class changes. Tested with Xcode 12 / iOS 14
struct TestSheet: View {
#State private var popoverIsShown = false
var body: some View {
Button("Show Sheet") {
self.popoverIsShown = true
}
.sheet(isPresented: self.$popoverIsShown) {
RandomSheet(popoverIsShown: self.$popoverIsShown)
}
}
}
struct RandomSheet: View {
#Binding var popoverIsShown: Bool
var body: some View {
Button("Close") { self.popoverIsShown = false }
}
}

Is there a way to dismiss a modal view without animation in SwiftUI?

Is there a way to dismiss a modal view without animation in SwiftUI?
I want to dismiss a modal without the dismiss animation because I want to navigate from the modal view to a new SwiftUI View using a view router. Everything is working, except for the transition animation from the modal view to the new full-screen view. I followed that tutorial to create a view router: Tutorial
I'm using that code snippet to present the modal view:
struct ContentView: View {
#State private var showModal = false
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button(action: {
self.showModal = true
}) {
Text("Show modal")
}.sheet(isPresented: self.$showModal) {
ModalView()
}
}
}
struct ModalView: View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
Group {
Text("Modal view")
Button(action: {
self.viewRouter.currentPage = "New View"
}) {
Text("Dismiss")
}
}
}
}
Source: Answer by #M Reza Farahani
Here is a solution in Swift: Swift solution
Did not fully test this since I dont have the ViewRouter
You should move the
#Environment(\.presentationMode) var presentationMode
the ModalView and add
self.presentationMode.wrappedValue.dismiss()
to the button action in that ModalView
Edit:
After I added
.animation(.none)
To the ModalView it worked for me
Alright thats one ugly a** comment so putting it here:
struct ModalView: View {
// #EnvironmentObject var viewRouter: ViewRouter
#Environment(\.presentationMode) var presentationMode
var body: some View {
Group {
Text("Modal view")
Button(action: {
// self.viewRouter.currentPage = "New View"
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Dismiss")
}
}
.animation(.none)
}
}

Resources