Search code examples
iosswiftuiviewuipickerview

Animate background color of selected UIPickerView Row in Swift


I want to change background color of selected row in UIPickerView with animation. I'm reloading all components when a new row selected and in viewForRow function If current row is selected one, I make Its' background color to red. However, It looks like in bug. First screen shot is what I want to achieve and second one is in my app. Btw, It will be excellent If I can set that red color with animation

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    pickerView.reloadAllComponents()
}

func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
    let view = UIView()
    view.frame = CGRect(x: 0, y: 0, width: width, height: height)

    let label = UILabel()
    label.frame = CGRect(x: 0, y: 0, width: height, height: height)
    label.textAlignment = .center
    label.font = UIFont.systemFont(ofSize: 30)
    label.text = numbers[row]
    view.addSubview(label)

    view.transform = CGAffineTransform(rotationAngle: 90 * (.pi/180))
    if pickerView.selectedRow(inComponent: component) == row {
    label.attributedText =  NSAttributedString(string: numbers[row], attributes: [NSAttributedStringKey.font:UIFont.systemFont(ofSize: 30),NSAttributedStringKey.foregroundColor:UIColor.white])
        view.backgroundColor = UIColor.red
    }
    return view
}

First Second


Solution

  • A slightly different approach since I couldn't find a smooth way to animate background on selected row. It can be done but not much of an improvement, so give this a try:

    Highlighted PickerView

    override func viewDidLoad() {
        //...
    
        /*
         Create a colored `view` that stays bang in the center on top
         of the `pickerView`.
         The `pickerView` will scroll behind it normally and no need
         for animating background color or even reloading
         */
        createHighlightView()
    }
    
    func createHighlightView() {
        let highlightView = UIView(frame: CGRect.zero)
        highlightView.backgroundColor = UIColor.red.withAlphaComponent(0.2)
    
        /*
         Now lets programmatically add constraints
         */
        highlightView.translatesAutoresizingMaskIntoConstraints = false
        pickerView.addSubview(highlightView)
    
        //HightLight View's width
        highlightView.addConstraint(NSLayoutConstraint(item: highlightView,
                                                       attribute: .width,
                                                       relatedBy: .equal,
                                                       toItem: nil,
                                                       attribute: .notAnAttribute,
                                                       multiplier: 1,
                                                       constant: width))
    
        //HightLight View's height
        highlightView.addConstraint(NSLayoutConstraint(item: highlightView,
                                                       attribute: .height,
                                                       relatedBy: .equal,
                                                       toItem: nil,
                                                       attribute: .notAnAttribute,
                                                       multiplier: 1,
                                                       constant: height))
    
        //HightLight View should be bang center-aligned with pickerView
        pickerView.addConstraint(NSLayoutConstraint(item: highlightView,
                                                    attribute: .centerX,
                                                    relatedBy: .equal,
                                                    toItem: pickerView,
                                                    attribute: .centerX,
                                                    multiplier: 1,
                                                    constant: 0))
        pickerView.addConstraint(NSLayoutConstraint(item: highlightView,
                                                    attribute: .centerY,
                                                    relatedBy: .equal,
                                                    toItem: pickerView,
                                                    attribute: .centerY,
                                                    multiplier: 1,
                                                    constant: 0))
    }
    

    And now your delegates can be simply:

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        //no need to reload
        //do whatever else you want
    }
    
    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
        /*
         Just return a `UILabel`. No need to put it in a `UIView`.
         Nothing special either, just slap text into it
         */
    
        var label = view as? UILabel
    
        if label == nil {
            label = UILabel()
    
            //All the rest are safe force unwraps so chill tf out
            label!.frame = CGRect(x: 0, y: 0, width: height, height: height)
            label!.textAlignment = .center
            label!.font = UIFont.systemFont(ofSize: 30)
    
            label!.transform = CGAffineTransform(rotationAngle: 90 * (.pi/180))
        }
    
        label!.text = arrDatasource[row]
    
        return label!
    }