SwiftUI: sheet will not present with Picker appeared - swift

It may be a bug of SwiftUI?
There is a Picker component and two buttons in the ContentView. The first button is simply changing the "show" var to true and the second button change the var with a delay. The sheet should be present by tapping the buttons. But I found that if the Picker is tapped at first and keep nothing selected then only the button with delay can present the sheet. The button without delay will cause an error:
[Presentation] Attempt to present <_TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView_: 0x14e80e400> on <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x14c819600> (from <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x14c819600>) which is already presenting <_UIContextMenuActionsOnlyViewController: 0x14d63be90>.
After that, the sheet will never be able to present again. Is that the only solution (adding a delay) to avoid this situation?
import SwiftUI
struct ContentView: View {
#State var show = false
#State var picked = "1"
let values = ["1", "2", "3"]
var body: some View {
VStack {
Picker(selection: $picked, label: Text("")) {
ForEach (values, id: \.self) { value in
Text(value)
}
}
Text("⬆️ Tap me but don't select")
.padding(.bottom, 120)
Text("1. Tap Picker")
Text("2. Tap show sheet button")
Button("show sheet") {
show = false
show = true
}
.padding([.top, .bottom], 20)
Button("show sheet with delay") {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
show = true
}
}
}
.padding()
.sheet(isPresented: $show, content: {
Button("close") {
show = false
}
})
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Related

Dismiss view and push to navigation stack SwiftUI

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)
)
}
}

SwiftUI: How to remove delay from dismissing a sheet?

I want to offset a view when a sheet comes up and set it back when the sheet gets dismissed. The code itself works, but I have a problem.
When the sheet first comes up, everything is running as expected. But when I dismiss the sheet, there is a ~ 1 second delay before the offset goes back to 0. (Running it in the actual simulator shows the same result)
How can I get rid of the delay or control the delay duration?
My code:
import SwiftUI
struct FirstView: View {
#State var showSheet: Bool = false
var body: some View {
Text("Hello, World!")
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet) {
LaggyView()
.presentationDetents([.medium, .large])
}
.offset(y: showSheet ? -300 : 0)
.animation(.default, value: showSheet)
}
}
struct LaggyView: View {
var body: some View {
Text("hello")
}
}
struct FirstView_Previews: PreviewProvider {
static var previews: some View {
FirstView()
}
}

SwiftUI keyboard-shortcut without modifier doesn't work in popover

In a View with a textfield I have a popover, activated by a button.
In that popover I want to listen to a keyboardShortcut without modifiers.
These keypresses arrive in the textfild of the parent view instead of the popover.
What can I do, to react on these in the popover?
struct ContentView: View {
#State var showPopOver = false
#State var text = ""
var body: some View {
VStack{
TextField("enter text here", text: $text)
Button("show popover"){ showPopOver = true }
.popover(isPresented: $showPopOver) {PopOverView(text: $text)} }
.keyboardShortcut("p")
.padding()
}
}
struct PopOverView: View {
#Environment(\.dismiss) var dismiss
#Binding var text: String
var body: some View {
VStack{
Text("my Popover")
Button("set Tom"){
text = "Tom"
dismiss()
}
.keyboardShortcut("t",modifiers: [])
//.keyboardShortcut("t") // like this it works
Button("set Frank"){
text = "Frank"
dismiss()
}
.keyboardShortcut("f",modifiers: [])
}
.padding()
}
}
KeyboardShortcuts with a modifier in the popover do work (commented out).

SwiftUI: Sheet gets dismissed immediately after being presented

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

How to resolve "Use of unresolved identifier 'PresentationLink'" error in swiftUI? [duplicate]

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)
})
}
}