iOS开发:如何为倒计时UIPickerView添加Hour/Minute/Second文本?
为UIPickerView的三列添加Hour/Minute/Second文本(iOS倒计时应用)
我正在开发一款带倒计时功能的iOS应用,想要实现带Hour、Minute、Second标签的UIPickerView效果(附参考与当前效果截图)。之前试过用UIDatePicker,但它不支持选秒;尝试给UILabel加Hour、Minute、Second文本,结果在不同设备上位置偏移。作为新手开发者,求修改现有代码,给UIPickerView三列分别加上对应的Hour、Minute、Second文本。
现有代码
func displayTimePickerAlert() { let alertController = UIAlertController(title: "Select countdown time", message: "\n\n\n\n\n\n", preferredStyle: .actionSheet) timePickerView.dataSource = self timePickerView.delegate = self timePickerView.frame = CGRect(x: 8, y: 20, width: alertController.view.bounds.width - 20, height: 140) alertController.view.addSubview(timePickerView) let okAction = UIAlertAction(title: "OK", style: .default) { [weak self] (_) in let selectedHour = self?.selectedHour ?? 0 let selectedMinute = self?.selectedMinute ?? 0 let selectedSecond = self?.selectedSecond ?? 0 self?.remainingTime = (selectedHour * 3600) + (selectedMinute * 60) + selectedSecond } alertController.addAction(okAction) present(alertController, animated: true, completion: nil) }
extension CountdownController: UIPickerViewDataSource, UIPickerViewDelegate { func numberOfComponents(in pickerView: UIPickerView) -> Int { return 3 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { switch component { case 0: // Giờ return hours.count case 1: // Phút return minutes.count case 2: // Giây return seconds.count default: return 0 } } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { switch component { case 0: return String(hours[row]) case 1: return String(minutes[row]) case 2: return String(seconds[row]) default: return "" } } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { if component == 0 { selectedHour = hours[row] } else if component == 1 { selectedMinute = minutes[row] } else if component == 2 { selectedSecond = seconds[row] } updateCountdownLabel() } }
解决方案:自定义行视图整合数字与单位
不用额外添加独立UILabel,直接通过pickerView(_:viewForRow:forComponent:reusing:)方法自定义每一行的视图,把数字和单位标签整合在一起,从根源解决设备适配的位置偏移问题。
修改后的完整代码
1. 替换UIPickerViewDelegate的标题方法为自定义视图方法
extension CountdownController: UIPickerViewDataSource, UIPickerViewDelegate { func numberOfComponents(in pickerView: UIPickerView) -> Int { return 3 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { switch component { case 0: return hours.count case 1: return minutes.count case 2: return seconds.count default: return 0 } } // 替换原titleForRow方法,自定义每行视图 func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { // 创建容器视图承载数字和单位标签 let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: 40)) // 数字标签 let numberLabel = UILabel() numberLabel.translatesAutoresizingMaskIntoConstraints = false numberLabel.textAlignment = .center numberLabel.font = UIFont.systemFont(ofSize: 18, weight: .semibold) switch component { case 0: numberLabel.text = String(hours[row]) case 1: numberLabel.text = String(minutes[row]) case 2: numberLabel.text = String(seconds[row]) default: numberLabel.text = "" } containerView.addSubview(numberLabel) // 单位标签 let unitLabel = UILabel() unitLabel.translatesAutoresizingMaskIntoConstraints = false unitLabel.textAlignment = .center unitLabel.font = UIFont.systemFont(ofSize: 12, weight: .regular) unitLabel.textColor = .secondaryLabel switch component { case 0: unitLabel.text = "Hour" case 1: unitLabel.text = "Minute" case 2: unitLabel.text = "Second" default: unitLabel.text = "" } containerView.addSubview(unitLabel) // 自动布局约束,确保适配所有设备 NSLayoutConstraint.activate([ numberLabel.topAnchor.constraint(equalTo: containerView.topAnchor), numberLabel.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), unitLabel.topAnchor.constraint(equalTo: numberLabel.bottomAnchor, constant: 2), unitLabel.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), unitLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) ]) return containerView } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { if component == 0 { selectedHour = hours[row] } else if component == 1 { selectedMinute = minutes[row] } else if component == 2 { selectedSecond = seconds[row] } updateCountdownLabel() } // 可选:设置每列宽度,优化布局 func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { return 80 // 可根据需求调整 } }
2. 优化AlertController的布局(替换硬编码换行与frame)
func displayTimePickerAlert() { let alertController = UIAlertController(title: "Select countdown time", message: nil, preferredStyle: .actionSheet) timePickerView.dataSource = self timePickerView.delegate = self timePickerView.translatesAutoresizingMaskIntoConstraints = false alertController.view.addSubview(timePickerView) // 用自动布局替代硬编码的frame和换行符 NSLayoutConstraint.activate([ timePickerView.topAnchor.constraint(equalTo: alertController.view.topAnchor, constant: 50), timePickerView.leadingAnchor.constraint(equalTo: alertController.view.leadingAnchor, constant: 8), timePickerView.trailingAnchor.constraint(equalTo: alertController.view.trailingAnchor, constant: -8), timePickerView.heightAnchor.constraint(equalToConstant: 140) ]) let okAction = UIAlertAction(title: "OK", style: .default) { [weak self] (_) in let selectedHour = self?.selectedHour ?? 0 let selectedMinute = self?.selectedMinute ?? 0 let selectedSecond = self?.selectedSecond ?? 0 self?.remainingTime = (selectedHour * 3600) + (selectedMinute * 60) + selectedSecond } alertController.addAction(okAction) present(alertController, animated: true, completion: nil) }
说明
- 自定义行视图将数字和单位整合,彻底避免独立UILabel的位置偏移问题,自动布局适配所有设备尺寸。
- 移除AlertController中硬编码的换行符和frame,改用自动布局约束让弹窗布局更稳定。
- 可根据应用风格调整字体、颜色、列宽等参数。
内容的提问来源于stack exchange,提问作者Johnny Nguyên
相关产品推荐
相关产品推荐

