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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 06:05:01