UIPickerView 确实按行传递

问题描述

我有这个演示应用程序:

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

但问题是没有平滑过渡:

正如您从 GIF 中看到的,背景仅在滚动结束后才发生变化。我不希望它变成那样。

这是我的代码

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 而不是 String,因为我有一个想法:每次测试每行的位置发生变化时,当行位于屏幕中间时,我应该更新背景。但不幸的是,我不知道该怎么做。

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

谢谢!

解决方法

委托方法 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)"
    }
}