Search code examples
iosswiftrefreshcloudkitckrecord

Feed loses all data after refresh or restart


I am using cloudkit to create an app like twitter and everything works fine until I refresh or restart the app. There is no error, but the feed becomes empty and loses all data. I have not tried to see what happens when multiple people use the app.

class SweetsTableViewController: UITableViewController {
    var sweets = [CKRecord]()
    var refresh: UIRefreshControl!
    override func viewDidLoad() {
        super.viewDidLoad()
        refresh = UIRefreshControl()
        refresh.attributedTitle = NSAttributedString(string: "Pull to Refresh")
        refresh.addTarget(self, action: "loadData", forControlEvents: .ValueChanged)
        self.tableView.addSubview(refresh)
        loadData()
    }
    func loadData()
    {
        sweets = [CKRecord]()
        let publicData = CKContainer.defaultContainer().publicCloudDatabase
        let query = CKQuery(recordType: "Beam", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray:nil))
        query.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: false)]
        publicData.performQuery(query, inZoneWithID: nil) { (results: [CKRecord]?, error: NSError?) -> Void in
            if let sweets = results
            {
                self.sweets = sweets
                dispatch_async(dispatch_get_main_queue(), { () -> Void in
                    self.tableView.reloadData()
                    self.refresh.endRefreshing()
                })
            }
        }
    }
    @IBAction func sendSweet(sender: AnyObject)
    {
        let alert = UIAlertController(title: "New Beam", message: "Type a Beam", preferredStyle: .Alert)
        alert.addTextFieldWithConfigurationHandler { (textField: UITextField) in
            textField.placeholder = "Your Beam"
        }
        alert.addAction(UIAlertAction(title: "Send", style: .Default, handler: {(action: UIAlertAction) -> Void in
            let textField = alert.textFields!.first!
            if textField.text != ""
            {
                let newBeam = CKRecord(recordType: "Beam")
                newBeam["content"] = textField.text
                let publicData = CKContainer.defaultContainer().publicCloudDatabase
                publicData.saveRecord(newBeam, completionHandler: {(record:CKRecord?, error: NSError?) -> Void in
                    if error == nil
                    {
                        dispatch_async(dispatch_get_main_queue(), {() -> Void in
                            print("Beam saved")
                            self.tableView.beginUpdates()
                            self.sweets.insert(newBeam, atIndex: 0)
                            let indexPath = NSIndexPath(forRow: 0, inSection: 0)
                            self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Top)
                            self.tableView.endUpdates()
                        })
                    }else{
                        print(error)
                    }
                })
            }
        }))
        alert.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
        self.presentViewController(alert, animated: true, completion: nil)
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sweets.count
    }
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
        if sweets.count == 0
        {
            return cell
        }
        let sweet = sweets[indexPath.row]
        if let sweetContent = sweet["content"] as? String
        {
            let dateFormat = NSDateFormatter()
            dateFormat.dateFormat = "MM/dd/yyyy"
            let dateString = dateFormat.stringFromDate(sweet.creationDate!)
            cell.textLabel?.text = sweetContent
            cell.detailTextLabel?.text = dateString
        }
        return cell
    }

Solution

  • Records written to a CloudKit database are not always immediately returned in queries (CKQuery/CKQueryOperation).

    Query indexes are updated asynchronously so they are not guaranteed to be current. If you query for records that you recently changed and not allow enough time for those changes to be processed, the query results may be incorrect. The results may not contain the correct records and the records may be out of order.

    Reference: CKQueryOperation

    You may want to investigate using a CKSubscription / CKQuerySubscription (iOS 10+) to receive notifications when database changes occur.