Anyone an idea how to create an Alert in SwiftUI that contains a TextField?
-
Here you go https://gist.github.com/gurjitdhiman/38ee1be49e5767cdf8833e4c68094e02 – Gurjit Singh Jul 20 '23 at 11:45
15 Answers
Alert
is quite limited at the moment, but you can roll your own solution in pure SwiftUI.
Here's a simple implementation of a custom alert with a text field.
struct TextFieldAlert<Presenting>: View where Presenting: View {
@Binding var isShowing: Bool
@Binding var text: String
let presenting: Presenting
let title: String
var body: some View {
GeometryReader { (deviceSize: GeometryProxy) in
ZStack {
self.presenting
.disabled(isShowing)
VStack {
Text(self.title)
TextField(self.title, text: self.$text)
Divider()
HStack {
Button(action: {
withAnimation {
self.isShowing.toggle()
}
}) {
Text("Dismiss")
}
}
}
.padding()
.background(Color.white)
.frame(
width: deviceSize.size.width*0.7,
height: deviceSize.size.height*0.7
)
.shadow(radius: 1)
.opacity(self.isShowing ? 1 : 0)
}
}
}
}
And a View
extension to use it:
extension View {
func textFieldAlert(isShowing: Binding<Bool>,
text: Binding<String>,
title: String) -> some View {
TextFieldAlert(isShowing: isShowing,
text: text,
presenting: self,
title: title)
}
}
Demo:
struct ContentView : View {
@State private var isShowingAlert = false
@State private var alertInput = ""
var body: some View {
NavigationView {
VStack {
Button(action: {
withAnimation {
self.isShowingAlert.toggle()
}
}) {
Text("Show alert")
}
}
.navigationBarTitle(Text("A List"), displayMode: .large)
}
.textFieldAlert(isShowing: $isShowingAlert, text: $alertInput, title: "Alert!")
}
}

- 4,572
- 1
- 29
- 35

- 21,796
- 7
- 67
- 74
-
this gives me Abort 6 error. result not found Cross-reference to module 'SwiftUI' ... View ... in an extension in module ... opaque return type of – MobileMon Jun 26 '19 at 19:40
-
@MobileMon please double check your code - I just tested this on macoS 10.15 b2 / Xcode 11 b2 and it compiles fine without issues. Tested on both sim and device. – Matteo Pacini Jun 27 '19 at 03:46
-
4This compiles for me but the TextField won't allow editing. It's like it's disabled or something. – drewster Dec 26 '19 at 20:32
-
12I had to change `TextField(self.$text)` to `TextField(self.title, text: self.$text)` to make it compile but I cannot edit the text either. – simibac Feb 13 '20 at 14:16
-
1@simibac You can find the fix here: https://stackoverflow.com/questions/59493767/swiftui-textfield-acts-disabled-in-vstack-inside-zstack-simulating-an-alert-wit – Alexey Mar 15 '20 at 14:13
-
2to fix editing, I changed "let presenting: () -> Presenting " " self.presenting() .blur(radius: self.isShowing ? 2 : 0) .disabled(self.isShowing)" and TextFieldAlert(isShowing: isShowing, text: text, presenting: { self }, – Simone Pistecchia Apr 01 '20 at 14:00
-
Works except for the extra ".textFieldAlert" function. That function ends up replacing the NavigationView since the method returns a totally different view. – Rob Barber Apr 10 '22 at 01:51
-
This works... but for some reason it kills all the animation when I put my List in and out of edit mode. Very weird. – Peter Johnson Apr 16 '22 at 11:52
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)
}
}

- 2,557
- 20
- 31
-
8
-
-
Anyone used this with the .alert(item: Item, alert: (Item) -> Alert) API? – Will Alexander Jul 16 '21 at 10:00
-
I am handling this alert under a button. Somehow I can capture the value that was entered in the modal. How can I make sure get the value from it after tapping the button from the modal? – user4150758 Oct 18 '21 at 16:43
iOS 16+
struct ContentView: View {
@State private var presentAlert = false
@State private var username: String = ""
@State private var password: String = ""
var body: some View {
Button("Show Alert") {
presentAlert = true
}
.alert("Login", isPresented: $presentAlert, actions: {
TextField("Username", text: $username)
SecureField("Password", text: $password)
Button("Login", action: {})
Button("Cancel", role: .cancel, action: {})
}, message: {
Text("Please enter your username and password.")
})
}
}

