display firebase data inside uipickerview

CODE HAS BEEN UPDATED AND IS WORKING AS EXPECTED.


Hello, I have a View Controller with a text field and a pickerview. I want to display the data i have stored in Firebase inside the pickerview. I'm able to retrieve and print the data from Firebase but I can't find a way to display it inside the picker view. Here is my code:


import UIKit
import Firebase


class pickerVC: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate {


    @IBOutlet weak var labelTxt: UITextField!
    @IBOutlet weak var infoPickerViewer: UIPickerView!

    var dbRef: CollectionReference!
    var pickerView: UIPickerView?
    var itemsClass = [ItemInfo]()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let pickerView = UIPickerView()
        infoPickerViewer.delegate = self
        infoPickerViewer.dataSource = self

        dbRef = Firestore.firestore().collection(ITEMS_REF)

        labelTxt.inputView = pickerView
        labelTxt.delegate = self
  
        self.infoPickerViewer = pickerView
        self.infoPickerViewer?.delegate = self
        self.infoPickerViewer?.dataSource = self
        
        self.infoPickerViewer.reloadAllComponents()
        getItems()

    }

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
      func textFieldDidBeginEditing(_ textField: UITextField) {
            labelTxt = textField
        }
    
        func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
                self.pickerView?.reloadAllComponents()
                return true
        }
    

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        if labelTxt.isFirstResponder {
            return self.itemsClass.count
        }
        return 0
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            if labelTxt.isFirstResponder {
                return itemsClass[row].itemName
            }
            return nil
        }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
            if labelTxt.isFirstResponder {
                let item = itemsClass[row].itemName
                labelTxt.text = item
            }
        }

    
    func getItems() {
        dbRef.getDocuments { (snapshot, error) in
            if let err = error {
                debugPrint("error fetching docs: \(err)")
            } else {
                self.infoPickerViewer.reloadAllComponents()
                let snap = snapshot
                for document in snap!.documents {
                    let data = document.data()
                    let itemCode = data[ITEMS_CODE] as? String ?? ""
                    let itemName = data[ITEMS_NAME] as? String ?? ""
                    
                    let t = ItemInfo(itemCode: itemCode, itemName: itemName)
                    
                    self.itemsClass.append(t)
           
                    print("ITEMS_CODE", itemCode as Any)
                    print("ITEMS_NAME", itemName as Any)
                    
                }
            }
        }
    }
}

The Firebase DB is structured as follow:

collection/AutoID/itemCode: "item1"

itemName: "item2"

collection/AutoID/itemCode: "item3"

itemName: "item4"


I only need to display the itemName inside the pickerview, the itemCode I'm going to use it to run a query.


Any help with this is greatly appreciated.

Accepted Reply

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {


must return a String?


but you return itemsClass[row]

which is of type itemInfo

Note: name of a class should start with Uppercase, as ItemInfo


So, probably you should return itemsClass[row].itemName

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            if labelTxt.isFirstResponder {
                return itemsClass[row].itemName
            }
            return nil
        }

Replies

You have to implement it in


    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            if labelTxt.isFirstResponder {
                return items[row] as String
            }
            return nil
        }


I don't understand what you expect with

if labelTxt.isFirstResponder


Add a test to check:


    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            if labelTxt.isFirstResponder {
               print("row", row, items[row])
                return items[row] as String
            }
           print("row", row, nil)
            return nil
        }


But the error is probably in viewDidLoad:


    override func viewDidLoad() { 
        super.viewDidLoad() 
        
        let pickerView = UIPickerView() 
        infoPickerViewer.delegate = self 
        infoPickerViewer.dataSource = self 
        
        dbRef = Firestore.firestore().collection(ITEMS_REF) 
        
        labelTxt.inputView = pickerView 
        labelTxt.delegate = self 
 
        getItems() 
    }


Line 10, you set the inputView to a picker created locally that is never initialized.


I would change with this:


    override func viewDidLoad() {
        super.viewDidLoad()
       
        // REMOVE THIS.        let pickerView = UIPickerView()
        infoPickerViewer.delegate = self
        infoPickerViewer.dataSource = self
       
        dbRef = Firestore.firestore().collection(ITEMS_REF)
       
        labelTxt.inputView = infoPickerViewer  // REPLACING pickerView
        labelTxt.delegate = self

        getItems()
    }

Hi Claude,

i use labelTxt.isFirstResponder so when i click on the text field, the pickerviewer shows up and when i select something from the pickviewer, it shows in the text field. I'm using that on a separate project with the difference that I'm using a local array.


Currently I'm not getting an error, the issue that i'm having is how to display the Firebase info in the pickerviewer. Here is a print out of the console:

ITEMS_CODE item1

ITEMS_NAME item2

ITEMS_CODE item5

ITEMS_NAME item6

ITEMS_CODE item3

ITEMS_NAME item4


The only item i want to display in the pickerviewer is the ITEM_NAME, i'm going to use the ITEM_CODE to run a query to Firebase on a different VC.


I made a change to the getItems() function:


    func getItems() {
        dbRef.getDocuments { (snapshot, error) in
            if let err = error {
                debugPrint("error fetching docs: \(err)")
            } else {
                
                let snap = snapshot
                for document in snap!.documents {
                    let data = document.data()
                    let itemCode = data[ITEMS_CODE] as? String ?? ""
                    let itemName = data[ITEMS_NAME] as? String ?? ""
                    
                    let t = intemInfo(itemCode: itemCode, itemName: itemName)
                    
                    self.itemsClass.append(t)
                    
                    //print(document.data())
                    print("ITEMS_CODE", itemCode as Any)
                    print("ITEMS_NAME", itemName as Any)
                    
                }
            }
        }
    }


My confusion is on how to display just the itemName. Hope this is clear.

I miss something.


in pickerView titleForRow, you use items[row] as data source.


But I do not see where you set items in getItems. Maybe in intemInfo (probably itemInfo mispelled), but you do not show the func.


something like:

                items = []
                for document in snap!.documents {
                    let data = document.data()
                    let itemCode = data[ITEMS_CODE] as? String ?? ""
                    let itemName = data[ITEMS_NAME] as? String ?? ""
                    items.append(itemName)     // Unless done in intemInfo ?
                    let t = intemInfo(itemCode: itemCode, itemName: itemName)
                  
                    self.itemsClass.append(t)
                  
                    //print(document.data())
                    print("ITEMS_CODE", itemCode as Any)
                    print("ITEMS_NAME", itemName as Any)
                  
                }

Sorry, intemInfo it's a typo. I'm using the following var:


var itemsClass = [itemInfo]()


This is the class:


class itemInfo {

    private(set) var itemCode: String?
    private(set) var itemName: String?
   
    init(itemCode: String, itemName: String) {

        self.itemCode = itemCode
        self.itemName = itemName
    }
}


and this is how the pickerviewer is configured:


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

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        if labelTxt.isFirstResponder {
            return self.itemsClass.count
        }
        return 0
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            if labelTxt.isFirstResponder {
                return itemsClass[row] //here im getting the following error: Cannot convert return      
                                                          //expression of type 'itemInfo' to return type 'String?'
            }
            return nil
        }

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {


must return a String?


but you return itemsClass[row]

which is of type itemInfo

Note: name of a class should start with Uppercase, as ItemInfo


So, probably you should return itemsClass[row].itemName

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            if labelTxt.isFirstResponder {
                return itemsClass[row].itemName
            }
            return nil
        }

This:

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?

yes, needs to return a string.


I have updated the name of the class (Thank you). Code has been corrected, added few things i was missing and is working as expected.

Thank you for your help.


I updated my original code with all the changes.