SwiftUI: How can I display an alert on the very first start - swift

I'm new in SwiftUI and I would like to show an alert when the user start the app for the first time. If the user opens the app a second (or third...) time, the alert should not come anymore.
struct ContentView: View {
#State var alertShouldBeShown = true
var body: some View {
VStack {
Text("Hello World!")
.alert(isPresented: $alertShouldBeShown, content: {
Alert(title: Text("Headline"),
message: Text("Placeholder"),
dismissButton: Alert.Button.default(
Text("Accept"), action: {
}
)
)
})
}
}
}

Update: Xcode 13.4
Original variant is still valid and works "as-is", but now we can simplify code a bit with AppStorage
struct ContentView: View {
#AppStorage("FirstStart") var alertShouldBeShown = true
// ... same code
dismissButton: Alert.Button.default(
Text("Accept"), action: {
alertShouldBeShown = false
})
}
Original
Use UserDefaults to store state value.
Here is possible solution. Tested with Xcode 11.7/iOS 13.7
struct ContentView: View {
#State var alertShouldBeShown = !UserDefaults.standard.bool(forKey: "FirstStart")
var body: some View {
VStack {
Text("Hello World!")
.alert(isPresented: $alertShouldBeShown, content: {
Alert(title: Text("Headline"),
message: Text("Placeholder"),
dismissButton: Alert.Button.default(
Text("Accept"), action: {
UserDefaults.standard.set(true, forKey: "FirstStart")
}
)
)
})
}
}
}