- 3,254
- 2
- 22
- 61
You can simply use UIAlertController
directly. No need to roll your own alert dialog UI:
private func alert() {
let alert = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
alert.addTextField() { textField in
textField.placeholder = "Enter some text"
}
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in })
showAlert(alert: alert)
}
func showAlert(alert: UIAlertController) {
if let controller = topMostViewController() {
controller.present(alert, animated: true)
}
}
private func keyWindow() -> UIWindow? {
return UIApplication.shared.connectedScenes
.filter {$0.activationState == .foregroundActive}
.compactMap {$0 as? UIWindowScene}
.first?.windows.filter {$0.isKeyWindow}.first
}
private func topMostViewController() -> UIViewController? {
guard let rootController = keyWindow()?.rootViewController else {
return nil
}
return topMostViewController(for: rootController)
}
private func topMostViewController(for controller: UIViewController) -> UIViewController {
if let presentedController = controller.presentedViewController {
return topMostViewController(for: presentedController)
} else if let navigationController = controller as? UINavigationController {
guard let topController = navigationController.topViewController else {
return navigationController
}
return topMostViewController(for: topController)
} else if let tabController = controller as? UITabBarController {
guard let topController = tabController.selectedViewController else {
return tabController
}
return topMostViewController(for: topController)
}
return controller
}
Most of this code is just boilerplate to find the ViewController that should present the alert.
Call alert()
e.g. from the action
of a button:
struct TestView: View {
var body: some View {
Button(action: { alert() }) { Text("click me") }
}
}
Please beware though that there seems to be a bug in beta 5 and onward that can sometimes cause the emulator to freeze once a text field is shown: Xcode 11 beta 5: UI freezes when adding textFields into UIAlertController

- 2,702
- 26
- 36
-
1This works fine in both iOS and MacOs. Also in MacOS this standard alert is very unstable, but solid in iOS: `.alert(isPresented: $showingAlert) { Alert(title: Text("title"), message: Text("message"), primaryButton: .default(Text("Ok")), secondaryButton: .cancel (Text("Cancel")))}` why is the question? – Jan Bergström Mar 14 '20 at 03:25
-
1
-
1This is a decent answer, however this breaks my layout, once the dialog is shown, until the views state is changed again. It causes the spacing on the left and right side of the view to become larger... – frankenapps May 01 '20 at 04:41
-
1
-
1@Genki `alert.textFields!.first!.text!`. Reference: https://medium.com/swift-india/uialertcontroller-in-swift-22f3c5b1dd68 – Ignis Incendio May 30 '22 at 13:13
I found modals and alerts in SwiftUI to be lacking several features. For instance, there doesn't seem to be a way of presenting a modal with FormSheet style.
When I need to present a complex alert (such as one with textfields), I create a pure SwiftUI view with all the content of the alert, and then present it as a FormSheet using a UIHostController.
If you do not have a UIViewController around to call present(), you can always use the root view controller.
With this approach you get some nice features, such as the standard alert animation both going in, and out. You can also drag the alert down to dismiss it.
The alert view also moves up when the keyboard appears.
This works nicely on iPad. On iPhone, FormSheet is full screen so you may need to tweak the code to find a solution. I think this will give you a good starting point.
It is something like this:
struct ContentView : View {
@State private var showAlert = false
var body: some View {
VStack {
Button(action: {
let alertHC = UIHostingController(rootView: MyAlert())
alertHC.preferredContentSize = CGSize(width: 300, height: 200)
alertHC.modalPresentationStyle = UIModalPresentationStyle.formSheet
UIApplication.shared.windows[0].rootViewController?.present(alertHC, animated: true)
}) {
Text("Show Alert")
}
}
}
}
struct MyAlert: View {
@State private var text: String = ""
var body: some View {
VStack {
Text("Enter Input").font(.headline).padding()
TextField($text, placeholder: Text("Type text here")).textFieldStyle(.roundedBorder).padding()
Divider()
HStack {
Spacer()
Button(action: {
UIApplication.shared.windows[0].rootViewController?.dismiss(animated: true, completion: {})
}) {
Text("Done")
}
Spacer()
Divider()
Spacer()
Button(action: {
UIApplication.shared.windows[0].rootViewController?.dismiss(animated: true, completion: {})
}) {
Text("Cancel")
}
Spacer()
}.padding(0)
}.background(Color(white: 0.9))
}
}
If you find yourself using this a lot, the button row may be encapsulated in a separate view for easy reuse.

