Search code examples
iosswiftuikituipickerviewuistackview

Swift - creating a UIPickerView within a UIStackView PROGRAMMATICALLY


I'm a complete beginner to swift & iOS dev in general, so be easy on me :)
In my app, I have a horizontal StackView.
Within that StackView - I have a label and a button, and now I would like to add a PickerView that would be populated from some list of options.
I've been googling and reading threads, but the closest I've gotten was getting the PickerView to show its position (using some background color) but with no actual values inside.
This is the code where I create and customize my StackView's components:

class SingleReportInputStackView: UIStackView {
    ...  // creating and customizing my StackView


    private func getObjects() -> (UILabel, UIButton, UIPickerView) {
        let myLabel: UILabel = {
            ...  // creating UILabel
        }()
        
        let myButton: UIButton = {
            ... // creating UIButton
        }()
        
        class MyPicker: NSObject, UIPickerViewDelegate, UIPickerViewDataSource {
            let dataArray = ["English", "Maths", "History", "German", "Science"]
            let UIPicker: UIPickerView = UIPickerView()
            
            override init() {
                super.init()
                self.UIPicker.delegate = self
                self.UIPicker.dataSource = self
                self.UIPicker.backgroundColor = UIColor.white
            }
            
            func numberOfComponents(in pickerView: UIPickerView) -> Int {
                return 1
            }
            
            func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
                return dataArray.count
            }
            
            func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
                let row = dataArray[row]
                return row
            }
        }
        
        let myPicker = MyPicker()
        return (myLabel, myButton, myPicker.UIPicker)
    }
    ...
}

Then, I add those components to my Horizontal StackView by calling setupSingleInput():

class SingleReportInputStackView: UIStackView {
    ...
    private func setupSingleInput() {
        let (myLabel, myButton, myPicker) = getObjects()
        self.addArrangedSubview(myLabel)
        self.addArrangedSubview(myButton)
        self.addArrangedSubview(myPicker)
        self.translatesAutoresizingMaskIntoConstraints = false
    }
    ...
}

As I've said, I can see the label, the button and the PickerView's white background (looks like an empty, white rectangle).

BTW, I don't have a storyboard (if that wasn't obvious already) - I'm creating the UI programatically.

Can someone help me out? Why is my PickerView not being properly populated by my dataArray?


Solution

  • I'm a complete beginner to swift & iOS dev in general ...

    I'd recommend starting a bit simpler... embedding a class inside a func is almost certainly not the way to go here.

    The biggest problem is that you create an instance of MyPicker inside your getObjects() func, but then you return a UI element from that class, and the class instance goes away -- it goes out of scope:

    private func getObjects() -> (UILabel, UIButton, UIPickerView) {
        // ... all the stuff you're doing in here
    
        let myPicker = MyPicker()
    
        // as soon as you return, myPicker no longer exists!!!
    
        return (myLabel, myButton, myPicker.UIPicker)
    }
    

    So, you have returned a UIPickerView, but it no longer has any code (its Delegate and DataSource) backing it.

    Here's a quick modification:

    class SingleReportInputStackView: UIStackView {
        
        private var myPicker: MyPicker!
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            setupSingleInput()
        }
        required init(coder: NSCoder) {
            super.init(coder: coder)
            setupSingleInput()
        }
        private func setupSingleInput() {
            let (myLabel, myButton) = getObjects()
            myPicker = MyPicker()
            self.addArrangedSubview(myLabel)
            self.addArrangedSubview(myButton)
            self.addArrangedSubview(myPicker.UIPicker)
            self.translatesAutoresizingMaskIntoConstraints = false
        }
        
        private func getObjects() -> (UILabel, UIButton) {
            let myLabel: UILabel = {
                let v = UILabel()
                v.text = "The Label"
                return v
            }()
            
            let myButton: UIButton = {
                let v = UIButton(type: .system)
                v.setTitle("The Button", for: [])
                return v
            }()
            
            return (myLabel, myButton)
        }
        
        private class MyPicker: NSObject, UIPickerViewDelegate, UIPickerViewDataSource {
            let dataArray = ["English", "Maths", "History", "German", "Science"]
            let UIPicker: UIPickerView = UIPickerView()
            
            override init() {
                super.init()
                self.UIPicker.delegate = self
                self.UIPicker.dataSource = self
                self.UIPicker.backgroundColor = UIColor.white
            }
            
            func numberOfComponents(in pickerView: UIPickerView) -> Int {
                return 1
            }
            
            func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
                return dataArray.count
            }
            
            func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
                let row = dataArray[row]
                return row
            }
        }
        
    }
    

    and a sample view controller to try it:

    class UriYakirViewController: UIViewController {
        
        let singleReportStack = SingleReportInputStackView()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // a yellow-ish background so we can see the white picker view frame
            view.backgroundColor = UIColor(red: 1.0, green: 0.8, blue: 0.5, alpha: 1)
            
            view.addSubview(singleReportStack)
            
            singleReportStack.axis = .vertical
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                singleReportStack.centerXAnchor.constraint(equalTo: g.centerXAnchor),
                singleReportStack.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            ])
            
        }
        
    }
    

    That code will give you this result:

    enter image description here