I'm trying to create a SwiftUI alert dialog that contains a picker view inside of it, similar to the example here https://github.com/zhiyao92/PickerView-on-AlertController.
This is the code I've built so far in PickerAlert.swift
import SwiftUI
public struct PickerAlert {
public var title: String = "" // Title of the dialog
public var message: String = "" // Dialog message
public var pickerData: [String] = ["red", "green"] // Data for the picker
public var action: (String?) -> Void // Triggers when either of the two buttons closes the dialog
}
extension UIAlertController {
convenience init(alert: PickerAlert) {
self.init(title: alert.title, message: alert.message, preferredStyle: .alert)
let pickerFrame = UIPickerView(frame: CGRect(x: 5, y: 20, width: 250, height: 140))
pickerFrame.reloadAllComponents()
// Set the picker view as the accessory view of the alert controller
self.view.addSubview(pickerFrame)
self.view.translatesAutoresizingMaskIntoConstraints = false
self.view.addConstraints([
pickerFrame.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 60),
pickerFrame.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20),
pickerFrame.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20),
pickerFrame.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -50)
])
addAction(UIAlertAction(title: "Cancel", style: .destructive) { _ in
alert.action(nil)
})
addAction(UIAlertAction(title: "OK", style: .default) { _ in
alert.action("dummy data")
})
}
}
struct PickerAlertWrapper<Content: View>: UIViewControllerRepresentable {
@Binding var isPresented: Bool
let alert: PickerAlert
let content: Content
func makeUIViewController(context: UIViewControllerRepresentableContext<PickerAlertWrapper>) -> UIHostingController<Content> {
UIHostingController(rootView: content)
}
final class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
var alertController: UIAlertController?
let alert: PickerAlert
init(alert: PickerAlert, controller: UIAlertController? = nil) {
self.alert = alert
self.alertController = controller
super.init()
// Create the picker view instance and add it as a subview to the alert controller's view
if let pickerFrame = alertController?.view.subviews.first as? UIPickerView {
pickerFrame.dataSource = self
pickerFrame.delegate = self
pickerFrame.reloadAllComponents()
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return alert.pickerData.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return alert.pickerData[row]
}
}
func makeCoordinator() -> Coordinator {
let coordinator = Coordinator(alert: alert)
return coordinator
}
func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: UIViewControllerRepresentableContext<PickerAlertWrapper>) {
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)
}
}
}
extension View {
public func alert(isPresented: Binding<Bool>, _ alert: PickerAlert) -> some View {
PickerAlertWrapper(isPresented: isPresented, alert: alert, content: self)
}
}
This is the code of a SwiftUI view that shows the alert with the picker.
struct MyView: View {
@StateObject var viewModel = MyViewModel()
var body: some View {
ZStack {
Text("")
.hidden()
.alert(isPresented: $viewModel.showSelectHomeAlert,
PickerAlert(
title: "asdf"
) { selection in
if let selection = selection {
print("selection: \(selection)")
}
}
)
...
}
}
}
This is a screenshot of the output I'm seeing. It doesn't look like the UIPickerView is getting populated with the default values of "red" and "green" that I put. How can I get this to work?

I did something similar. I have the picker in two different alerts. I did not add the alert. as you did in your code below. I'm new to this stuff so take my answer with a grain of salt