Navigation Bar back button disappears after showing an alert - ios

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

Related

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

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

Xcode 13.2.1 SwiftUI 3 iOS 15.2: View with toolbar buttons get frozen when navigated from view with searchbar

Parent view has list with searchable functionality, trying to search for a recipe and then navigate to the child view that has toolbar with button, the child view get frozen when navigating from the searchbar, if I navigate from the normal list without searching in the parent view, everything works as expected, but when I try to search and then navigate, the child view get frozen, I tried:
removing the toolbar and navigated from the searchbar, worked as expected.
created empty toolbar in the child view without buttons, worked as expected.
I have an old app that started having this issue, I created a new project to check if this issue happens only in my code, but no, this issue also happens in a new project with iOS 15.2 and Xcode 13.2.1.
I noticed that I started having this issue when I tried to release a new version with iOS 15.2, I suppose this is an iOS and SwiftUI issue but not sure how to work around on it.
Parent View:
import SwiftUI
struct RecipesView: View {
#State private var recipes: [String] = ["Brownies", "Cake", "Bread", "Pan"]
#State private var filteredRecipes: [String] = []
#State private var searchText: String = ""
var body: some View {
NavigationView {
List {
ForEach(self.filteredRecipes, id: \.self) { recipe in
NavigationLink {
RecipeView(recipe: recipe)
} label: {
Text(recipe)
}
}
}
.searchable(text: self.$searchText)
.onChange(of: self.searchText, perform: { newValue in self.filter() })
.onAppear { self.filter() }
}
}
private func filter() {
if self.searchText.isEmpty {
self.filteredRecipes = self.recipes
} else {
self.filteredRecipes = self.recipes.filter {
return ($0.lowercased().contains(self.searchText.lowercased()))
}
}
}
}
Child View:
import SwiftUI
struct RecipeView: View {
var recipe: String
var body: some View {
List {
Section {
Text(self.recipe)
}
Section {
Text("Another Text")
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
} label: {
Text("Edit")
}
}
}
}
}

How to Show Alert from Anywhere in app SwiftUI?