- 37,663
- 13
- 111
- 125
-
Any lead for iPhone workarounds? BTW iPad will also show it fullscreen when sharing screen with another app. – Rivera Oct 23 '19 at 13:39
-
4**Swift5 update** Update the TextField line to this ->. TextField("Type text here", text: $text).textFieldStyle(RoundedBorderTextFieldStyle()).padding() – uplearned.com Nov 10 '19 at 01:35
-
1By the way this will not work in the preview for the iPhone. You will have to build it in the simulator and it looks quite different for iPhone. the above only looks like this in the iPad simulator. – uplearned.com Nov 10 '19 at 01:43
-
1kontiki This appears to not work under Xcode 12.2. Create a new project and past the code in and the preferredContentSize value appears to get ignored. – xdeleon Dec 23 '20 at 03:41
Simple native solution for iOS
extension View {
public func textFieldAlert(
isPresented: Binding<Bool>,
title: String,
text: String = "",
placeholder: String = "",
action: @escaping (String?) -> Void
) -> some View {
self.modifier(TextFieldAlertModifier(isPresented: isPresented, title: title, text: text, placeholder: placeholder, action: action))
}
}
public struct TextFieldAlertModifier: ViewModifier {
@State private var alertController: UIAlertController?
@Binding var isPresented: Bool
let title: String
let text: String
let placeholder: String
let action: (String?) -> Void
public func body(content: Content) -> some View {
content.onChange(of: isPresented) { isPresented in
if isPresented, alertController == nil {
let alertController = makeAlertController()
self.alertController = alertController
guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return
}
scene.windows.first?.rootViewController?.present(alertController, animated: true)
} else if !isPresented, let alertController = alertController {
alertController.dismiss(animated: true)
self.alertController = nil
}
}
}
private func makeAlertController() -> UIAlertController {
let controller = UIAlertController(title: title, message: nil, preferredStyle: .alert)
controller.addTextField {
$0.placeholder = self.placeholder
$0.text = self.text
}
controller.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
self.action(nil)
shutdown()
})
controller.addAction(UIAlertAction(title: "OK", style: .default) { _ in
self.action(controller.textFields?.first?.text)
shutdown()
})
return controller
}
private func shutdown() {
isPresented = false
alertController = nil
}
}
Usage:
struct ContentView: View {
@State private var isRenameAlertPresented = false
@State private var title = "Old title"
var body: some View {
VStack {
Button("Rename title") {
isRenameAlertPresented = true
}
Text(title)
}
.textFieldAlert(
isPresented: $isRenameAlertPresented,
title: "Rename",
text: "Title",
placeholder: "",
action: { newText in
title = newText ?? ""
}
)
}
}

- 657
- 8
- 10
-
1I got this working and it does what I needed. However the example usage was fairly vague. I spent a while trying to troubleshoot why errors were popping up. You could show the example with a code block instead of `rename`. Also, the line `UIApplication.shared.windows.first?.rootViewController?.present(alertController, animated: true)` uses a deprecated approach. It should be replaced with `(UIApplication.shared.connectedScenes.first as! UIWindowScene).windows.first?.rootViewController?.present(alertController, animated: true)`. I thought another error happened too that was easily fixed. – Jason Cramer Feb 07 '22 at 16:10
-
3Thanks for the suggestions. I updated the example and removed deprecated API. – nightwill Feb 10 '22 at 09:54
-
-
It's very simple) Just add the code `$0.autocapitalizationType = .words` after `$0.text = self.text` – nightwill Mar 07 '22 at 06:29
Although not exactly the same, if all you're looking for is a native, modal-like view with an edit box, you could use a popover. It works out of the box (minus a sizing bug) without needing to traverse the view hierarchy.

