You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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"数据,显示为空,需要解决该问题。


问题原因

  1. Coordinator初始化时机错误:创建Coordinator时,alertController还未实例化,导致无法找到PickerView并设置其数据源和代理。
  2. PickerView未绑定正确的数据源:创建PickerView后,没有及时关联到实现了UIPickerViewDataSource和UIPickerViewDelegate的Coordinator。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 01:10:06