UIPickerView 确实按行传递

UIPickerView did pass by row

我有这个演示应用程序:

如您所见,背景的 alpha 正根据该值变为黑色。

但问题是没有平滑过渡:

从动图中可以看出,滚动结束后背景才会发生变化。我不希望它变成那样。

这是我的代码:

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {

    @IBOutlet weak var pickerView: UIPickerView!
    @IBOutlet weak var backView: UIView!
    
    let max = 100
    
    override func viewDidLoad() {
        super.viewDidLoad()

        pickerView.delegate = self
        pickerView.dataSource = self
    }
    
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return max + 1 // To include '0'
    }
    
    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
        
        let l = UILabel(frame: .zero)
        l.text = String(max - row)
        l.textColor = .white
        
        l.font = UIFont.preferredFont(forTextStyle: .title3)
        l.textAlignment = .center
        
        return l
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        backView.alpha = CGFloat(max - row) / 100
    }


}

我给委托 UIView 而不是字符串,因为我有一个想法:每次每行的位置发生变化时进行测试,当该行位于屏幕中间时我应该更新背景。但不幸的是我不知道该怎么做。

也许你能帮忙?或者提出任何其他想法?

谢谢!

委托方法 didSelectRow 仅在滚动停止时调用,因此这不是您应该更新 alpha 的地方。 UIPickerView 没有委托方法可以在滚动期间通知您有关更改的信息,但是 UIPickerView 将调用您的数据源和委托方法来获取标题,或者在您的情况下是给定行的视图它可以在用户滚动时显示。所以你应该做的就是把你的 alpa 改变逻辑移到那里:

func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
    backView.alpha = CGFloat(max - row) / 100
}

请注意,此委托方法将在 UIPickerView 加载时调用,因此您可能应该禁用 alpha 更改,直到视图布局不正确(也许 viewDidAppear 会这样做)。

由于委托方法有时会出现意外行为(不仅调用下一行,还调用选取器中的任何一行),我们应该存储并检查该行是否仅比上次保存的值领先或落后一步,否则我们应该忽略它。我做了一个简单的演示来演示它是如何工作的:

import UIKit

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
    private let pickerView = UIPickerView()
    private var isPickerReady = false
    private var lastValue = 0
    private let max = 100

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(pickerView)
        pickerView.translatesAutoresizingMaskIntoConstraints = false

        pickerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        pickerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        pickerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        pickerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true


        pickerView.delegate = self
        pickerView.dataSource = self
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        isPickerReady = true
    }

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return max
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        // Do not update the value until the view is not loaded 
        // Only consider delegate methods which are one step ahead or behind the last value
        if row + 1 == lastValue && isPickerReady || row - 1 == lastValue && isPickerReady {
            lastValue = row
            view.alpha = CGFloat(max - lastValue ) / 100
        }
        return "Tiltle \(row)"
    }
}