SwiftUI: How to present new views on clicking ActionSheet button? - ios

I have an action sheet consist of three actions
Action_1
Action_2
Cancel
If I tap on 'Action_1', My app should present ActionView_1().
If I tap on 'Action_2', My app should present ActionView_2().
I got this for presenting a view
.sheet(isPresented: $isAddSecretVisible){ActionView_1()}.
But this for presenting a view with a Button click.
I need the same action if tap on the Actionsheet button.
Answer needs in SwiftUI
Thanks in advance.

You should define 2 State, each for each a sheet:
#State var isMainActionPresented = false
#State var isActionViewPresented = false
And a State to determine witch actionSheet to present. So you can have an enum for that like:
enum ActionViewMode {
case first
case second
}
And a helper extension on that:
extension ActionViewMode {
var view: some View {
switch self {
case .first: return ActionView1()
case .second: return ActionView2()
}
}
}
Then on click of any Button or ActionSheet.Button, toggle the desired state. Look at the complete ContentView code below:
#State var actionViewMode = ActionViewMode.first
#State var isMainActionPresented = false
#State var isActionViewPresented = false
var body: some View {
Button(action: {
self.isMainActionPresented = true
}) {
Text("ActionSheet")
}
.actionSheet(isPresented: $isMainActionPresented) {
ActionSheet(
title: Text("Title"),
message: Text("Message"),
buttons: [
.default(
Text("Action_1"),
action: {
self.actionViewMode = .first
self.isActionViewPresented = true
}),
.default(
Text("Action_2"),
action: {
self.actionViewMode = .second
self.isActionViewPresented = true
}),
.cancel()
])
}
.sheet(isPresented: $isActionViewPresented) {
self.actionViewMode.view
}
}
SwiftUI will handle the rest.
Note that you can't chain multiple sheets one after another, because each one overrides previous somehow.

Reference link : SwiftUI
ActionSheet(title: Text("iOSDevCenters"), message: Text("SubTitle"), buttons: [
.default(Text("Action_1"), action: {
print("Action_1")
ActionView_1()
}),
.destructive(Text("Cancel"))
])
})

Related

Navigation Bar back button disappears after showing an alert

I have a simple view:
struct AccountView: View {
#State private var showingLogoutAlert = false
var body: some View {
Button("Log out!") {
showingLogoutAlert = true
}
.alert(isPresented: $showingLogoutAlert, content: {
Alert(title: Text("Log out of Flow?"),
primaryButton: .cancel(),
secondaryButton: .destructive(Text("Log out"), action: {
//logOut()
}))
})
}
}
When you tap the button, it will show an alert. The problem is, after the alert dismisses, the back button also disappears! I have no idea why this is happening. See the gif below.
I've tried to reduce the problem down to the bare minimum code. Replicated it on a separate, new app. Still the same issue.
this is the working code I used in my test, on MacOS 13.2, Xcode 14.2, tested on real ios 16.3 devices (not Previews), and macCatalyst.
struct ContentView: View {
var body: some View {
NavigationStack {
NavigationLink(destination: AccountView()) {
Text("AccountView")
}
}
}
}
struct AccountView: View {
#State private var showingLogoutAlert = false
var body: some View {
Button("Log out!") {
showingLogoutAlert = true
}
.alert(isPresented: $showingLogoutAlert, content: {
Alert(title: Text("Log out of Flow?"),
primaryButton: .cancel(),
secondaryButton: .destructive(Text("Log out"), action: {
//logOut()
}))
})
}
}

How to switch cancel and destructive alert buttons in SwiftUI?

I created a simple alert and I would like to have the destructive button on the left and the 'cancel' button on the right, but I don't know how to manage it, any suggestions?
Code:
struct ContentView: View {
#State private var showAlert = false
var body: some View {
VStack {
Button("Present alert") {
showAlert.toggle()
}
}
.alert("Do you want to quit?",
isPresented: $showAlert,
actions: {
Button("No", role: .cancel) {
//
}
Button ("Yes", role: .destructive) {
//
}
}, message: {
Text("You will go back to main menu")
})
}
}
I've done a little research and the only solution I found is to use the deprecated struct Alert that still works but may not do so in the future, this is the code:
.alert(isPresented: $showAlert) {
Alert(
title: Text("Do you want to leave the game?"),
message: Text("You will go back to the main menu."),
primaryButton: .destructive(Text("Yes"), action: {
// code here
}),
secondaryButton: .default(Text("No")) //<-- use default here
)
}
Pay attention, you have to use .default() as a 'cancel' button, you cannot use .cancel()

Move to other Picker View tabs only after the user taps on a button in an alert view - SwiftUI

