SwiftUI Alert内嵌UIPickerView无数据填充问题求助
解决SwiftUI内嵌UIPickerView的Alert无法加载数据问题
问题背景
我尝试在SwiftUI中实现一个内嵌UIPickerView的Alert对话框,以下是编写的PickerAlert.swift代码:
import SwiftUI public struct PickerAlert { public var title: String = "" // 对话框标题 public var message: String = "" // 对话框消息 public var pickerData: [String] = ["red", "green"] // 选择器数据 public var action: (String?) -> Void // 按钮关闭时触发的回调 } 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() // 将选择器添加为Alert的子视图 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() // 尝试给选择器设置数据源和代理 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) } }
调用该组件的SwiftUI视图代码:
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)") } } ) // 其他视图内容 } } }
目前Alert中的UIPickerView并未加载预设的"red"和"green"数据,显示为空,需要解决该问题。
问题原因
- Coordinator初始化时机错误:创建Coordinator时,
alertController还未实例化,导致无法找到PickerView并设置其数据源和代理。 - PickerView未绑定正确的数据源:创建PickerView后,没有及时关联到实现了
UIPickerViewDataSource和UIPickerViewDelegate的Coordinator。 - OK按钮未返回实际选择值:当前OK按钮返回固定字符串,没有获取用户在PickerView中选中的内容。
修复方案
修改代码如下,主要调整PickerView的代理绑定时机和数据获取逻辑:
1. 修改UIAlertController扩展,保存PickerView引用
给UIAlertController添加一个属性存储PickerView,方便后续访问:
extension UIAlertController { private var pickerView: UIPickerView? { return view.subviews.first(where: { $0 is UIPickerView }) as? UIPickerView } convenience init(alert: PickerAlert) { self.init(title: alert.title, message: alert.message, preferredStyle: .alert) let pickerView = UIPickerView(frame: CGRect(x: 5, y: 20, width: 250, height: 140)) self.view.addSubview(pickerView) // 调整约束,避免覆盖标题和按钮 self.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ pickerView.topAnchor.constraint(equalTo: self.titleLabel?.bottomAnchor ?? self.view.topAnchor, constant: 16), pickerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20), pickerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20), pickerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -80) ]) addAction(UIAlertAction(title: "Cancel", style: .destructive) { _ in alert.action(nil) }) addAction(UIAlertAction(title: "OK", style: .default) { [weak self] _ in guard let pickerView = self?.pickerView, let selectedRow = pickerView.delegate?.pickerView?(pickerView, titleForRow: pickerView.selectedRow(inComponent: 0), forComponent: 0) else { alert.action(nil) return } alert.action(selectedRow) }) } }
2. 修改PickerAlertWrapper的updateUIViewController方法
在创建alertController后,再给PickerView设置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) } let alertController = UIAlertController(alert: alert) context.coordinator.alertController = alertController // 给PickerView设置数据源和代理 if let pickerView = alertController.pickerView { pickerView.dataSource = context.coordinator pickerView.delegate = context.coordinator pickerView.reloadAllComponents() } uiViewController.present(alertController, animated: true) } if !isPresented && uiViewController.presentedViewController == context.coordinator.alertController { uiViewController.dismiss(animated: true) } }
3. 简化Coordinator初始化方法
去掉Coordinator初始化时的PickerView绑定逻辑,因为现在绑定时机移到了updateUIViewController中:
final class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate { var alertController: UIAlertController? let alert: PickerAlert init(alert: PickerAlert) { self.alert = alert super.init() } 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] } }
修复后效果
修改完成后,Alert中的PickerView会正确加载预设的"red"和"green"选项,点击OK按钮时会返回用户选中的实际值,Cancel按钮返回nil。
内容的提问来源于stack exchange,提问作者Simon Rex
相关产品推荐
相关产品推荐

