I have this demo app:
As you can see, the alpha of the background is changing to black according to the value.
But the problem is that there is no smooth transition:
As you can see from the GIF, the background is only changing after the scrolling is over. And I don't want it to be like that.
this is my code:
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
}
}
I am giving the delegate UIView
instead of String, because I had an idea: to test every time the location of each row is changing, and when the row is in the middle of the screen I should update the background. But unfortunately I don't know how to do that.
Maybe you can help? or perhaps suggest any other ideas?
Thank you!
The delegate method didSelectRow
is only called when the rolling stops, so this is not the place you should update your alpha. UIPickerView
has no delegate method which will notify you about the change during the rolling, however the UIPickerView
will call your data source and delegate methods to get the title or in your case the view for a given row so it can be displayed as the user scrolls. So what you should do is just move your alpa changing logic there:
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
backView.alpha = CGFloat(max - row) / 100
}
Note that this delegate method will be called when the UIPickerView
is loaded, so maybe you should disable the alpha changing until view is not layout out correctly(maybe viewDidAppear
will do it).
As the delegate method can sometimes behave unexpectedly(calling not just the next lines, but any line from the picker), we should store and also check if the row is just one step ahead or behind the last saved value, otherwise we should just ignore that. I made a simple demo to demonstrate how it works:
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)"
}
}