What I would like to be able to do is that when the user taps on a color tab, first present an alert view asking before actually moving to the other tab and if the user decides to Cancel it will not move to the other tab but if the user taps the OK button, the Picker will actually move to the selected tab.
How can I move to a different color tab only after the user taps on the YES button in the alert view?
struct ContentView: View {
#State private var favoriteColor = 0
#State private var showAlert = false
var body: some View {
VStack {
Picker("What is your favorite color?", selection: $favoriteColor) {
Text("Red").tag(0)
Text("Green").tag(1)
}
.pickerStyle(.segmented)
.onChange(of: favoriteColor) { tag in
showAlert = true
}
.alert("By changing color also modifieds other things...change color?", isPresented: $showAlert) {
Button("YES") {
// move to selected color tab
}
Button("Cancel") {
// do nothing
}
}
}
}
}
SwiftUI is reactive world, means our code receives control post-factum... use UIKit representable instead for that...
... Or, here is a possible workaround with proxy binding (however not sure if behavior persists in all OS versions)
Tested with Xcode 13.4 / iOS 15.5
Main part:
var selected: Binding<Int> { // << proxy !!
Binding<Int>(
get: { favoriteColor },
set: {
toConfirm = $0 // store for verification !!
showAlert = true // show alert !!
}
)
}
var body: some View {
VStack {
// << bind to proxy binding here !!
Picker("What is your favorite color?", selection: selected) {
Text("Red").tag(0)
Text("Green").tag(1)
}
.pickerStyle(.segmented)
.alert("By changing color also modifieds other things...change color?", isPresented: $showAlert, presenting: toConfirm) { color in // << inject for verification !!
Button("YES") {
favoriteColor = color // << do verification here !!
}
Button("Cancel") {
// do nothing
}
}
}
Test code on GitHub

Why does my SwiftUI ActionSheet button/title display dark?

I have a common feature in my app where users can report posts but tapping a button and selecting "Report" from an action sheet.
For some reason my action-sheet title, .destructive AND .default buttons display darker than they should while the .cancel button displays as it should, see image below..
Below is my code and structure. I do not have any Extensions for ActionSheet that would be doing this and it occurs globally in my app, why could this be happening?
Thank you
Parent List View for each post
struct NewsFeed: View {
#EnvironmentObject var session : SessionStore
#StateObject var newsfeedVM = NewsFeedViewModel()
var body: some View {
NavigationView {
List {
ForEach(newsfeedVM.posts, id: \.id) { post in
PostCell(post: post)
}
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarTitle(Text(""), displayMode: .inline)
}
}
}
PostCell subview with ActionSheet
struct PostCell: View {
#StateObject var postVM = PostViewModel()
#State var post: Post
var body: some View {
VStack {
PostHeadline(postVM: postVM, post: $post)
PostMedia(postVM: postVM, post: $post)
CommentCell(postVM: postVM, post: $post)
}
.actionSheet(isPresented: $postVM.showActionSheet) {
ActionSheet(title: Text("Report a post"), buttons: [
.destructive(Text("Report")) {
self.reportPost(post: post)
},
.cancel() {
postVM.selectedPost = ""
}
])
}
}
func reportPost(post: Post) {/// }
}

Swift UI need to keep both NavigationLink to detail view and Tap gesture recognizer

I am trying a simple app that is a List with items, they lead to detail view. I also have a search bar that opens keyboard, and I need to hide the keyboard when the user taps anywhere outside of the keyboard.
#State private var keyboardOpen: Bool = false
var body: some View {
NavigationView {
Form {
Section {
TextField("Search", text: $cityStore.searchTerm, onCommit: debouncedFetch)
.keyboardType(.namePhonePad)
.disableAutocorrection(true)
.onTapGesture { self.keyboardOpen = true }
.onDisappear { self.keyboardOpen = false }
}
Section {
List {
ForEach(cities) { city in
NavigationLink(
destination: DetailView(city: city)) {
VStack(alignment: .leading) {
Text("\(city.name)")
}
}
}
}
}
}
.navigationBarTitle("City list")
.onTapGesture {
if self.keyboardOpen {
UIApplication.shared.endEditing()
self.keyboardOpen = false
}
}
}
}
Do you know if it's possible to keep both gesture tap and follow to detail view?
Actually it should work, but it is not due to bug of .all GestureMask. I submitted feedback to Apple #FB7672055, and recommend to do the same for everybody affected, the more the better.
Meanwhile, here is possible alternate approach/workaround to achieve similar effect.
Tested with Xcode 11.4 / iOS 13.4
extension UIApplication { // just helper extension
static func endEditing() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
}
}
struct TestEndEditingOnNavigate: View {
#State private var cities = ["London", "Berlin", "New York"]
#State private var searchTerm = ""
#State private var tappedLink: String? = nil // track tapped link
var body: some View {
NavigationView {
Form {
Section {
TextField("Search", text: $searchTerm)
}
Section {
List {
ForEach(cities, id: \.self) { city in
self.link(for: city) // decompose for simplicity
}
}
}
}
.navigationBarTitle("City list")
}
}
private func link(for city: String) -> some View {
let selection = Binding(get: { self.tappedLink }, // proxy bindng to inject...
set: {
UIApplication.endEditing() // ... side effect on willSet
self.tappedLink = $0
})
return NavigationLink(destination: Text("city: \(city)"), tag: city, selection: selection) {
Text("\(city)")
}
}
}
I think you could easily handle this scenario with boolean flags, when your keyboard opens you can set a flag as true and when it dismisses a the flag goes back to false, so in that case when the keyboard is open and the tap gesture is triggered you can check if the keyboard flag is active and not go to detail but instead effectively dismiss the keyboard and viceversa. Let me know if maybe I misunderstood you.

Resources