Access numberOfRowsInComponent from iCloud

Discussion in 'iOS Programming' started by SoCalResident, Feb 21, 2018.

  1. SoCalResident macrumors newbie

    Joined:
    Jun 28, 2007
    #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
     
  2. Krevnik macrumors 68040

    Krevnik

    Joined:
    Sep 8, 2003
    #2
    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()
            }
        }
    }
    
     
  3. SoCalResident thread starter macrumors newbie

    Joined:
    Jun 28, 2007
    #3
    Krevnik:

    Looks like a good solution, thank you muchly.

    Looking forward to implementing.

    ~paul
     

Share This Page