- 86,719
- 67
- 308
- 465
As already was mentioned Alert
is provide not many functionality and so almost useless in any non-standard cases when using in SwiftUI.
I ended up with a bit extensive solution - View
that may behave as alert with high customisation level.
Create
ViewModel
for popUp:struct UniAlertViewModel { let backgroundColor: Color = Color.gray.opacity(0.4) let contentBackgroundColor: Color = Color.white.opacity(0.8) let contentPadding: CGFloat = 16 let contentCornerRadius: CGFloat = 12 }
we also need to configure buttons, for this purpose let's add one more type:
struct UniAlertButton { enum Variant { case destructive case regular } let content: AnyView let action: () -> Void let type: Variant var isDestructive: Bool { type == .destructive } static func destructive<Content: View>( @ViewBuilder content: @escaping () -> Content ) -> UniAlertButton { UniAlertButton( content: content, action: { /* close */ }, type: .destructive) } static func regular<Content: View>( @ViewBuilder content: @escaping () -> Content, action: @escaping () -> Void ) -> UniAlertButton { UniAlertButton( content: content, action: action, type: .regular) } private init<Content: View>( @ViewBuilder content: @escaping () -> Content, action: @escaping () -> Void, type: Variant ) { self.content = AnyView(content()) self.type = type self.action = action } }
add View that can become our customizable popUp:
struct UniAlert<Presenter, Content>: View where Presenter: View, Content: View { @Binding private (set) var isShowing: Bool let displayContent: Content let buttons: [UniAlertButton] let presentationView: Presenter let viewModel: UniAlertViewModel private var requireHorizontalPositioning: Bool { let maxButtonPositionedHorizontally = 2 return buttons.count > maxButtonPositionedHorizontally } var body: some View { GeometryReader { geometry in ZStack { backgroundColor() VStack { Spacer() ZStack { presentationView.disabled(isShowing) let expectedWidth = geometry.size.width * 0.7 VStack { displayContent buttonsPad(expectedWidth) } .padding(viewModel.contentPadding) .background(viewModel.contentBackgroundColor) .cornerRadius(viewModel.contentCornerRadius) .shadow(radius: 1) .opacity(self.isShowing ? 1 : 0) .frame( minWidth: expectedWidth, maxWidth: expectedWidth ) } Spacer() } } } } private func backgroundColor() -> some View { viewModel.backgroundColor .edgesIgnoringSafeArea(.all) .opacity(self.isShowing ? 1 : 0) } private func buttonsPad(_ expectedWidth: CGFloat) -> some View { VStack { if requireHorizontalPositioning { verticalButtonPad() } else { Divider().padding([.leading, .trailing], -viewModel.contentPadding) horizontalButtonsPadFor(expectedWidth) } } } private func verticalButtonPad() -> some View { VStack { ForEach(0..<buttons.count) { Divider().padding([.leading, .trailing], -viewModel.contentPadding) let current = buttons[$0] Button(action: { if !current.isDestructive { current.action() } withAnimation { self.isShowing.toggle() } }, label: { current.content.frame(height: 35) }) } } } private func horizontalButtonsPadFor(_ expectedWidth: CGFloat) -> some View { HStack { let sidesOffset = viewModel.contentPadding * 2 let maxHorizontalWidth = requireHorizontalPositioning ? expectedWidth - sidesOffset : expectedWidth / 2 - sidesOffset Spacer() if !requireHorizontalPositioning { ForEach(0..<buttons.count) { if $0 != 0 { Divider().frame(height: 44) } let current = buttons[$0] Button(action: { if !current.isDestructive { current.action() } withAnimation { self.isShowing.toggle() } }, label: { current.content }) .frame(maxWidth: maxHorizontalWidth, minHeight: 44) } } Spacer() } } }
to simplify usage let's add extension to
View
:extension View { func assemblyAlert<Content>( isShowing: Binding<Bool>, viewModel: UniAlertViewModel, @ViewBuilder content: @escaping () -> Content, actions: [UniAlertButton] ) -> some View where Content: View { UniAlert( isShowing: isShowing, displayContent: content(), buttons: actions, presentationView: self, viewModel: viewModel) } }
And usage:
struct ContentView: View {
@State private var isShowingAlert: Bool = false
@State private var text: String = ""
var body: some View {
VStack {
Button(action: {
withAnimation {
isShowingAlert.toggle()
}
}, label: {
Text("Show alert")
})
}
.assemblyAlert(isShowing: $isShowingAlert,
viewModel: UniAlertViewModel(),
content: {
Text("title")
Image(systemName: "phone")
.scaleEffect(3)
.frame(width: 100, height: 100)
TextField("enter text here", text: $text)
Text("description")
}, actions: buttons)
}
}
}
Demo:

