SwiftUI | How to hide cancel button in alert window? - ios

While testing new alert method in iOS 15(document), I just found a weird behavior that alert has.
Description
This is the codes:
import SwiftUI
struct ContentView: View {
#State var show = false
var body: some View {
VStack {
Button(action: {
show = true
}) {
Text("Alert")
}
}
.alert("alert", isPresented: $show) {
Button(action: {}) {
Text("button1")
}
Button("button2", role: .destructive, action: {})
}
}
}
As you can see, I added only 2 buttons but SwiftUI just adds Cancel button at the end of the buttons.
However, it doesn't happen when any of the buttons doesn't have a role.
import SwiftUI
struct ContentView: View {
#State var show = false
var body: some View {
VStack {
Button(action: {
show = true
}) {
Text("Alert")
}
}
.alert("alert", isPresented: $show) {
Button(action: {}) {
Text("button1")
}
Button(action: {}) {
Text("button1")
}
}
}
}
It's really weird. I even have no idea that it's intended by Apple or just a bug.
Question
Therefore my question is, how can I remove that cancel button after I added a button with a role. Is there any way to do this or I have to just accept it..
Any advice will be appreciated.

As role destructive button is for deletes user data, or performs an irreversible operation according to Apple docs.
In the alert view maybe because of you have a button with role .destructive so the alert default add a .cancel button. For closing the alert you should define a button with role .cancel
struct ContentView: View {
#State var show = false
var body: some View {
VStack {
Button(action: {
show = true
}) {
Text("Alert")
}
}
.alert("alert", isPresented: $show) {
Button(action: {}) {
Text("button1")
}
Button("button2", role: .cancel, action: {})
}
}
}

Related

SwiftUI dismissing .alert pops NavigationView

I've got the following sample code running on watchos 8+:
struct TestView: View {
#State private var showingAlert = false
var body: some View {
NavigationView {
NavigationLink {
Button(action: {
showingAlert = true
}, label: {
Text("Show dialog")
})
.alert("", isPresented: $showingAlert, actions: {
Button("refresh") {
}
})
} label: {
Text("Show detail")
}
}
}
}
It's a simple screen with a NavigationLinks that pushes into navigation a new view with a simple Button.
On button press I'm displaying an alert with a simple button. When the alert is dismissed, the detail view is also dismissed and the interface returns back to it's original state.
It looks like this:
I am wondering what am I missing here. How can I dismiss the alert but remain on the second view that shows the "Show dialog" button.
View separations and not needed NavigationView (watchOS only!).
So here is fixed code. Tested with Xcode 13.4 / watchOS 8.5
struct TestView: View {
var body: some View {
NavigationLink {
DestinationView()
} label: {
Text("Show detail")
}
}
struct DestinationView: View {
#State private var showingAlert = false
var body: some View {
Button(action: {
showingAlert = true
}, label: {
Text("Show dialog")
})
.alert("", isPresented: $showingAlert, actions: {
Button("refresh") {
}
})
}
}
}

Why is NavigationLink not working inside of a SwiftUI alert?

I have an alert that upon tapping I would like to return the user back to another view. The alert is showing, but why does it not navigate upon tapping?
VStack{
.alert("End of available content", isPresented: $model.alertIsPresented) {
NavigationLink(destination: SearchView()) {
Button("OK", role:.cancel) {}
}
}
}
Because NavigationLink needs to be inside of a hierarchy using NavigationView. An alert is a modal presented outside of that structure.
If you would like to programmatically navigate, you can use the isActive property of a NavigationLink within the NavigationView hierarchy.
struct ContentView : View {
#State private var alertIsPresented = false
#State private var navLinkActive = false
var body: some View {
NavigationView {
VStack{
Button("Present alert") {
alertIsPresented = true
}
.alert("End of available content", isPresented: $alertIsPresented) {
Button("Navigate") {
navLinkActive = true
}
}
NavigationLink(isActive: $navLinkActive, destination: { SearchView() }, label: {
EmptyView()
})
}
}
}
}
struct SearchView : View {
var body: some View {
Text("Search")
}
}

SwiftUI, weird NavigationLink behavior when working with actionSheet

