Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

SoCalResident

macrumors newbie
Original poster
Jun 28, 2007
11
1
My project has a UIPickerView and I need to return the count of an array for numberOfRowsInComponent.

Heres my code so far:
  1. Code:
    import UIKit 
    import CloudKit 
    class SecondViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource  { 
        var db:CKDatabase? 
        var currentRecords:[CKRecord] = [] 
        var currentRecordsInt:[Int] = [] 
        var numb: Int = 0 
        var numbReturn: Int = 0 
        
        @IBOutlet weak var pickCar: UIPickerView! 
        
        override func viewDidLoad() { 
           
            self.pickCar.delegate = self 
            self.pickCar.dataSource = self 
            db = CKContainer.default().privateCloudDatabase 
            super.viewDidLoad() 
           
        } 
        
        func numberOfComponents(in pickerView: UIPickerView) -> Int { 
            return 1 
        } 
     
        func getCarArrayNumb()   -> Int { 
            
                    let predicate = NSPredicate(value: true) 
                    let query = CKQuery(recordType: "mpgTracker", predicate: predicate) 
            
            db?.perform(query, inZoneWith: nil, completionHandler: { (records:[CKRecord]?, e:Error?) in 
                if e != nil { 
                    return 
                } 
                self.currentRecords = records! 
                var record: CKRecord = records![0] 
                print("getCarArrayNumb record.allKeys() \(record.allKeys())")   // OK, expected array 
                
                var counts: [String: Int] = [:] 
                
                var numb: Int = 0 
                
                func countNumb() -> Int { 
                for item in record.allKeys() { 
                    counts[item] = (counts[item] ?? 0) + 1 
                    numb = numb + 1 
                } 
                    print("return numb: \(numb)")   //     13 
                    return numb 
                } 
                print("countNumb: \(countNumb())")  //     13 
            }) 
            return 1 
        } 
        
        func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 
            return getCarArrayNumb() 
        } 
        
        func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { 
            
            return "" 
        }

    I'm sure the answer is simple but so far it alludes me.

    Thanks for any help!!

    ~paul
 
You probably shouldn't be waiting until the UIPickerView asks to have the count/items ready.

A couple tweaks I'd make to this code:
  • Move the code that performs the query into it's own 'kickOffQuery' function, call it in viewDidLoad or viewWillAppear.
  • Add a way to track the state of this async data loading. Also, it helps to know if the currentRecords are valid, or just empty when dealing with async data loading. My example makes the array optional very intentionally for this reason.
  • Make your handler update the data, and trigger a reload of the data, if appropriate.
Something a bit like:

Code:
class MyViewController {
    // Made optional. If null, data conclusively hasn't been loaded yet.
    // If empty, the data has been loaded, there's just nothing there.
    var currentRecords : [CKRecord]?
    var dataAlreadyRequested : Bool = false

    override func viewDidLoad() {
        // ...
        self.kickOffQuery()
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        dataAlreadyRequested = true
        guard realRecords = currentRecords else { return 1 } // This makes sense further down.

        return realRecords.count
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
         guard realRecords = currentRecords else { return "Loading..." }

         return self.getTitleForRecord(self.currentRecords[row])
    } 

    private func kickOffQuery() {
        let predicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "mpgTracker", predicate: predicate)

        db?.perform(query, inZoneWith: nil, completionHandler: { (records:[CKRecord]?, e:Error?) in
            // These should probably do more than simply return here.
            guard let realRecords = records else { return }
            guard e == nil else { return }

            // ...

            self.assignNewRecords(realRecords)
        })
    }

    private func assignNewRecords(_ records: [CKRecord]) {
        // This is useful for making edits more "atomic".
        // CKDatabase can call you back on background threads.
        // You don't want to be making changes to data the main thread may be reading.
        //
        // Depending on style, you may want the handler to be the one dispatching instead.
        DispatchQueue.main.async {
            self.currentRecords = realRecords
            self.dataAvailable = true
            self.reloadIfNeeded()
        }
    }

    private func reloadIfNeeded() {
        if (dataAlreadyRequested) {
            self.view.reloadAllComponents()
        }
    }
}
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.