import SwiftUI
struct ContentView: View {
#State private var isAlert = false
var body: some View {
Button(action: {
self.isAlert = true
}) {
Text("Click Alert")
.foregroundColor(Color.white)
}
.padding()
.background(Color.blue)
.alert(isPresented: $isAlert) { () -> Alert in
Alert(title: Text("iOSDevCenters"), message: Text("This Tutorial for SwiftUI Alert."), primaryButton: .default(Text("Okay"), action: {
print("Okay Click")
}), secondaryButton: .default(Text("Dismiss")))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Related

Embedded navigationlinks

I am trying to develop an app to do a little cognitive testing
When opening the app there is a NavigationView{} with NavigationLink{}
So far so normal
One of the links
NavigationLink(destination: IntermediateCoreView()) { Text("Go to tests") }
Takes you to a 'prescreen for the tests, from which you can link to the 'test' its self
From the IntermediateCoreView.swift you can NavigationLink(destination: ContentView()) { Text("+ new test") }
Which works, you can take the test. At the end of the test (X seconds pass) and then it displays an alert that the user has run out of time and takes them back to the Intermediate CoreView
This is where it goes wrong as the IntermediateCoreView, 'Back' button in the NavigationView goes back to the 'test' view. Not back to the InitialView
I get that this is 'expected behaviour', the test is back from the screen its been sent to. Is there a way to override this?
To add a minimal example - the .app file:
import SwiftUI
#main
struct minRep2App: App {
var body: some Scene {
WindowGroup {
initialView()
}
}
}
Then the initial view controller:
import SwiftUI
struct initialView: View {
var body: some View {
NavigationView{
List{
NavigationLink(destination: ContentView()) {
Text("Go to CoRe tests")
}
}
}
}
}
struct initialView_Previews: PreviewProvider {
static var previews: some View {
initialView()
}
}
Lastly the test demo
import SwiftUI
struct ContentView: View {
#State private var timeRemaining = 50.00
#State private var showingAlert = false
#State var showIntermediate: Bool = false
let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
var body: some View {
NavigationLink(destination: initialView(), isActive: self.$showIntermediate)
{
EmptyView()
}
Text("Test goes here")
.padding()
HStack{
// Text("Score: \(score)")
Text("Time: \(timeRemaining)")
}.padding(.bottom, 10)
.onReceive(timer) { time in
if self.timeRemaining > 0.1 {
self.timeRemaining -= 1
}
if self.timeRemaining == 0.0 {
self.showingAlert = true
self.timer.upstream.connect().cancel()
}
}
.alert(isPresented: $showingAlert){
Alert(title: Text("Warning"), message: Text("Sorry your time is up!"), dismissButton: .default(Text("OK"), action: {
self.showIntermediate = true
})
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Change your ContentView to this:
import SwiftUI
struct ContentView: View {
#Environment(\.presentationMode) var presentationMode
#State private var timeRemaining = 50.00
#State private var showingAlert = false
let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
var body: some View {
Text("Test goes here")
.padding()
HStack{
// Text("Score: \(score)")
Text("Time: \(timeRemaining)")
}.padding(.bottom, 10)
.onReceive(timer) { time in
if self.timeRemaining > 0.1 {
self.timeRemaining -= 1
}
if self.timeRemaining == 0.0 {
self.showingAlert = true
self.timer.upstream.connect().cancel()
}
}
.alert(isPresented: $showingAlert){
Alert(title: Text("Warning"), message: Text("Sorry your time is up!"), dismissButton: .default(Text("OK"), action: {
self.presentationMode.wrappedValue.dismiss()
})
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This has nothing to do with the back button which will work fine. The problem is the logic in your alert caused you to instantiate a new, wholly different, InitialView. Think of it like webpages. If you navigate from one to the next, and then click on a link that goes to the first one, you have actually gone FORWARD to the first page, and if you click the back button you will get to the second page. Same thing here.
Instead, you should use #Environment(\.presentationMode) var presentationMode which, when you call self.presentationMode.wrappedValue.dismiss() will trigger you to go back to the view that presented the view that you are trying to navigate back from.

SwiftUI - Proceed to new view, from an alert

I am new to SwiftUI. I have the following code, and I would like to proceed to 'ContentView2', when the user clicks 'Proceed' on the alert
struct ContentView: View {
#State var alertIsShown: Bool
var myArray = ["Blue","Red","Pink","Yellow"]
var body: some View {
NavigationView{
List(0..<myArray.count) { i in
Button(action: {
print("Button tapped")
alertIsShown = true
}) {
Text(myArray[i])
}
.alert(isPresented: $alertIsShown) {
Alert(title: Text("You have chosen"), message: Text("Are you sure you want to go to contentview2?"), primaryButton: .destructive(Text("Proceed!")) {
print("Proceeding...")
///Solution?
}, secondaryButton: .cancel())
}
}
}
}
}
struct ContentView2 : View {
var body: some View {
Text("Im the second view")
// This will eventually say "Im the yellow view", based on the selection on CV1
}
}
Is this possible?
Here an example code which is working. But it will alway navigate to ContentView2, independent on which item in the list you will click.
struct ContentView: View {
#State var alertIsShown = false
#State private var navigate = false
var myArray = ["Blue","Red","Pink","Yellow"]
var body: some View {
NavigationView{
ZStack {
NavigationLink(destination: ContentView2(), isActive: $navigate){
EmptyView()
}
List(0..<myArray.count) { i in
Button(action: {
print("Button tapped")
alertIsShown = true
}) {
Text(myArray[i])
}
.alert(isPresented: $alertIsShown) {
Alert(title: Text("You have chosen"), message: Text("Are you sure you want to go to contentview2?"), primaryButton: .destructive(Text("Proceed!")) {
navigate.toggle()
}, secondaryButton: .cancel())
}
}
}
}
}
}

SwiftUI: How can I change the Color of Alert Button and NavigationLink back button?

How can I change the Color from the Button in a Alert and the back Button from NavigationLink? To set .accentColor(.init("someColor")) after the Text from the Alert Button doesn't work. To set .accentColor(.init("someColor")) after navigationLink does not work too. What can I do?
The Alert Button:
The Back Button from NavigationLink:
struct ContentView: View {
#State var showSheet = false
#State var alertShouldBeShown = !UserDefaults.standard.bool(forKey: "£Start")
var body: some View {
Button(action: {
self.showSheet.toggle()
}) {
Text("click me")
//.accentColor(colorScheme == .dark ? Image("") : Image("Info-Icon"))
}.sheet(isPresented: $showSheet) {
SheetView(showSheet: self.$showSheet)
}
.alert(isPresented: $alertShouldBeShown, content: {
Alert(title: Text("Headline"),
message: Text("Text"),
dismissButton: Alert.Button.default(
Text("Ok"), action: {
UserDefaults.standard.set(true, forKey: "Start")
}
)
)
})
}
}
struct SheetView: View {
#Binding var showSheet: Bool
var body: some View {
NavigationView {
DetailView()
.navigationBarTitle(Text("Headline"), displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
self.showSheet = false
}) {
Text("Done")
.bold()
})
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct DetailView: View {
var body: some View {
List {
HStack {
NavigationLink(destination: DetailViewOne()) {
Text("View 1")
}
}
HStack {
NavigationLink(destination: DetailViewTwo()) {
Text("View 2")
}
}
}
}
}
struct DetailViewOne: View {
var body: some View {
Text("1")
}
}
struct DetailViewTwo: View {
var body: some View {
Text("2")
}
}
You can change the NavigationBar accent color by using accentColor but you need to apply it to the SheetView (the root view of a given environment):
SheetView(showSheet: self.$showSheet)
.accentColor(.red)
Unfortunately SwiftUI doesn't allow much of Alert customisation so far.
However, as Alert is built on top of UIKit components (UIAlertController) this also means you can change the appearance of UIView when contained in UIAlertController.
You can put this code in your #main App init or in the SceneDelegate:
UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = .red

NavigationView pops back to root, omitting intermediate view

In my navigation, I want to be able to go from ContentView -> ModelListView -> ModelEditView OR ModelAddView.
Got this working, my issue now being that when I hit the Back button from ModelAddView, the intermediate view is omitted and it pops back to ContentView; a behaviour that
ModelEditView does not have.
There's a reason for that I guess – how can I get back to ModelListView when dismissing ModelAddView?
Here's the code:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List{
NavigationLink(
destination: ModelListView(),
label: {
Text("1. Model")
})
Text("2. Model")
Text("3. Model")
}
.padding()
.navigationTitle("Test App")
}
}
}
struct ModelListView: View {
#State var modelViewModel = ModelViewModel()
var body: some View {
List(modelViewModel.modelValues.indices) { index in
NavigationLink(
destination: ModelEditView(model: $modelViewModel.modelValues[index]),
label: {
Text(modelViewModel.modelValues[index].titel)
})
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(
trailing:
NavigationLink(
destination: ModelAddView(modelViewModel: $modelViewModel), label: {
Image(systemName: "plus")
})
)
}
}
struct ModelEditView: View {
#Binding var model: Model
var body: some View {
TextField("Titel", text: $model.titel)
}
}
struct ModelAddView: View {
#Binding var modelViewModel: ModelViewModel
#State var model = Model(id: UUID(), titel: "")
var body: some View {
TextField("Titel", text: $model.titel)
}
}
struct ModelViewModel {
var modelValues: [Model]
init() {
self.modelValues = [ //mock data
Model(id: UUID(), titel: "Foo"),
Model(id: UUID(), titel: "Bar"),
Model(id: UUID(), titel: "Buzz")
]
}
}
struct Model: Identifiable, Equatable {
let id: UUID
var titel: String
}
Currently placing a NavigationLink in the .navigationBarItems may cause some issues.
A possible solution is to move the NavigationLink to the view body and only toggle a variable in the navigation bar button:
struct ModelListView: View {
#State var modelViewModel = ModelViewModel()
#State var isAddLinkActive = false // add a `#State` variable
var body: some View {
List(modelViewModel.modelValues.indices) { index in
NavigationLink(
destination: ModelEditView(model: $modelViewModel.modelValues[index]),
label: {
Text(modelViewModel.modelValues[index].titel)
}
)
}
.background( // move the `NavigationLink` to the `body`
NavigationLink(destination: ModelAddView(modelViewModel: $modelViewModel), isActive: $isAddLinkActive) {
EmptyView()
}
.hidden()
)
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing: trailingButton)
}
// use a Button to activate the `NavigationLink`
var trailingButton: some View {
Button(action: {
self.isAddLinkActive = true
}) {
Image(systemName: "plus")
}
}
}

How can I use Navigation in alert using SwiftUI

I'm working on a Bluetooth Application.It has onboarding and dashboard.On the Onboarding there is pairing and instructions on how to use the module, and the dashboard controls the peripheral device.So I need to unpair using an alert and navigate it to a different page called Onboarding.How can I navigate to a different view using an alert.
Code Block
import SwiftUI
import BLE
struct Dashboard: View {
#EnvironmentObject var BLE: BLE
#State private var showUnpairAlert: Bool = false
#State private var hasConnected: Bool = false
let defaults = UserDefaults.standard
let defaultDeviceinformation = "01FFFFFFFFFF"
struct Keys {
static let deviceInformation = "deviceInformation"
}
var body: some View {
VStack(alignment: .center, spacing: 0) {
// MARK: - Menu Bar
HStack(alignment: .center, spacing: 10) {
VStack(alignment: .center, spacing: 4) {
Text(self.hasConnected ? "PodId \(checkForDeviceInformation())":"Pod is not connected")
.font(.footnote)
.foregroundColor(.white)
Button(action: {
print("Unpair tapped!")
self.showUnpairAlert = true
}) {
HStack {
Text("Unpair")
.fontWeight(.bold)
.font(.body)
}
.frame(minWidth: 85, minHeight: 35)
.foregroundColor(.white)
.background(Color(red: 0.8784313725490196, green: 0.34509803921568627, blue: 0.36470588235294116))
.cornerRadius(30)
}
}
}
}
.alert(isPresented: $showUnpairAlert) {
Alert(title: Text("Unpair from \(checkForDeviceInformation())"), message: Text("Do you want to unpair the current pod?"), primaryButton: .destructive(Text("Unpair")) {
self.unpairAndSetDefaultDeviceInformation()
}, secondaryButton: .cancel())
}
}
func checkForDeviceInformation() -> String {
let deviceInformation = defaults.value(forKey: Keys.deviceInformation) as? String ?? ""
print("Device Info \(deviceInformation)")
return deviceInformation
}
func unpairAndSetDefaultDeviceInformation() {
defaults.set(defaultDeviceinformation, forKey: Keys.deviceInformation)
print("Pod unpaired and view changed to Onboarding")
}
}
Thank you !!!!
I simplified your code snapshot for demo, but think the idea would be clear
struct TestNavigationFromAlert: View {
#State private var showUnpairAlert: Bool = false
#State private var activateLink: Bool = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("Your Onboarding page"), isActive: $activateLink,
label: { EmptyView() })
// DEMO BUTTON - REMOVE IT
Button(action: { self.showUnpairAlert = true }) { Text("Alert") }
// YOUR CODE IS HERE
}
.alert(isPresented: $showUnpairAlert) {
Alert(title: Text("Unpair from \(checkForDeviceInformation())"), message: Text("Do you want to unpair the current pod?"), primaryButton: .destructive(Text("Unpair")) {
self.unpairAndSetDefaultDeviceInformation()
}, secondaryButton: .cancel())
}
}
}
func checkForDeviceInformation() -> String {
// YOUR CODE IS HERE
return "Stub information"
}
func unpairAndSetDefaultDeviceInformation() {
// YOUR CODE IS HERE
DispatchQueue.main.async {
self.activateLink = true
}
}
}