I understand that an Alert can be presented as a function of a Button, but can an Alert be presented inside a conditional? Such as:
struct ContentView: View {
var body: some View {
Text("Hello World!")
}
}
if isValid {
//present alert
let alert = UIAlertController(title: "My Title", message: "This
is my message.", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style:
UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
With this I get
Value of type 'ContentView' has no member 'present'
I'm not sure why are you using UIKit. Here's an example of how an alert may be presented when something changes a flag. In this case, a two second timer:
import SwiftUI
class MyModel: ObservableObject {
#Published var isValid: Bool = false
init() {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
self.isValid = true
}
}
}
struct ContentView: View {
#ObservedObject var model: MyModel = MyModel()
var body: some View {
VStack {
Text("Some text")
Text("Some text")
Text("Some text")
Text("Some text")
}.alert(isPresented: $model.isValid, content: {
Alert(title: Text("Title"),
message: Text("Message"),
dismissButton: .default(Text("OK")) { print("do something") })
})
}
}
Related
how can I navigate from UIAlertController(UIKit) to a new view(SwiftUi)
due the the reason the swiftui alert can't use a textfield
I used UIKit(UIalertcontroller)
and I want to open a new screen after I click the submit button
I have tried:
var passwordField: UITextField? = nil
let alert = UIAlertController(title: "Admin Passcode", message: "Please enter an administrator password", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Submit", style: .default, handler: { Action in
if let passwordField = passwordField {
if passwordField.text == Constants.AdminPwd {
print("Wow😁")
DeviceConfigurationView(activeView: true)
} else {
let alert2 = UIAlertController(title: "Bad Password", message: "Incorrect password", preferredStyle: .alert)
alert2.addAction(UIAlertAction(title: "Dismiss", style: .default, handler: nil))
rootContoller().present(alert2, animated: true, completion: nil)
}
}
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .default) {
action in
alert.dismiss(animated: true)
})
alert.addTextField {
(textField) in
textField.placeholder = "Password"
textField.isSecureTextEntry = true
textField.becomeFirstResponder()
passwordField = textField
}
rootContoller().present(alert, animated: true, completion: nil)
func rootContoller()->UIViewController{
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return .init()
}
guard let root = screen.windows.first?.rootViewController else {
return .init()
}
return root
}
}
this is the view I want to navigate to:
import UIKit
struct DeviceConfigurationView: View {
#State var activeView: Bool
var body: some View {
NavigationView {
DeviceView()
}
.navigationViewStyle(.stack)
}
}
struct DeviceView: View {
#Environment(\.presentationMode) var presentationMode
#State var ToggleIsOn: Bool = false
#State var isExpandAudioSampleRate: Bool = false
#State var isExpandVideoResolution: Bool = false
#State var isExpandVideoResolutionForLocalVideoFile: Bool = false
#State private var textField: String = ""
var body: some View {
List {
// Device
Section(header:Text("Device"),
footer:
HStack {
Text("Device ID")
TextField("Device id", text: $textField)
}) {
}
// offline
Section {
Toggle(isOn: $ToggleIsOn) {
Text("Offline Mode")
.opacity(5)
}
} header: {
Text("Offline")
}
// manage Recordings
Section {
Button {
// action
} label: {
Text("Delete All Recordings")
.foregroundColor(Color.Theme.redcoolor)
}
} header: {
Text("Manage Recordings")
}
// Firebase
Section {
HStack {
Text("Crash testing")
Spacer()
Button {
// action
} label: {
Text("Crash")
.foregroundColor(Color.blue)
}
}
} header: {
Text("Firebase")
}
Section {
HStack{
Text("Audio SampleRate")
.bold()
Spacer()
Button {
isExpandAudioSampleRate.toggle()
} label: {
Image(systemName: isExpandAudioSampleRate ? "chevron.up" : "chevron.down")
.foregroundColor(Color.blue)
.font(.system(size: 30))
}
}
}
Section {
HStack{
Text("Video Resolution")
.bold()
Spacer()
Button {
isExpandVideoResolution.toggle()
} label: {
Image(systemName: isExpandVideoResolution ? "chevron.up" : "chevron.down")
.foregroundColor(Color.blue)
.font(.system(size: 30))
}
}
}
Section {
HStack{
Text("Video Resolution For Local Video File")
.bold()
Spacer()
Button {
isExpandVideoResolutionForLocalVideoFile.toggle()
} label: {
Image(systemName: isExpandVideoResolutionForLocalVideoFile ? "chevron.up" : "chevron.down")
.foregroundColor(Color.blue)
.font(.system(size: 30))
}
}
}
}
.listStyle(PlainListStyle())
.navigationTitle("Device Configuration")
.navigationBarItems(
leading: Button(action: {
presentationMode.wrappedValue.dismiss()
print("Cancelled")
}, label: {
Text("Cancel")
}),
trailing: Button(action: {
// MainView()
print("Saved")
}, label: {
Text("Save")
}))
}
}
and I am getting this error
my error
From your code logic, it seems to be like
if passwordField.text == Constants.AdminPwd {
print("Wow😁")
let controller = UIHostingController(rootView: DeviceConfigurationView(activeView: true))
rootContoller().present(controller, animated: true, completion: nil)
} else {
...
}
This question already has answers here:
SwiftUI: How do I make a button open a URL in safari?
(5 answers)
Closed 10 months ago.
There are dozens of Stackoverflow answers for mailto links in Swift 5.
The consensus look like this
let url = NSURL(string: "mailto:jon.doe#mail.com")
UIApplication.sharedApplication().openURL(url)
But how do I actually use this code? Ideally in an alert, but at least a general Text element
import SwiftUI
struct MainMenu: View {
#State private var showAlert = false
// What do I put here
var emailLink:UIApplication {
let url = URL(string: "mailto:jon.doe#mail.com")!
return UIApplication.shared.openURL(url)
}
// To make it work in here
var body: some View {
HStack(alignment: .center, spacing: .zero, content: {
Text(
"Here is an email link \(emailLink)"
)
Button(action: {
showAlert = true
}) {
Text("MENU")
}
.alert(isPresented: $showAlert, content: {
Alert(
title: Text("Title Here"),
message: Text(
"Need Help, \(emailLink)"
)
)
})
})
}
}
Without an Alert, based on the suggestive comment of George this works:
var body: some View {
Text(
"Here is an email link "
)
Link("jon.doe#mail.com", destination: URL(string: "mailto:jon.doe#mail.com")!)
You can use Environment var openURL for this, documentation here.
struct ContentView: View {
#Environment(\.openURL) var openURL
#State var alert: Bool = false
var body: some View {
HStack {
Button(action: {
alert.toggle()
}, label: {
Label("Email", systemImage: "envelope.fill")
})
}
.alert("Contact", isPresented: $alert, actions: {
Button(action: {
mailto("jon.doe#mail.com")
}, label: {
Label("Email", systemImage: "envelope.fill")
})
Button("Cancel", role: .cancel, action: {})
})
}
func mailto(_ email: String) {
let mailto = "mailto:\(email)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
print(mailto ?? "")
if let url = URL(string: mailto!) {
openURL(url)
}
}
}
I have register screen in SwiftUI, and I want to use two register screen for users, one view have mail and password, and other view have name surname and occupation, but I am still do not understand how I will connect both register view, any idea?
RegistrationViewModel:
enum RegistrationState {
case successfullyRegistered
case failed(error: Error)
case na
}
protocol RegistrationViewModel {
func create()
var service: RegistrationService { get }
var state: RegistrationState { get }
var hasError: Bool { get }
var newUser: RegistrationCredentials { get }
init(service: RegistrationService)
}
final class RegistrationViewModelImpl: ObservableObject, RegistrationViewModel {
let service: RegistrationService
#Published var state: RegistrationState = .na
#Published var newUser = RegistrationCredentials(email: "",
password: "",
firstName: "",
lastName: "",
occupation: "")
#Published var hasError: Bool = false
private var subscriptions = Set<AnyCancellable>()
init(service: RegistrationService) {
self.service = service
setupErrorSubscription()
}
func create() {
service
.register(with: newUser)
.sink { [weak self] res in
switch res {
case .failure(let error):
self?.state = .failed(error: error)
default: break
}
} receiveValue: { [weak self] in
self?.state = .successfullyRegistered
}
.store(in: &subscriptions)}}
private extension RegistrationViewModelImpl {
func setupErrorSubscription() {
$state
.map { state -> Bool in
switch state {
case .successfullyRegistered,
.na:
return false
case .failed:
return true}}
.assign(to: &$hasError)}}
firstscreen:
struct RegisterView: View {
#StateObject private var viewModel = RegistrationViewModelImpl(
service: RegistrationServiceImpl()
)
var body: some View {
NavigationView {
VStack(spacing: 32) {
VStack(spacing: 16) {
InputTextFieldView(text: $viewModel.newUser.email,
placeholder: "Email",
keyboardType: .emailAddress,
systemImage: "envelope")
InputPasswordView(password: $viewModel.newUser.password,
placeholder: "Password",
systemImage: "lock")
}
ButtonView(title: "Next") {
viewModel.create()
}
}
.padding(.horizontal, 15)
.alert(isPresented: $viewModel.hasError,
content: {
if case .failed(let error) = viewModel.state {
return Alert(
title: Text("Error"),
message: Text(error.localizedDescription))
} else {
return Alert(
title: Text("Error"),
message: Text("Something went wrong"))
}})}}}
secondscreen:
import SwiftUI
struct SecondRegisterView: View {
#StateObject private var viewModel = RegistrationViewModelImpl(
service: RegistrationServiceImpl()
)
var body: some View {
NavigationView {
VStack(spacing: 32) {
VStack(spacing: 16) {
InputTextFieldView(text: $viewModel.newUser.firstName,
placeholder: "First Name",
keyboardType: .namePhonePad,
systemImage: nil)
InputTextFieldView(text: $viewModel.newUser.lastName,
placeholder: "Last Name",
keyboardType: .namePhonePad,
systemImage: nil)
InputTextFieldView(text: $viewModel.newUser.occupation,
placeholder: "Occupation",
keyboardType: .namePhonePad,
systemImage: nil)
}
ButtonView(title: "Sign up") {
viewModel.create()
}
}
.padding(.horizontal, 15)
.navigationTitle("Register")
.applyClose()
.alert(isPresented: $viewModel.hasError,
content: {
if case .failed(let error) = viewModel.state {
return Alert(
title: Text("Error"),
message: Text(error.localizedDescription))
} else {
return Alert(
title: Text("Error"),
message: Text("Something went wrong"))
}
})
}
}
}
The problem lies in these declarations in your views:
#StateObject private var viewModel = RegistrationViewModelImpl(...)
In RegisterView and SecondRegisterView you are using two different instances of your view model, so you will have two different sets of data between your views.
What you need to do is to create only once your view model in the parent view of your app (the view that calls the registration views), then pass the view model to both views.
In your parent view (the one that calls RegisterView and SecondRegisterView):
// Use this declaration ONLY ONCE in your code for registration purposes
#StateObject private var viewModel = RegistrationViewModelImpl(
service: RegistrationServiceImpl()
)
In RegisterView and in SecondRegisterView, replace the command above with:
#ObservedObject var viewModel: RegistrationViewModelImpl
When calling both views from the parent view, pass your view model, like this:
RegisterView(viewModel: viewModel)
and
SecondRegisterView(viewModel: viewModel)
I want to delete list items and when I delete list items, it will show confirmation dialog like .alert dialog. I have code below and if I want to remove list item .alert dialog is work, but if I try to remove all list items, .alert dialog not work, and I am not able to remove all items, I do not know where I missed? I guess most probably it is due to the I have two .alert dialog and they are conflicted, any idea?
struct CustomView: View {
#State private var selectedUsers: CustomModel?
#State var users: [CustomModel]
#State private var selectDelete = false
#State private var selectAllDelete = false
var body: some View {
ScrollView(.vertical, showsIndicators: false, content: {
VStack(content: {
ForEach(users){ user in
CustomRowView(user: user)
.contextMenu {
Button(action: {
selectDelete = true
}) {
Text("remove")
}
Button(action: {
selectAllDelete = true
}) {
Text("remove all")
}
}
.alert(isPresented: $selectDelete) {
Alert(title: Text("title"),
message: Text("message"),
primaryButton: .destructive(Text("Delete")) {
self.delete(item: data)
},
secondaryButton: .cancel()
)
}
.alert(isPresented: $selectAllDelete) {
Alert(title: Text("title"),
message: Text("message"),
primaryButton: .destructive(Text("Delete")) {
self.datas.removeAll()
},
secondaryButton: .cancel()
)
}
.onDelete { (indexSet) in
self.users.remove(atOffsets: indexSet)
}
}
})
})
}
private func delete(item user: CustomModel) {
if let index = users.firstIndex(where: { $0.id == user.id }) {
users.remove(at: index)
}
}
}
model:
struct CustomModel: Identifiable{
var id = UUID().uuidString
var name: String
}
var users = [
CustomModel(name: "david"),
CustomModel(name: "marry"),
CustomModel(name: "henry"),
CustomModel(name: "nadi"), ]
You can create an alert type and handle it using switch statement.
enum AlertType {
case selectDelete
case selectAllDelete
}
private var alertType: AlertType?
#State private var isAlertPresented = false
...
Button(action: {
alertType = .selectDelete
isAlertPresented = true
}) {
Text("remove all")
}
...
.alert(isPresented: $isAlertPresented) {
presentAlert()
}
...
func presentAlert() -> Alert {
switch alertType {
case .selectDelete:
return Alert(title: Text("title"),
message: Text("message"),
primaryButton: .destructive(Text("Delete")) {
self.delete(item: data)
},
secondaryButton: .cancel())
case .selectAllDelete:
return Alert(title: Text("title"),
message: Text("message"),
primaryButton: .destructive(Text("Delete")) {
self.datas.removeAll()
},
secondaryButton: .cancel())
default:
return Alert(title: Text(""))
}
}
If you apply the modifier to each Button it'll work. Also, you might find confirmationDialog more suitable for this task.
Move your Buttons into custom Views will help too because body has a 10 View limit.
I understand there is PresentationButton and NavigationButton in order to change views in the latest SwiftUI. However I want to do a simple operation like below. When user clicks on SignIn button if credentials are correct it will sign them in but also do a segue (in this case change the view). However I could not check if they are correct in PresentationButton and I could not change the view in a normal button.
Is there another way to do that?
#IBAction func signInClicked(_ sender: Any) {
if emailText.text != "" && passwordText.text != "" {
Auth.auth().signIn(withEmail: emailText.text!, password: passwordText.text!) { (userdata, error) in
if error != nil {
//error
} else {
performSegue(withIdentifier: "toFeedActivity", sender: nil)
}
}
} else {
//error
}
}
Here's one way.
struct AppContentView: View {
#State var signInSuccess = false
var body: some View {
return Group {
if signInSuccess {
AppHome()
}
else {
LoginFormView(signInSuccess: $signInSuccess)
}
}
}
}
struct LoginFormView : View {
#State private var userName: String = ""
#State private var password: String = ""
#State private var showError = false
#Binding var signInSuccess: Bool
var body: some View {
VStack {
HStack {
Text("User name")
TextField("type here", text: $userName)
}.padding()
HStack {
Text(" Password")
TextField("type here", text: $password)
.textContentType(.password)
}.padding()
Button(action: {
// Your auth logic
if(self.userName == self.password) {
self.signInSuccess = true
}
else {
self.showError = true
}
}) {
Text("Sign in")
}
if showError {
Text("Incorrect username/password").foregroundColor(Color.red)
}
}
}
}
struct AppHome: View {
var body: some View {
VStack {
Text("Hello freaky world!")
Text("You are signed in.")
}
}
}
I had the same need in one of my app and I've found a solution...
Basically you need to insert your main view in a NavigationView, then add an invisible NavigationLink in you view, create a #state var that controls when you want to push the view and change it's value on your login callback...
That's the code:
struct ContentView: View {
#State var showView = false
var body: some View {
NavigationView {
VStack {
Button(action: {
print("*** Login in progress... ***")
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.showView = true
}
}) {
Text("Push me and go on")
}
//MARK: - NAVIGATION LINKS
NavigationLink(destination: PushedView(), isActive: $showView) {
EmptyView()
}
}
}
}
}
struct PushedView: View {
var body: some View {
Text("This is your pushed view...")
.font(.largeTitle)
.fontWeight(.heavy)
}
}
Try with state & .sheet
struct ContentView: View {
#State var showingDetail = false
var body: some View {
Button(action: {
self.showingDetail.toggle()
}) {
Text("Show Detail")
}.sheet(isPresented: $showingDetail) {
DetailView()
}
}
}
You can use navigation link with tags so,
Here is the code:
first of all, declare tag var
#State var tag : Int? = nil
then create your button view:
Button("Log In", action: {
Auth.auth().signIn(withEmail: self.email, password: self.password, completion: { (user, error) in
if error == nil {
self.tag = 1
print("success")
}else{
print(error!.localizedDescription)
}
})
So when log in success tag will become 1 and when tag will become 1 your navigation link will get executed
Navigation Link code:
NavigationLink(destination: HomeView(), tag: 1, selection: $tag) {
EmptyView()
}.disabled(true)
if you are using Form use .disabled because here the empty view will be visible on form and you don't want your user to click on it and go to the homeView.