Search code examples
iosswiftuipickerview

UIPickerView (size of cells)


I am trying to make a horizontal UIPivkerView. I did everything but I do not understand how to set the size for a cell because some words are too long. I tried to learn the length of each word and based on it set the cell size. But it not work for me. I noticed that I can increase the size of the cell if I increase its size and width in proportion too.

I need a make PickerView with like 50 width and 50 height but I do not understand how to deal with cells.

If I set value 85 I get this:

enter image description here

If set 50:

enter image description here

import UIKit

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
    var data = ["Hello", "WorldWorld", "Of", "PickerPickerPicker", "View"]

    let viewPicker = UIPickerView()
    var rotationAngele: CGFloat!

    var width: CGFloat = 85
    var height: CGFloat = 85
    //var width: CGFloat!

    override func viewDidLoad() {
        super.viewDidLoad()

        viewPicker.delegate = self
        viewPicker.dataSource = self

        viewPicker.layer.borderColor = UIColor.black.cgColor
        viewPicker.layer.borderWidth = 1.5

        // Rotation
        rotationAngele = -90 * (.pi / 180)
        viewPicker.transform = CGAffineTransform(rotationAngle: rotationAngele)

        //width = data[4].widthOfString(usingFont: UIFont(name:"verdana", size: 24.0)!)

        // Create
        viewPicker.frame = CGRect(x: 0 - 150, y: 0, width: view.frame.width + 300, height: 85)
        viewPicker.center = self.view.center

        self.view.addSubview(viewPicker)
    }

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

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

    func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
        return 85
    }

    func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
        return 85
    }

    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {

        //width = data[row].widthOfString(usingFont: UIFont(name:"verdana", size: 24.0)!)

        let view = UIView()
        view.frame = CGRect(x: 0, y: 0, width: 150, height: 150)

        let label = UILabel()
        label.frame = CGRect(x: 0, y: 0, width: 150, height: 150)

        label.textAlignment = .center
        label.font = UIFont(name:"verdana", size: 24.0)!
        label.text = data[row]
        view.addSubview(label)

        view.transform = CGAffineTransform(rotationAngle: 90 * (.pi / 180))

        return view
    }
}

////////////////////////////////////////////////////
extension String {
    func widthOfString(usingFont font: UIFont) -> CGFloat {

        let fontAttributes = [NSAttributedStringKey.font: font]
        let size = self.size(withAttributes: fontAttributes)
        return size.width
    }

    func heightOfString(usingFont font: UIFont) -> CGFloat {
        let fontAttributes = [NSAttributedStringKey.font: font]
        let size = self.size(withAttributes: fontAttributes)
        return size.height
    }
}

Solution

  • Ok, when you rotate UIPickerView by applying transform, when you specify the component width you actually specifying the rowHeight.

    It's not that clear if you inspect the view but if you log the frame size you will understand.

    So you should set the pickerView:rowHeightForComponent to make it wide enough

    func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
        return 300 // you can calculate this based on your container view or window size
    }
    

    Further you can also use intrinsicContentSize of UILabel to figure out the correct size of the label and update its frame accordingly to make sure the label also rendered correctly.

     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: 150, height: 30)
    
        let label = UILabel()
        label.frame = CGRect(x: 0, y: 0, width: 150, height: 30)
    
        label.textAlignment = .center
        label.font = UIFont(name:"verdana", size: 24.0)!
        label.text = data[row]
        let updatedLabelSize = label.intrinsicContentSize
        dump(updatedLabelSize)
        label.frame = CGRect(x: 0, y: 0, width: updatedLabelSize.width, height: 30)
        view.frame = CGRect(x: 0, y: 0, width: updatedLabelSize.width, height: 30)
        view.addSubview(label)
    
        view.transform = CGAffineTransform(rotationAngle: 90 * (.pi / 180))
    
        return view
    }
    

    Here is how UIPickerView actually look like when you transform it. Red box is after transformation, things that you see Blue highlight is the real frame

    PickerView frame