- 10,908
- 11
- 91
- 124
-
I really liked this answer and have added it to my SwiftUI helpers. I have a few minor changes: (i) !requireHorizontalPositioning check in horizontalButtonsPadFor(..) not needed, (ii) I wanted the horizontal divider between two buttons to not have top nor bottom spaces like standard alerts... so VStack(spacing: 0) in buttonsPad(..) AND when !requireHorizontalPositioning in the main UniAlert body do not have "bottom" viewModel.contentPadding for the VStack with the content and button pad views AND removed the last Spacer below the ZStack. – Kpalser Nov 26 '20 at 08:43
-
thanks for comment, glad that this answer helps u. I will definitely check and update the code a bit later. Meanwhile u may also faced with case when u want to present this alert over full contenxt of your view. how to do this - u can find small article here - https://khorbushko.github.io/article/2020/11/24/present-view-over-full-context.html – hbk Nov 26 '20 at 10:18
This is an example based on the SwiftUI Sheet
class that displays a dialog with a prompt, a text field, and the classic OK and Dismiss button
First lets make our Dialog
class, which will pop when user want to edit a value:
import SwiftUI
struct Dialog: View {
@Environment(\.presentationMode) var presentationMode
/// Edited value, passed from outside
@Binding var value: String?
/// Prompt message
var prompt: String = ""
/// The value currently edited
@State var fieldValue: String
/// Init the Dialog view
/// Passed @binding value is duplicated to @state value while editing
init(prompt: String, value: Binding<String?>) {
_value = value
self.prompt = prompt
_fieldValue = State<String>(initialValue: value.wrappedValue ?? "")
}
var body: some View {
VStack {
Text(prompt).padding()
TextField("", text: $fieldValue)
.frame(width: 200, alignment: .center)
HStack {
Button("OK") {
self.value = fieldValue
self.presentationMode.wrappedValue.dismiss()
}
Button("Dismiss") {
self.presentationMode.wrappedValue.dismiss()
}
}.padding()
}
.padding()
}
}
#if DEBUG
struct Dialog_Previews: PreviewProvider {
static var previews: some View {
var name = "John Doe"
Dialog(prompt: "Name", value: Binding<String?>.init(get: { name }, set: {name = $0 ?? ""}))
}
}
#endif
Now we use it this way in the caller View:
import SwiftUI
struct ContentView: View {
/// Is the input dialog displayed
@State var dialogDisplayed = false
/// The name to edit
@State var name: String? = nil
var body: some View {
VStack {
Text(name ?? "Unnamed").frame(width: 200).padding()
Button(name == nil ? "Set Name" : "Change Name") {
dialogDisplayed = true
}
.sheet(isPresented: $dialogDisplayed) {
Dialog(prompt: name == nil ? "Enter a name" : "Enter a new name", value: $name)
}
.onChange(of: name, perform: { value in
print("Name Changed : \(value)")
}
.padding()
}
.padding()
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif

- 2,607
- 24
- 23
Step1: Make root view as ZStack
Step2: Add variable for show/hide
@State var showAlert = false
Step 3: Add this custom layout inside root view (ZStack)
if $showAlert.wrappedValue {
ZStack() {
Color.grayBackground
VStack {
//your custom layout text fields buttons
}.padding()
}
.frame(width: 300, height: 180,alignment: .center)
.cornerRadius(20).shadow(radius: 20)
}

- 16,071
- 12
- 120
- 159
Based on the idea of tanzolone
import Foundation
import Combine
import SwiftUI
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(isPresented: Binding<Bool>, alert: TextFieldAlert) {
self._isPresented = isPresented
self.alert = alert
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@Binding
private var isPresented: Bool
private var alert: TextFieldAlert
// 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: alert.title, message: alert.message, preferredStyle: .alert)
// add a textField and create a subscription to update the `text` binding
vc.addTextField {
// TODO: 需要补充这些参数
// $0.placeholder = alert.placeholder
// $0.keyboardType = alert.keyboardType
// $0.text = alert.defaultValue ?? ""
$0.text = self.alert.defaultText
}
if let cancel = alert.cancel {
vc.addAction(UIAlertAction(title: cancel, style: .cancel) { _ in
// self.action(nil)
self.isPresented = false
})
}
let textField = vc.textFields?.first
vc.addAction(UIAlertAction(title: alert.accept, style: .default) { _ in
self.isPresented = false
self.alert.action(textField?.text)
})
present(vc, animated: true, completion: nil)
}
}
struct TextFieldAlert {
let title: String
let message: String?
var defaultText: String = ""
public var accept: String = "好".localizedString // The left-most button label
public var cancel: String? = "取消".localizedString // The optional cancel (right-most) button label
public var action: (String?) -> Void // Triggers when either of the two buttons closes the dialog
}
struct AlertWrapper: UIViewControllerRepresentable {
@Binding var isPresented: Bool
let alert: TextFieldAlert
typealias UIViewControllerType = TextFieldAlertViewController
func makeUIViewController(context: UIViewControllerRepresentableContext<AlertWrapper>) -> UIViewControllerType {
TextFieldAlertViewController(isPresented: $isPresented, alert: alert)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: UIViewControllerRepresentableContext<AlertWrapper>) {
// no update needed
}
}
struct TextFieldWrapper<PresentingView: View>: View {
@Binding var isPresented: Bool
let presentingView: PresentingView
let content: TextFieldAlert
var body: some View {
ZStack {
if (isPresented) {
AlertWrapper(isPresented: $isPresented, alert: content)
}
presentingView
}
}
}
extension View {
func alert(isPresented: Binding<Bool>, _ content: TextFieldAlert) -> some View {
TextFieldWrapper(isPresented: isPresented, presentingView: self, content: content)
}
}
How to use
xxxView
.alert(isPresented: $showForm, TextFieldAlert(title: "添加分组", message: "") { (text) in
if text != nil {
self.saveGroup(text: text!)
}
})

- 21
- 1
HostingWindow+present
extension UIWindow {
public func showAlert(alertController: UIAlertController, placeholder: String, primaryTitle: String, cancelTitle: String, primaryAction: @escaping (String) -> Void) {
alertController.addTextField { textField in
textField.placeholder = placeholder
}
let primaryButton = UIAlertAction(title: primaryTitle, style: .default) { _ in
guard let text = alertController.textFields?[0].text else { return }
primaryAction(text)
}
let cancelButton = UIAlertAction(title: cancelTitle, style: .cancel, handler: nil)
alertController.addAction(primaryButton)
alertController.addAction(cancelButton)
self.rootViewController?.present(alertController, animated: true)
}
}

- 5,652
- 2
- 34
- 34
I created a swift package, swift-ui-accompanist that has a product AccompanistAlert that you can add to your project and use as below.
import AccompanistAlert
import SwiftUI
struct ContentView: View {
@State var showingAddBookUI = false
@State var newBookName = ""
var body: some View {
VStack {
Button(action: { showingAddBookUI.toggle() }) {
Image(systemName: "plus")
}
}
.accompanist.alert("Add Book", isPresented: $showingAddBookUI) {
TextField("Book Name", text: $newBookName)
Button("Save", action: {
// TODO: Save book to database
newBookName = ""
})
.disabled(newBookName.count < 3)
Button("Cancel", role: .cancel, action: {
newBookName = ""
})
}
}
Notice that the Alert is constructed using the Swift UI DSL you are familiar with and the API is similar to that provided by Apple.

- 1
- 2
Great question I myself have struggled finding a good way of adding a textfield to my Alerts. And I think I have "solved" it - I found a quite simple way of doing it and I like it, and I hope you will too.
struct AlertContent: View {
@State private var showingSimpleAlert = false
@State private var userInput: String = ""
var body: some View {
VStack {
Button(action: {
self.showingSimpleAlert = true
}) {
Text("Simple Alert")
}
.alert("Feedback", isPresented: $showingSimpleAlert) {
TextField("Enter user input", text: $userInput)
Button("SEND", action: sendUserInput)
} message: {
Text("Please give us feedback.")
}
}
}
func sendUserInput() {
print("Sending user input")
}
}
And you will get the following result: Image of Alert with a text field
If you want to learn more about alerts in SwiftUI feel free to check out my post here: https://softwareanders.com/swiftui-alert/

- 69
- 7
func dialog(){
let alertController = UIAlertController(title: "Contry", message: "Write contrt code here", preferredStyle: .alert)
alertController.addTextField { (textField : UITextField!) -> Void in
textField.placeholder = "Country code"
}
let saveAction = UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in
let secondTextField = alertController.textFields![0] as UITextField
print("county code : ",secondTextField)
})
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil )
alertController.addAction(saveAction)
alertController.addAction(cancelAction)
UIApplication.shared.windows.first?.rootViewController?.present(alertController, animated: true, completion: nil)
}
Usage
Button(action: { self.dialog()})
{
Text("Button")
.foregroundColor(.white).fontWeight(.bold)
}

- 621
- 2
- 7
- 16