SwiftUI using .alert(isPresented) with a TextField for input?
extension UIAlertController {
convenience init(alert: AlertConfig) {
self.init(title: alert.title, message: nil, preferredStyle: .alert)
addTextField { $0.placeholder = alert.placeholder }
addAction(UIAlertAction(title: alert.cancel, style: .cancel) { _ in
alert.action(nil)
})
let textField = self.textFields?.first
addAction(UIAlertAction(title: alert.accept, style: .default) { _ in
alert.action(textField?.text)
})
}
}
struct AlertHelper<Content: View>: UIViewControllerRepresentable {
@Binding var isPresented: Bool
let alert: AlertConfig
let content: Content
func makeUIViewController(context: UIViewControllerRepresentableContext<AlertHelper>) -> UIHostingController<Content> {
UIHostingController(rootView: content)
}
final class Coordinator {
var alertController: UIAlertController?
init(_ controller: UIAlertController? = nil) {
self.alertController = controller
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: UIViewControllerRepresentableContext<AlertHelper>) {
uiViewController.rootView = content
if isPresented && uiViewController.presentedViewController == nil {
var alert = self.alert
alert.action = {
self.isPresented = false
self.alert.action($0)
}
context.coordinator.alertController = UIAlertController(alert: alert)
uiViewController.present(context.coordinator.alertController!, animated: true)
}
if !isPresented && uiViewController.presentedViewController == context.coordinator.alertController {
uiViewController.dismiss(animated: true)
}
}
}
public struct AlertConfig {
public var title: String
public var placeholder: String = ""
public var accept: String = "OK"
public var cancel: String = "Cancel"
public var action: (String?) -> ()
}
extension View {
public func alert(isPresented: Binding<Bool>, _ alert: AlertConfig) -> some View {
AlertHelper(isPresented: isPresented, alert: alert, content: self)
}
}
struct ContentView: View {
@State var isAlert = false
var body: some View {
VStack {
Button("Show Alert") {
self.isAlert = true
}
}
.alert(isPresented: $isAlert, AlertConfig(title: "Title", action: {
print("Text \($0 ?? "Cancelled")")
}))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
How to add a TextField to Alert in SwiftUI?
As the Alert
view provided by SwiftUI
doesn't do the job you will need indeed to use UIAlertController
from UIKit
. Ideally we want a TextFieldAlert
view that we can presented in the same way we would present the Alert
provided by SwiftUI
:
struct MyView: View {
@Binding var alertIsPresented: Bool
@Binding var text: String? // this is updated as the user types in the text field
var body: some View {
Text("My Demo View")
.textFieldAlert(isPresented: $alertIsPresented) { () -> TextFieldAlert in
TextFieldAlert(title: "Alert Title", message: "Alert Message", text: self.$text)
}
}
}
We can achieve this writing a couple of classes and adding a modifier in a View
extension.
1) TextFieldAlertViewController
creates a UIAlertController
(with a text field of course) and presents it when it appears on screen. User changes to the text field are reflected into a Binding<String>
that is passed during initializazion.
class TextFieldAlertViewController: UIViewController {
/// Presents a UIAlertController (alert style) with a UITextField and a `Done` button
/// - Parameters:
/// - title: to be used as title of the UIAlertController
/// - message: to be used as optional message of the UIAlertController
/// - text: binding for the text typed into the UITextField
/// - isPresented: binding to be set to false when the alert is dismissed (`Done` button tapped)
init(title: String, message: String?, text: Binding<String?>, isPresented: Binding<Bool>?) {
self.alertTitle = title
self.message = message
self._text = text
self.isPresented = isPresented
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Dependencies
private let alertTitle: String
private let message: String?
@Binding private var text: String?
private var isPresented: Binding<Bool>?
// MARK: - Private Properties
private var subscription: AnyCancellable?
// MARK: - Lifecycle
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentAlertController()
}
private func presentAlertController() {
guard subscription == nil else { return } // present only once
let vc = UIAlertController(title: alertTitle, message: message, preferredStyle: .alert)
// add a textField and create a subscription to update the `text` binding
vc.addTextField { [weak self] textField in
guard let self = self else { return }
self.subscription = NotificationCenter.default
.publisher(for: UITextField.textDidChangeNotification, object: textField)
.map { ($0.object as? UITextField)?.text }
.assign(to: \.text, on: self)
}
// create a `Done` action that updates the `isPresented` binding when tapped
// this is just for Demo only but we should really inject
// an array of buttons (with their title, style and tap handler)
let action = UIAlertAction(title: "Done", style: .default) { [weak self] _ in
self?.isPresented?.wrappedValue = false
}
vc.addAction(action)
present(vc, animated: true, completion: nil)
}
}
2) TextFieldAlert
wraps TextFieldAlertViewController
using the UIViewControllerRepresentable
protocol so that it can be used within SwiftUI.
struct TextFieldAlert {
// MARK: Properties
let title: String
let message: String?
@Binding var text: String?
var isPresented: Binding<Bool>? = nil
// MARK: Modifiers
func dismissable(_ isPresented: Binding<Bool>) -> TextFieldAlert {
TextFieldAlert(title: title, message: message, text: $text, isPresented: isPresented)
}
}
extension TextFieldAlert: UIViewControllerRepresentable {
typealias UIViewControllerType = TextFieldAlertViewController
func makeUIViewController(context: UIViewControllerRepresentableContext<TextFieldAlert>) -> UIViewControllerType {
TextFieldAlertViewController(title: title, message: message, text: $text, isPresented: isPresented)
}
func updateUIViewController(_ uiViewController: UIViewControllerType,
context: UIViewControllerRepresentableContext<TextFieldAlert>) {
// no update needed
}
}
3) TextFieldWrapper
is a simple ZStack
with a TextFieldAlert
on the back (only if isPresented
is true) and a presenting view on the front. The presenting view is the only one visibile.
struct TextFieldWrapper<PresentingView: View>: View {
@Binding var isPresented: Bool
let presentingView: PresentingView
let content: () -> TextFieldAlert
var body: some View {
ZStack {
if (isPresented) { content().dismissable($isPresented) }
presentingView
}
}
}
4) The textFieldAlert
modifier allows us to smoothly wrap any SwiftUI view in a TextFieldWrapper
and obtain the desired behaviour.
extension View {
func textFieldAlert(isPresented: Binding<Bool>,
content: @escaping () -> TextFieldAlert) -> some View {
TextFieldWrapper(isPresented: isPresented,
presentingView: self,
content: content)
}
}
Is it possible to save the text entered into an alert's textfield in SwiftUI and display it in the app like this - Text( alert text input here )?
You will want to grab the value of the text field from inside the Done button's handler. So all you need to do is move the bracket down:
alert.addAction(UIAlertAction(title: "Done", style: .default) { _ in
let textField = alert.textFields![0] as UITextField
alertInput = textField.text ?? "Name"
})
The code inside this closure is called when the user taps this UIAlertAction you have set up.
Note that if the field is left empty, the value of textField.text
will be ""
and not nil
, so in order to use the default value you provide, you may need some additional logic here to check for a blank string as well.
SwiftUI: UIAlertController's textField does not responding in UIAlertAction
Here is full demo module for solution that works. Tested with Xcode 11.4 / iOS 13.4
See also important comments inline
struct AlertControl: UIViewControllerRepresentable {
@Binding var textString: String
@Binding var show: Bool
var title: String
var message: String
func makeUIViewController(context: UIViewControllerRepresentableContext<AlertControl>) -> UIViewController {
return UIViewController() // holder controller - required to present alert
}
func updateUIViewController(_ viewController: UIViewController, context: UIViewControllerRepresentableContext<AlertControl>) {
guard context.coordinator.alert == nil else { return }
if self.show {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
context.coordinator.alert = alert
alert.addTextField { textField in
textField.placeholder = "Enter some text"
textField.text = self.textString // << initial value if any
textField.delegate = context.coordinator // << use coordinator as delegate
}
alert.addAction(UIAlertAction(title: "cancel", style: .destructive) { _ in
// your action here
})
alert.addAction(UIAlertAction(title: "Submit", style: .default) { _ in
// your action here
})
DispatchQueue.main.async { // must be async !!
viewController.present(alert, animated: true, completion: {
self.show = false // hide holder after alert dismiss
context.coordinator.alert = nil
})
}
}
}
func makeCoordinator() -> AlertControl.Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var alert: UIAlertController?
var control: AlertControl
init(_ control: AlertControl) {
self.control = control
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text as NSString? {
self.control.textString = text.replacingCharacters(in: range, with: string)
} else {
self.control.textString = ""
}
return true
}
}
}
// Demo view for Alert Controll
struct DemoAlertControl: View {
@State private var text = ""
@State private var showAlert = false
var body: some View {
VStack {
Button("Alert") { self.showAlert = true }
.background(AlertControl(textString: self.$text, show: self.$showAlert,
title: "Title goes here", message: "Message goes here"))
Text(self.text)
}
}
}
config 2 alerts messages in button swiftUI
For multiple alerts in a single view, you can use an enum.
First, you need to create an enum like this and define all the alert message
enum AlertType: Identifiable {
var id: UUID {
return UUID()
}
case success
case error
var title: String {
switch self {
case .success:
return "Success !"
case .error:
return "Error !"
}
}
var message: String {
switch self {
case .success:
return "Insert with success !"
case .error:
return "This category already exist !!"
}
}
}
now create one state var in the view.
struct NewCategoryView: View {
@State private var alertType: AlertType?
// Other code
}
and add the alert at the end
//end Vstak
.navigationTitle("New Category")
.onAppear(perform: { updateCategoryList()} )
.alert(item: self.$alertType, content: { (type) -> Alert in
Alert(title: Text(type.title), message: Text(type.message),
dismissButton: .default(Text("OK")))
})
now show the alert by assigning the value. Like this
if condition_true {
alertType = AlertType.success //<-- Here
} else {
alertType = AlertType.error //<-- Here
}
Present alert on previous screen SwiftUI
you could try this approach using a simple binding:
import SwiftUI
import PopupView
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView : View {
var body: some View {
NavigationView {
HelpView()
}
}
}
struct HelpView: View {
@State var showPopup = false // <-- here
var body: some View {
ZStack {
Color.pink.ignoresSafeArea()
List {
NavigationLink(destination: FeedbackView(showPopup: $showPopup)) { // <-- here
Text("Report a problem").fontWeight(.semibold)
}
HStack {
Link(destination: URL(string: "https://sauced.app")!) {
Text("Help Center").fontWeight(.semibold)
}
Spacer()
Image(systemName: "chevron.right").foregroundColor(Color.black)
}
}
}
.popup(isPresented: $showPopup, autohideIn: 3) {
Text("THANK YOU")
.frame(width: 200, height: 60)
.background(Color(red: 0.85, green: 0.8, blue: 0.95))
.cornerRadius(30.0)
}
.listStyle(InsetGroupedListStyle())
.navigationBarTitle("Help")
}
}
struct FeedbackView : View {
@Environment(\.dismiss) var dismiss
@Binding var showPopup: Bool // <-- here
var body: some View {
Button(action: {
showPopup = true // <-- here
dismiss()
}, label: {
Text("Send").fontWeight(.semibold)
})
}
}
Get input value from TextField in iOS alert in Swift
Updated for Swift 3 and above:
//1. Create the alert controller.
let alert = UIAlertController(title: "Some Title", message: "Enter a text", preferredStyle: .alert)
//2. Add the text field. You can configure it however you need.
alert.addTextField { (textField) in
textField.text = "Some default text"
}
// 3. Grab the value from the text field, and print it when the user clicks OK.
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak alert] (_) in
let textField = alert.textFields![0] // Force unwrapping because we know it exists.
print("Text field: \(textField.text)")
}))
// 4. Present the alert.
self.present(alert, animated: true, completion: nil)
Swift 2.x
Assuming you want an action alert on iOS:
//1. Create the alert controller.
var alert = UIAlertController(title: "Some Title", message: "Enter a text", preferredStyle: .Alert)
//2. Add the text field. You can configure it however you need.
alert.addTextFieldWithConfigurationHandler({ (textField) -> Void in
textField.text = "Some default text."
})
//3. Grab the value from the text field, and print it when the user clicks OK.
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: { [weak alert] (action) -> Void in
let textField = alert.textFields![0] as UITextField
println("Text field: \(textField.text)")
}))
// 4. Present the alert.
self.presentViewController(alert, animated: true, completion: nil)
Related Topics
How to Read a Property List from Data in Swift 3
Suppressing Implicit Returns in Swift
How to Implement Protocol Methods That Return Covariant Selfs
Swiftui Label Text and Image Vertically Misaligned
Swift: Programmatically Enumerate Outgoing Segues from a Uiviewcontroller
Wkwebview Auto Fill Login Form Swift 2
Accessor Gives the Wrong Value in Swift 1.2/2.0 Release Build Only
Core Data Predicate Not Working
How to Encode an Unmanaged<Seckey> to Base64 to Send to Another Server
How to Speed Up Updating Relationship Among Tables, After One or Both Tables Are Already Saved
How to Demonstrate a Zombie Object in Swift
Swift 2.0 Keychain Type Errors for Secitemcopymatching
Lazy Property Initialization in Swift
Swift 4: Nsfilenamespboardtype Not Available. What to Use Instead for Registerfordraggedtypes