Search code examples
swiftuipickerview

Issues passing data from uipickerview to uilabel


I'm having issues getting the selected result from the picker to show in my description when pressing Done. When running the app the description will print ["Male", "Female"]. The end result should have the selected option only inserted into the description.

I'm new to swift and am not sure what I have done wrong to get this outcome. Any help with this would be appreciated. The code is below:

class OnboardingViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {

@IBOutlet weak var collectionView: UICollectionView!
@IBOutlet weak var pageControl: UIPageControl!
@IBOutlet weak var NextButton: UIButton!
@IBOutlet weak var Description: UILabel!

let gender = ["Male", "Female"]

var genderPicker = UIPickerView()
var toolBar = UIToolbar()

var slides: [OnboardingSlide] = []

override func viewDidLoad() {
    super.viewDidLoad()

    NextButton.layer.cornerRadius = NextButton.frame.width / 2
    NextButton.layer.masksToBounds = true
    NextButton.alpha = 0
    
    Description.text = "An ideal daily intake of calories varies depending on age, metabolism and levels of physical activity, among other things. Generally, the recommended daily calorie intake is 2,000 calories a day for women and 2,500 for men."

    Description.numberOfLines = 0
    Description.lineBreakMode = .byTruncatingTail
    view.addSubview(Description)
    
}

@IBAction func genderSelectButton(_ sender: UIButton) {

        genderPicker = UIPickerView.init()
        genderPicker.delegate = self
        genderPicker.dataSource = self
        genderPicker.backgroundColor = UIColor.white
        genderPicker.setValue(UIColor.black, forKey: "textColor")
        genderPicker.autoresizingMask = .flexibleWidth
        genderPicker.contentMode = .center
        genderPicker.frame = CGRect.init(x: 0.0, y: UIScreen.main.bounds.size.height - 300, width: UIScreen.main.bounds.size.width, height: 300)
        self.view.addSubview(genderPicker)
                
        toolBar = UIToolbar.init(frame: CGRect.init(x: 0.0, y: UIScreen.main.bounds.size.height - 300, width: UIScreen.main.bounds.size.width, height: 50))
        toolBar.items = [UIBarButtonItem.init(title: "Done", style: .done, target: self, action: #selector(onDoneButtonTapped))]
        self.view.addSubview(toolBar)
    
    Description.alpha = 0
    
}

@objc func onDoneButtonTapped() {
    toolBar.removeFromSuperview()
    genderPicker.removeFromSuperview()
    Description.text = "You are \(gender)."
    Description.alpha = 100
    NextButton.alpha = 100
    
}

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

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

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
    return gender[row]
}

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    Description.text = gender[row]
    print(gender[row])
}

@IBAction func nextButtonClicked(_ sender: Any) {
}

Solution

  • You can either listen to the pickerView(_:didSelectRow:inComponent) and record the row value as the user changes the selection, or ask the picker for its selected row (using the selectedRow(inComponent:) method) when the user taps your done button.

    Your Description.text = "You are \(gender)." code doesn't make sense, since gender appears to be an array of gender values. I would expect that to to read Description.text = "You are \(gender[selectedRow])." (assuming you save the selected row to an instance var selectedRow.)

    Note that instance variables should begin with lower-case letters, and types should start with upper-case letters. Thus Description should be description.

    There is a strong convention in Swift to use case consistently (Class and type names should be upper "camel case" and variables and values should be named with lower camel case.

    Another thought:

    I would suggest making Gender an enum:

    enum Gender: String, CaseIterable {
       case male
       case female
    }
    

    Then you can fetch a gender using a 0-based index using Gender.allCases[index] and fetch the string value of a gender using

    let aGender: Gender = .male
    let genderName: String = aGender.rawValue
    

    You can also create a Gender from a string using Gender(rawValue:). Consider this code:

    func genderFromString(_ aString: String) {
        if let genderFromString = Gender(rawValue: aString) {
            print("genderFromString for '\(aString)' = \(genderFromString)")
        } else {
            print("Can't convert '\(aString)' to a Gender")
        }
    }
    
    genderFromString("male")
    
    genderFromString("Wombat")
    

    That outputs:

    genderFromString for 'male' = male
    Can't convert 'Wombat' to a Gender