I have condition to show alert in a view which can able to show from anywhere in the app. Like I want to present it from root view so it can possibly display in all view. Currently what happens when I present from very first view it will display that alert until i flow the same Navigation View. Once any sheets open alert is not displayed on it. Have any solutions in SwiftUI to show alert from one place to entire app.
Here is my current Implementation of code.
This is my contentView where the sheet is presented and also alert added in it.
struct ContentView: View {
#State var showAlert: Bool = false
#State var showSheet: Bool = false
var body: some View {
NavigationView {
Button(action: {
showSheet = true
}, label: {
Text("Show Sheet")
}).padding()
.sheet(isPresented: $showSheet, content: {
SheetView(showAlert: $showAlert)
})
}
.alert(isPresented: $showAlert, content: {
Alert(title: Text("Alert"))
})
}
}
Here from sheet I am toggle the alert and the alert is not displayed.
struct SheetView: View {
#Binding var showAlert: Bool
var body: some View {
Button(action: {
showAlert = true
}, label: {
Text("Show Alert")
})
}
}
here is the error in debug when we toggle button
AlertDemo[14187:3947182] [Presentation] Attempt to present <SwiftUI.PlatformAlertController: 0x109009c00> on <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x103908b50> (from <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x103908b50>) which is already presenting <_TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView_: 0x103d05f50>.
Any solution for that in SwiftUI? Thanks in Advance.
I was able to achieve this with this simplified version of what #workingdog suggested in their answer. It works as follows:
create the Alerter class that notifies the top-level and asks to display an alert
class Alerter: ObservableObject {
#Published var alert: Alert? {
didSet { isShowingAlert = alert != nil }
}
#Published var isShowingAlert = false
}
render the alert at the top-most level, for example in your #main struct or the ContentView
#main
struct MyApp: App {
#StateObject var alerter: Alerter = Alerter()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(alerter)
.alert(isPresented: $alerter.isShowingAlert) {
alerter.alert ?? Alert(title: Text(""))
}
}
}
}
set the alert that should be displayed from inside a child view
struct SomeChildView: View {
#EnvironmentObject var alerter: Alerter
var body: some View {
Button("show alert") {
alerter.alert = Alert(title: Text("Hello from SomeChildView!"))
}
}
}
Note on sheets
If you present views as sheets, each sheet needs to implement its own alert, just like MyApp does above.
If you have a NavigationView inside your sheet and present other views within this navigation view in the same sheet, the subsequent sheets can use the first sheet's alert, just like SomeChildView does in my example above.
Here is a possible example solution to show an Alert anywhere in the App.
It uses "Environment" and "ObservableObject".
import SwiftUI
#main
struct TestApp: App {
#StateObject var alerter = Alerter()
var body: some Scene {
WindowGroup {
ContentView().environment(\.alerterKey, alerter)
.alert(isPresented: $alerter.showAlert) {
Alert(title: Text("This is the global alert"),
message: Text("... alert alert alert ..."),
dismissButton: .default(Text("OK")))
}
}
}
}
struct AlerterKey: EnvironmentKey {
static let defaultValue = Alerter()
}
extension EnvironmentValues {
var alerterKey: Alerter {
get { return self[AlerterKey] }
set { self[AlerterKey] = newValue }
}
}
class Alerter: ObservableObject {
#Published var showAlert = false
}
struct ContentView: View {
#Environment(\.alerterKey) var theAlerter
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: SecondView()) {
Text("Click for second view")
}.padding(20)
Button(action: { theAlerter.showAlert.toggle()}) {
Text("Show alert here")
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct SecondView: View {
#Environment(\.alerterKey) var theAlerter
var body: some View {
VStack {
Button(action: { theAlerter.showAlert.toggle()}) {
Text("Show alert in second view")
}
}
}
}

SwiftUI: How to execute closure when Alert is dismissed?

I've been trying out swiftUI and looked at this Ray Wenderlich tutorial... I noticed they didn't re-implement the "nextRound" functionality... so I tried to do it myself. Ran into a problem (which maybe they did, also):
The basic question is more general:
Using swiftUI, how do you trigger a function when an Alert is dismissed -- when the user clicks "OK." ?
I've tried using the dismissButton argument of the Alert constructor...
(and also the .onDisappear method of View but I can't figure out how to apply it to the Alert view.)
Code:
import SwiftUI
struct ContentView: View {
#State var shouldShowAlert: Bool = false
// this never gets called
func onAlertDismissed() {
print("you will not see this in the console")
}
// this doesn't seem to work
var dismissButton: some View {
Button(action: {
self.onAlertDismissed()
}) {
// Bilbo Baggins does not appear -- "OK" still shows
Text("BILBO BAGGINS")
}
}
var body: some View {
VStack {
Spacer()
Button(action: {
self.shouldShowAlert = true
}) {
Text("show the alert!")
}
Spacer()
}.alert(isPresented: $shouldShowAlert, content: {
// what to add here?
Alert(title: Text("Alert:"), message: Text("press OK to execute onAlertDismissed()..."))
// what I have tried and doesn't work:
/*
Alert(title: Text("Alert:"), message: Text("press OK to execute onAlertDismissed()..."), dismissButton: self.dismissButton as? Alert.Button)
*/
})
}
}
The button is constructed a little differently. You basically have to use a static factory method from Alert.Button to construct them and pass those in.
Alert(title: Text("Alert:"),
message: Text("press OK to execute default action..."),
dismissButton: Alert.Button.default(
Text("Press ok here"), action: { print("Hello world!") }
)
)
Alert(title: Text("Alert!"), message: Text("Message"),
primaryButton: Alert.Button.default(Text("Yes"), action: {
print("Yes")
}),
secondaryButton: Alert.Button.cancel(Text("No"), action: {
print("No")
})
)
It's possible to create alerts like this:
import SwiftUI
struct ContentView: View {
#State var showingAlert = false
var body: some View {
VStack {
HStack {
Button(action: {
self.showingAlert = true
})
{
Text("Save")
.font(.headline)
}
.alert(isPresented: $showingAlert, content: {
return Alert(
title: Text("Save Product"),
message: Text("Are you sure you want to save the changes made?"),
primaryButton: .default(Text("Yes"), action: {
//insert an action here
}),
secondaryButton: .destructive(Text("No")))
})
}
}
}
}
By looking at your code, it appears you don’t include a button in the alert propert, so your alert is not executing any action, in swiftui the alert signature is
init(title: Text, message: Text? = nil, primaryButton: Alert.Button, secondaryButton: Alert.Button)
Implement the signature properly is the first step

Resources