Swift中跨类使用自定义UIPickerView的代理与数据源问题
嘿,我来帮你搞定这个自定义UIPickerView的代理和数据源问题!其实核心就是要确保代理/数据源的设置逻辑正确,同时必填的协议方法都实现到位,下面分两种常用场景给你拆解:
方案一:让自定义PickerView类自身实现代理和数据源
这种方式适合把PickerView的逻辑封装在内部,对外只暴露必要的接口(比如数据源修改),代码更模块化。
关键注意点:
- 自定义类要同时遵守
UIPickerViewDelegate和UIPickerViewDataSource协议 - 必须在初始化方法里把pickerView的
delegate和dataSource赋值为self - 一定要实现数据源的两个必填方法,以及至少一个代理的内容展示方法
代码示例:
import UIKit class CustomPickerView: UIPickerView, UIPickerViewDelegate, UIPickerViewDataSource { // 对外暴露的数据源接口,方便外部修改 var pickerOptions: [String] = ["默认选项1", "默认选项2", "默认选项3"] override init(frame: CGRect) { super.init(frame: frame) setupPickerConfig() } required init?(coder: NSCoder) { super.init(coder: coder) setupPickerConfig() } private func setupPickerConfig() { // 核心:绑定代理和数据源到自身 self.delegate = self self.dataSource = self // 可以加一些自定义样式,比如背景色、选中行颜色 self.backgroundColor = .systemGray6 } // MARK: - 数据源必填方法 func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 // 单列PickerView,多列的话改对应数字 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return pickerOptions.count } // MARK: - 代理方法:设置每行显示的标题 func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return pickerOptions[row] } // 可选:如果需要自定义行的视图,用这个方法代替titleForRow // func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { // let label = view as? UILabel ?? UILabel() // label.text = pickerOptions[row] // label.textAlignment = .center // return label // } }
在ViewController中使用:
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let customPicker = CustomPickerView(frame: CGRect(x: 0, y: 200, width: view.frame.width, height: 200)) // 替换自定义数据源 customPicker.pickerOptions = ["苹果", "香蕉", "橙子", "葡萄"] view.addSubview(customPicker) } }
方案二:让ViewController作为PickerView的代理和数据源
这种方式适合PickerView的逻辑需要和ViewController的其他业务联动的场景,比如选中后更新页面其他控件。
关键注意点:
- ViewController要遵守
UIPickerViewDelegate和UIPickerViewDataSource协议 - 初始化自定义PickerView后,必须手动把它的
delegate和dataSource赋值为ViewController实例 - 同样要实现数据源的两个必填方法
代码示例:
自定义PickerView类(只做样式封装,不用处理代理):
import UIKit class CustomPickerView: UIPickerView { override init(frame: CGRect) { super.init(frame: frame) setupCustomStyle() } required init?(coder: NSCoder) { super.init(coder: coder) setupCustomStyle() } private func setupCustomStyle() { self.layer.cornerRadius = 8 self.clipsToBounds = true self.backgroundColor = .systemGray6 } }
ViewController中的实现:
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource { // 定义PickerView的数据源 private let pickerData = ["红色", "绿色", "蓝色", "黄色"] override func viewDidLoad() { super.viewDidLoad() let customPicker = CustomPickerView(frame: CGRect(x: 0, y: 200, width: view.frame.width, height: 200)) // 核心:绑定代理和数据源到ViewController customPicker.delegate = self customPicker.dataSource = self view.addSubview(customPicker) } // MARK: - 数据源必填方法 func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return pickerData.count } // MARK: - 代理方法 func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return pickerData[row] } // 可选:处理选中事件 func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { print("选中了:\(pickerData[row])") // 这里可以联动更新页面其他控件 } }
常见问题排查
如果还是显示不出内容,检查这几点:
- 有没有忘记实现
numberOfComponents(in:)和pickerView(_:numberOfRowsInComponent:)这两个数据源必填方法?没实现的话PickerView会完全空白 - 数据源数组是不是空的?如果数组长度为0,自然没有内容可显示
- 代理/数据源的赋值时机是不是太晚?比如在PickerView已经添加到视图后才赋值,这时候需要调用
reloadAllComponents()刷新 - 有没有循环引用?不用担心,UIPickerView的
delegate和dataSource都是weak属性,不会造成循环引用
内容的提问来源于stack exchange,提问作者Emre Sarıçiçek
相关产品推荐
相关产品推荐

