SwiftUI modal presentation works only once from navigationBarItems
Well, the issue is in bad layout (seems broken constrains) of navigation bar button after sheet has closed
It is clearly visible in view hierarchy debug:
Here is a fix (workaround of course, but safe, because even after issue be fixed it will continue working). The idea is not to fight with broken layout but just create another button, so layout engine itself remove old-bad button and add new one refreshing layout. The instrument for this is pretty known - use .id()
So modified code:
struct ContentView: View {
@State var showSheetView = false
@State private var navigationButtonID = UUID()
var body: some View {
NavigationView {
Group {
Text("Master")
Button(action: { self.showSheetView.toggle() }) {
Text("Button 1")
}
}
.navigationBarTitle("Main")
.navigationBarItems(trailing: Button(action: {
self.showSheetView.toggle()
}) {
Text("Button 2").bold() // recommend .padding(.vertical) here
}
.id(self.navigationButtonID)) // force new instance creation
}
.sheet(isPresented: $showSheetView) {
DetailView(isPresented: self.$showSheetView)
.onDisappear {
// update button id after sheet got closed
self.navigationButtonID = UUID()
}
}
}
}
SwiftUI button in navigation bar only works once
It's a known bug related to navigation bar items and not relegated to just sheets, it seems to affect any modal, and I've encountered it in IB just the same when using modal segues.
Unfortunately this issue is still present in 11.3 build, hopefully they get this fixed soon.
SwiftUI Sheets: Inconsistent sheet behaviour with navigationBarItems
I believe this is a bug. Though my answer isn't going to solve this problem, but can give you options to consider.
Out of curiosity, I did some debugging of the view hierarchy. With the current setup, initially the navigation bar button stays here (highlighted as blueish color):
Then you present the sheet and do the dismissal of the sheet accordingly. After the dismissal, the position of the navigation bar button isn't quite right. See:
The Text
of the button stays in the right position, but the bar button is displaced from where it should be.
But this displacement doesn't happen if you use NavigationLink
instead of .sheet
presentation.
Well, this issue is prevalent for the large
option of TitleDisplayMode
of navigation bar title, and it's the default for a quite long. But if the inline
option is used, the issue doesn't exist. See, with the inline
option, before and after the sheet dismissal the navigation bar button stays at the same place:
So that means, you now have two options to consider:
Use
NavigationLink
instead of sheet presentation. You can do this by placing the below code inside the outermostView
(in your case theVStack
):NavigationLink(destination: TestView(), isActive: self.$isModalShowing) {
EmptyView()
// no need to provide any other view as it will be triggered by the action
// of navigation bar button item which already provides its own view
}
Note: This
NavigationLink
option isn't tested with Xcode 11.3 or newer as navigation link seems to misbehave with this version. But works as expected upto Xcode 11.2.1. More on SwiftUI unable to navigate back and forth with navigationLink
Use the
inline
option of the navigation barTitleDisplayMode
as:.navigationBarTitle(Text("Home"), displayMode: .inline)
// you can get rid of the title "Home" with empty "" string though
Can a Modal Sheet have a Navigation Bar in SwiftUI?
You have to wrap your modal view in a NaviagtionView
like this
@State var isModalSheetShown: Bool = false
var body: some View {
VStack {
Text("Main")
}
.navigationBarItems(trailing: Button("Add",
action: { self.isModalSheetShown = true }))
.sheet(isPresented: $isModalSheetShown) {
NavigationView {
VStack {
Text("Modal")
}
.navigationBarItems(trailing: Button("Done",
action: {}))
}
}
}
SwiftUI - Navigation bar button not clickable after sheet has been presented
I think this happens because the presentationMode
is not inherited from the presenter view, so the presenter didn't know that the modal is already closed. You can fix this by adding presentationMode
to presenter, in this case to ContentView.
struct ContentView: View {
@Environment(\.presentationMode) var presentation
@State var showSheet = false
var body: some View {
NavigationView {
VStack {
Text("Test")
}.sheet(isPresented: self.$showSheet) {
ModalView()
}.navigationBarItems(trailing:
Button(action: {
self.showSheet = true
}) {
Text("SecondView")
}
)
}
}
}
Tested on Xcode 12.5.
Here is the full working
example.
Related Topics
How to Calculate Actual Font Point Size in iOS 7 (Not the Bounding Rectangle)
How to Convert Bytes to a Float Value in Swift
Alternative to Usernotificationcenterdelegate's Willpresent When App Is in Background
Switching Viewcontrollers with Uisegmentedcontrol in iOS5
Could Not Find Module for Target 'X86_64-Apple-Ios-Simulator'
How to Make Uiimagepickercontroller for Camera and Photo Library at the Same Time in Swift
How to Get Managedobjectcontext for Viewcontroller Other Than Getting It from Appdelegate
Xcode Cannot Run on the Selected Destination
Code Signing Error: Application Failed Codesign Verification
Xcode Version 6.1 (6A1030) - Apple MACh O-Linker Error - Building
Add a Watermark on Video After Merging Video and Audio Asset into One in Swift3 iOS
How to Hide 'Back' Button on Navigation Bar on Iphone
Xcode Exception Breakpoint Doesn't Print Details of the Exception Being Thrown
iOS 7 and Later: Set Status Bar Style Per View Controller
Open Uisplitviewcontroller to Master View Rather Than Detail