I want to detect if the user meets the prerequisite first before I let him/her in. If the prerequisite is not met, the app will pop an actionSheet and show the user some ways to unlock the feature.
It works perfectly fine when I tap on the text. But when I tap on the blank place on the list. It just skip the Binding. And the weird thing is that in my actually project, the Binding becomes "true" even if I only set it to false.
Here's the question. Am I using the correct approach or did I miss anything? Or is this a bug?
Thank you.
struct ContentView: View {
#State var linkOne = false
#State var linkTwo = false
#State var linkThree = false
#State var actionOne = false
#State var actionTwo = false
#State var actionThree = false
var body: some View {
NavigationView{
List{
NavigationLink("Destination View One", destination: DestOneView(), isActive: self.$linkOne)
.actionSheet(isPresented: self.$actionOne) { () -> ActionSheet in
ActionSheet(title: Text("Hello"), message:Text("This is weird"), buttons: [ActionSheet.Button.cancel()])
}.onTapGesture {
self.actionOne = true
// self.linkOne = true
}
NavigationLink("Destination View Two", destination: DestTwoView(), isActive: self.$linkTwo)
.actionSheet(isPresented: self.$actionTwo) { () -> ActionSheet in
ActionSheet(title: Text("Hello"), message:Text("This is weird"), buttons: [ActionSheet.Button.cancel()])
}
.onTapGesture {
self.actionTwo = true
// self.linkTwo = true
}
NavigationLink("Destination View Three", destination: DestThreeView(), isActive: self.$linkThree)
.actionSheet(isPresented: self.$actionThree) { () -> ActionSheet in
ActionSheet(title: Text("Hello"), message:Text("This is weird"), buttons: [ActionSheet.Button.cancel()])
}
.onTapGesture {
self.actionThree = true
// self.linkThree = true
}
}
}
}
}
Three other views.
struct DestOneView: View {
var body: some View {
Text("First View")
}
}
struct DestTwoView: View {
var body: some View {
Text("Second View")
}
}
struct DestThreeView: View {
var body: some View {
Text("Third View")
}
}
Generally overriding gestures does not work well within the List. One of the solutions can be to use a Button to present a NavigationLink:
import SwiftUI
struct ContentView: View {
#State private var linkOne = false
...
var body: some View {
NavigationView {
List {
NavigationLink(destination: SomeView(), isActive: $linkOne) {
EmptyView()
}
Button(action: {
// here you can perform actions
self.linkOne = true
}, label: {
Text("Some text!")
})
Spacer()
}
}
}
}
When I came back and tested my the code. The solution didn't really work. May be because of the List's bug. People on another post said that the List in the new view only show once if the sheet is inside the List. So I only got an empty List in the new view when I tap the button in Xcode Version 11.5. For some reasion, if I use NavigationView, all contents are shrunk into the middle of the view instead of aligning to the top.
My work around is to set the Binding in .onAppear. It pops the actionSheet when the view loads. And then use the presentationMode method to return to the previous view.
#Environment(\.presentationMode) var presentationMode
.
.
.
ActionSheet.Button.default(Text("Dismiss"), action: {
self.presentationMode.wrappedValue.dismiss()}

Catch Tap gesture on Form in SwiftUI

struct ContentView: View {
#State var text = ""
var body: some View {
Form {
Button(action: {
print("Button pressed")
}) {
Text("Button")
}
}.simultaneousGesture(TapGesture().onEnded( { print("tap") }))
}
}
I need both Button' action and tap gesture on Form to be caught, but only print("tap") is executed. For a VStack works fine, but it seems the Form is a little bit special. Any idea ?
if you do it like this, you will get the button tap (but also the form tap). I don't know if this helps you.
#State var text = ""
var body: some View {
Form {
Button(action: {
}) {
Text("Button")
}.onTapGesture {
print("button")
}
}.onTapGesture {
print("form")
} }
}

How to present a View after tapping an ActionSheet.Button in SwiftUI?

I've been playing around with SwiftUI and got stumped on this simple thing. Basically, I'm trying to trigger a modal after tapping on an ActionSheet.Button. Here's my code so far:
struct SomePage: View {
#State var showSheet = false
var body: some View {
Button(action: {
self.showSheet = true
}) {
Text("Show ation sheet")
}.presentation(sheet)
}
private var sheet: ActionSheet? {
let button = ActionSheet.Button.default(Text("Button") {
self.showSheet = false
// what now??
}
let action = ActionSheet(title: Text("Title"),
message: nil,
buttons: [button])
return showSheet ? action : nil
}
// This is the modal I'm trying to present
// after tapping on the action sheet button
private var modal: Modal {
return Modal(SomePage())
}
}
I've tried adding a second presentation handler to the button and toggling a showModal property but obviously the debugger complained about attempting a second modal presentation while the first one was still being presented.
Does anybody have an idea on how to make this work?
You're not far off from it.
Add another #State to handle the presentation of the modal.
struct ContentView: View {
#State var showSheet = false
#State var showModal = false
var body: some View {
Button(action: {
self.showSheet = true
}) {
Text("Show action sheet")
}
.actionSheet(isPresented: $showSheet, content: actionSheet)
.sheet(isPresented: $showModal, content: { Text("Modal") })
}
private func actionSheet() -> ActionSheet {
let button = ActionSheet.Button.default(Text("Show modal")) {
self.showSheet = false
self.showModal = true
}
let actionSheet = ActionSheet(title: Text("Action Sheet"),
message: nil,
buttons: [button])
return actionSheet
}
}
Result:
Updated for Xcode 11 beta 5

Resources