I want to have a fullscreen SwiftUI View with a button in the Navigation Bar, which presents a SwiftUI Sheet above.
Unfortunately, the Compiler says: "Currently, only presenting a single sheet is supported.
The next sheet will be presented when the currently presented sheet gets dismissed."
This is my Code:
struct ContentView: View {
var body: some View {
EmptyView().fullScreenCover(isPresented: .constant(true), content: {
FullScreenView.init()
})
}
}
struct FullScreenView: View{
var body: some View {
NavigationView{
MasterView()
}.navigationViewStyle(DoubleColumnNavigationViewStyle())
}
}
struct MasterView: View {
#State private var showingSheet = false
var body: some View {
Form {
Section(header: Text("Header")) {
NavigationLink(destination: UIKitView()) { Text("Hey") }
}
}
.navigationBarItems(trailing:
HStack {
// First Try: Use a Button
Button("Plus"){
showingSheet = true
}.sheet(isPresented: $showingSheet){
AddContentView()
}
// Second Try: Use NavigationLink
NavigationLink(
destination: AddContentView(),
label: {
Image(systemName: "plus.square.fill")
})
})
}
}
The Problem
I want to show the SwiftUI View in Fullscreen, so I use fullScreenCover(...). With this first "Sheet", I cannot present a second sheet, my AddContentView() View. Is there any way how I can fix this? I really want to have this sheet above :(
Thanks for any help!!
Feel free to ask for other code or if there are ambiguities. :)
The error message says that the sheet cannot be displayed at the same time(Do not overlap sheets), so if you want to go to view and again to another view, you have to use a NavigationLink and only at the end .sheet()
.sheet(isPresented: $showingSheet){
AddContentView()
}
or fullScreenCover()
.fullScreenCover(isPresented: $showingSheet){
AddContentView()
}
Edited: Sheet is not overlapped twice in this code.
import SwiftUI
struct ContentView: View {
#State private var showingSheet = false
var body: some View {
NavigationView{
Form {
Section(header: Text("Header")) {
NavigationLink(destination: EmptyView()) { Text("Hey") }
}
}
.navigationBarItems(trailing:
HStack {
// First Try: Use a Button
Button("Plus"){
showingSheet = true
}.sheet(isPresented: $showingSheet){
EmptyView()
}
// Second Try: Use NavigationLink
NavigationLink(
destination: EmptyView(),
label: {
Image(systemName: "plus.square.fill")
})
})
}.navigationViewStyle(DoubleColumnNavigationViewStyle())
}
}
p.s. fullScreenCover() belongs to the sheet
Related
I have my entire application wrapped in a NavigationView and am trying to duplicate the transition in the brief video listed below. Based on what I am seeing, it looks like they present a fullScreenCover, and when a link is pressed it dismisses the fullScreenCover and pushes whatever was tapped onto the navigation stack once the dismiss has completed.
Example video
I currently have this...
#Environment(\.dismiss) var dismiss
ScrollView {
VStack {
ForEach(viewModel.searchResults, id: \.self) { bottle in
NavigationLink(destination: BottleDetailView(bottle: bottle)) {
BottleCell(bottle: bottle) <-- tapping this would dismiss fullscreenCover and push this NavigationLink into the NavigationStack of my app
}
}
}
}
I have tried embedding another navigation view in the fullscreenCover but that was not even close to duplicating the transition above. How can I duplicate this?
You can make use of a programmatically controllable NavigationStack. In the fullscreenCover first dismiss, then wait for it to vanish, then set the navigation destination.
Here is a full example:
struct ContentView: View {
#State private var search = ""
#State private var showSheet = false
// programmatically controllable Navigation Stack
#State private var path = [Int]()
var body: some View {
NavigationStack(path: $path) {
VStack {
searchField
.disabled(true)
.onTapGesture {
showSheet = true
}
Spacer()
Text("Other stuff")
Spacer()
}
.padding()
.navigationTitle("Find something")
.fullScreenCover(isPresented: $showSheet) {
fullscreenSheet
}
// this defines the destination(s) for the programatically activated navigation stack
.navigationDestination(for: Int.self) { value in
Text("Detail View for Result \(value)")
}
}
}
var fullscreenSheet: some View {
VStack(alignment: .leading, spacing: 30) {
HStack {
searchField
Button("Cancel") { showSheet = false }
}
// dummy search results
ForEach(1..<6) { result in
Button("Result \(result) >") {
// dismiss sheet
showSheet = false
// wait and trigger navigation
Task {
try await Task.sleep(for: .seconds(0.1))
self.path = [result]
}
}
}
Spacer()
}
.padding()
}
var searchField: some View {
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.white)
TextField("", text: $search,
prompt: Text("What are you looking for?")
.foregroundColor(.white)
)
}
.padding()
.background(
Capsule().fill(.gray)
)
}
}
I understand its new, but this seems like pretty basic functionality that is not here. When implementing a .searchable in the new iOS 15, it would seem that a NavigationLink does not work, at all.
Ideally, the searchable would produce a filtered list with a ForEach and for each item, a Nav Link could take you to another view based on your selection. The ForEach and list works and looks beautiful, it just wont take you anywhere.
Watching WWDC21, they talk an awful lot about .searchable, but very little demonstration/example is given..
Here is a simple example, with no ForEach loop, that shows it does not work at all.. Am I missing something?
Any insight appreciated:
import SwiftUI
struct ContentView: View {
#State private var term = ""
var body: some View {
NavigationView {
Text("Hello, world!")
.padding()
}
.searchable(text: $term) {
NavigationLink(destination: SecondView()) {
Text("Go to second view")
}
}
}
}
struct SecondView: View {
var body: some View {
Text("Goodbye!")
.padding()
}
}
NavigationLink must always be inside NavigationView, no matter what. If you want to put it outside, like inside .searchable, you should use programmatic navigation with isActive.
struct ContentView: View {
#State private var term = ""
#State private var isPresenting = false
var body: some View {
NavigationView {
/// NavigationView must only contain 1 view
VStack {
Text("Hello, world!")
.padding()
/// invisible NavigationLink
NavigationLink(destination: SecondView(), isActive: $isPresenting) { EmptyView()}
}
}
.searchable(text: $term) {
Button { isPresenting = true } label: {
Text("Go to second view")
}
}
}
}
struct SecondView: View {
var body: some View {
Text("Goodbye!")
.padding()
}
}
Result:
In the following sample code, a button is placed on a (form-) section header, which will toggle a sheet whenever it is pressed. The sheet has a list of elements to show.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack{
Form{
Section(header: headerView()) {
Text("Some Text")
}
}
}
}
}
struct headerView: View {
#State var showSheet = false
var body: some View {
Button(action: { self.showSheet.toggle()}){
HStack{
Spacer()
Image(systemName: "pencil.and.ellipsis.rectangle")
Text("View Sheet")
}
}.sheet(isPresented: $showSheet) {sheetView()}
}
}
struct sheetView: View {
#Environment(\.presentationMode) private var presentationMode
var body: some View {
NavigationView{
VStack(alignment: .leading) {
List() {
Text("List element 1")
Text("List element 2")
Text("List element 3")
Text("List element 4")
}
}
.navigationBarTitle(Text("Logs"), displayMode: .inline)
.navigationBarItems(leading: EditButton(), trailing: Button(action: {self.presentationMode.wrappedValue.dismiss()}) { Text("Done").bold()})
}
}
}
This has been working totally fine on iOS 13. However, in iOS 14 as you can see in my screenshot bellow it renders fully corrupted:
List elements have strange font size, color and are in upper-case (most important one!)
NavigationBar Buttons are greyed and in upper-case
NavigationBar title is in upper-case
The corrupted behaviour stays as long as you don't touch the screen. When you touch the screen and drag the sheet a little bit down then the list appearance will get corrected. If you do the same to the NavigationBar, it will then also be rendered correctly.
Is anybody also facing this issue? Any known fixes?
This looks like a bug. The possible workaround is to move sheet out of Form.
Tested with Xcode 12.0 / iOS 14.
struct ContentView: View {
#State var showSheet = false
var body: some View {
VStack{
Form{
Section(header:
headerView(showSheet: $showSheet)
) {
Text("Some Text")
}
}
}.sheet(isPresented: $showSheet) {sheetView()}
}
}
struct headerView: View {
#Binding var showSheet: Bool
var body: some View {
Button(action: { self.showSheet.toggle()}){
HStack{
Spacer()
Image(systemName: "pencil.and.ellipsis.rectangle")
Text("View Sheet")
}
}
}
}
I embedded a button on on the NavigationBar.
I'm try to make button to open a new View called DetailView
I try to use NavigationLink but it does't work inside a button.
import SwiftUI
struct ContentView: View {
#ObservedObject var dm: DataManager
#State var isAddPresented = false
var body: some View {
NavigationView {
HStack {
List () {
ForEach (dm.storage) { data in
StileCella(dm2: data)
}
}
.navigationBarTitle("Lista Rubrica")
.navigationBarItems(trailing: Button(action: {
self.isAddPresented = true
// Load here the DetailView??? How??
DetailView()
}) {
Text("Button")
})
}
}
}
}
struct DetailView: View {
var body: some View {
VStack(alignment: .center) {
Text("CIAO").bold()
Spacer()
Image(systemName: "star")
.resizable()
}
}
}
You just need to add a sheet modifier to your view, which presents your view depending on the value of isAddPresented, just like this:
struct ContentView: View {
#State var isAddPresented = false
var body: some View {
NavigationView {
List(dm.storage){ data in
StileCella(dm2: data)
}
.navigationBarTitle("Lista Rubrica")
.navigationBarItems(trailing: Button("Button") {
self.isAddPresented = true
})
} .sheet(isPresented: $isAddPresented,
onDismiss: { self.isAddPresented = false }) {
DetailView()
}
}
}
The important bit is to remember to set isAddPresented back to false in on dismiss to prevent it form presenting again.
If you want to open a new view just like we used to open through storyboard other than sheet, you can update the code in the following way:
import SwiftUI
struct ContentView: View {
#ObservedObject var dm: DataManager
#State var isAddPresented = false
var body: some View {
NavigationView {
HStack {
List () {
ForEach (dm.storage) { data in
StileCella(dm2: data)
}
}
.navigationBarTitle("Lista Rubrica")
.navigationBarItems(leading:
NavigationLink(destination: DetailView()) {
Text("Button")
})
}
}
}
}
struct DetailView: View {
var body: some View {
VStack(alignment: .center) {
Text("CIAO").bold()
Spacer()
Image(systemName: "star")
.resizable()
}
}
}
Instead of button, simply add NavigationLink inside navigationBarItems. This would do the trick! I wrote the complete for guidance but main change point is, I used
.navigationBarItems(leading:
NavigationLink(destination: DetailView()) {
Text("Button")
})
instead of:
.navigationBarItems(trailing: Button(action: {
self.isAddPresented = true
// Load here the DetailView??? How??
DetailView()
}) {
Text("Button")
})
I am attempting to dismiss a modal view presented via a .sheet in SwiftUI - called by a Button which is within a NavigationViews navigationBarItems, as per below:
struct ModalView : View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button(action: {
self.presentationMode.value.dismiss()
}, label: { Text("Save")})
}
}
struct ContentView : View {
#State var showModal: Bool = false
var body: some View {
NavigationView {
Text("test")
.navigationBarTitle(Text("Navigation Title Text"))
.navigationBarItems(trailing:
Button(action: {
self.showModal = true
}, label: { Text("Add") })
.sheet(isPresented: $showModal, content: { ModalView() })
)
}
}
}
The modal does not dismiss when the Save button is tapped, it just remains on screen. The only way to get rid of it is swiping down on the modal.
Printing the value of self.presentationMode.value always shows false so it seems to think that it hasn't been presented.
This only happens when it is presented from the NavigationView. Take that out and it works fine.
Am I missing something here, or is this a beta issue?
You need to move the .sheet outside the Button.
NavigationView {
Text("test")
.navigationBarTitle(Text("Navigation Title Text"))
.navigationBarItems(trailing:
Button("Add") {
self.showModal = true
}
)
.sheet(isPresented: $showModal, content: { ModalView() })
}
You can even move it outside the NavigationView closure.
NavigationView {
Text("test")
.navigationBarTitle(Text("Navigation Title Text"))
.navigationBarItems(trailing:
Button("Add") { self.showModal = true }
)
}
.sheet(isPresented: $showModal, content: { ModalView() })
Notice you can also simplify the Button call if you have a simple text button.
The solution is not readily apparent in the documentation and most tutorials opt for simple solutions. But I really wanted a button in the NavigationBar of the sheet that would dismiss the sheet. Here is the solution in six steps:
Set the DetailView to not show.
Add a button to set the DetailView to show.
Call the .sheet(isPresented modifier to display the sheet.
Wrap the view that will appear in the sheet in a NavigationView because we want to display a .navigationBarItem button.
PresentationMode is required to dismiss the sheet view.
Add a button to the NavBar and call the dismiss method.
import SwiftUI
struct ContentView: View {
// 1
#State private var showingDetail = false
var body: some View {
VStack {
Text("Hello, world!")
.padding()
Button("Show Detail") {
showingDetail = true // 2
}
// 3
.sheet(isPresented: $showingDetail) {
// 4
NavigationView {
DetailView()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct DetailView: View {
// 5
#Environment(\.presentationMode) var presentationMode
var body: some View {
Text("Detail View!")
// 6
.navigationBarItems(leading: Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "x.circle")
.font(.headline)
.foregroundColor(.accentColor)
})
}
}