Search code examples
iosswiftswift3

Set UILabel Text at Completion of CloudKit Asyncronous Operation in Swift


I'm new to Swift, and asynchronous code in general, so tell me if this is way off. Basically I want to:

  1. Open the App
  2. That triggers a read of CloudKit records
  3. Once the read is complete a UILabel will display the number of records retrieved

This clearly isn't useful in itself, but as a principle it will help me to understand the asynchronous code operation, and how to trigger actions on their completion.

// In ViewController Swift file:

class ViewController: UIViewController{
    
    override func viewDidLoad() {
        super.viewDidLoad()
        readDatabase()
    }
    @IBOutlet weak var myLabel: UILabel!
}

let VC=ViewController()


//In Another Swift file:

func readDatabase() {
    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: "myRecord", predicate: predicate)
    let container = CKContainer.default()
    let privateDB = container.privateCloudDatabase

    privateDB.perform(query, inZoneWith:nil) { (allRecs, err) in
        VC.myLabel.text = ("\(allRecs?.count) records retreived")
    
    /*
    ERROR OCCURS IN LINE ABOVE:
       CONSOLE: fatal error: unexpectedly found nil while unwrapping an Optional value
       BY CODE LINE: Thread 8:EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) 
    */

    }
}

I'm able to set the text field from within the viewDidLoad function, so why not from a function called within that function?

A few other things I've tried:

  • Use async dispatch to put it on thread 1
  • Implement a var with within the ViewController class, with a didSet that sets the text, set the var to the desired value in the privateDB.perform code to trigger the change

These both create the same problem as above.

Yes, I know there isn't any error handling in .perform, and yes there are records. If I trigger the setting of the UILabel text to the record count manually a few seconds after the view has loaded, it works fine.

So the question is... How do I use the completion of the database read as a trigger to load attributes of the records to the view?

Thanks


Edit

What actually happened here was that VC was created globally, but never presented - since loadView was never called for it, myLabel didn't exist and, being a force unwrapped property, caused a crash when it was referenced


Solution

  • Got it, the solution looks like this:

    // In ViewController Swift file:
    
    typealias CompletionHandler = (_ recCount:Int,_ err:Error?) -> Void
    
    class ViewController: UIViewController{
    
        override func viewDidLoad() {
            super.viewDidLoad()
            readDatabase(completionHandler: { (recCount,success) -> Void in
                if err == nil {
                     self.myLabel.text = "\(recCount) records loaded"
                } else {
                     self.myLabel.text = "load failed: \(err)"     
                }
            })
        }
        @IBOutlet weak var myLabel: UILabel!
    }
    
    
    //In Another Swift file:
    
    func readDatabase() {
        let predicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "myRecord", predicate: predicate)
        let container = CKContainer.default()
        let privateDB = container.privateCloudDatabase
    
        privateDB.perform(query, inZoneWith:nil) { (allRecs, err) in
            if let recCount = allRecs?.count {
                completionHandler(recCount,err)
            } else {
                completionHandler(0,err)
            }
        }
    }
    

    The difference between this and the original is that this uses the CompletionHandler typealias in the function call for loading the database records, which returns the count of records and an optional error.

    The completion operation can now live in the ViewController class and access the UILabel using self.myLabel, which solves the error that was occurring earlier, while keeping the database loading code separate to the ViewController class.

    This version of the code also has basic error handling.