Start developing iOS 10 - 22 with Swift using CloudKit


Last article Start developing iOS 10 - 21 with Swift using WK WebView and SFSafariViewController Learn how to open a web page. This article learns to use CloudKit.

iCloud was originally released by Jobs in WWDC 2011 to allow Apps and games to store data in the cloud and automatically synchronize data between Mac and iOS.
In recent years, it has gradually grown to serve the cloud. If you want to create apps that share data between users, you can consider using iCloud, so you don't need to build a back-end APIs yourself. Although the practical application of iCloud may not work well due to the domestic network environment, it is still necessary to learn something about it.

If you develop a Web application, you can also access the data in iCloud of an iOS application. Apple provides CloudKit JS and CloudKit libraries, respectively.


CloudKit defaults to providing free space for our app:


As the number of active app users increases, the free space will also increase. Introduction to Official Website.


Understanding the CloudKit framework

The CloudKit framework provides not only storage capabilities, but also various interactions between developers and iCloud. Containers and database are the basic elements of the CloudKit framework.

  • By default, an app is a container, the code is CKContainer, and a container contains three databases (CKDatabase):
    • A public database: All users in app can view it
    • A shared database: A group of users in app can view (iOS 10)
    • A private database: Viewed by a single user in app

Containers and Database

Record zones and Records

Add CloudKit to the application

  • First, you need a developer account.

  • Then open iCloud in Capabilities.


Managing Record s in CloudKit Dashboard

  • Click Cloud Kit Dashboard in the image above, or visit it directly https://icloud.developer.apple.com/dashboard/ . The latest CloudKit Dashboard page has changed a bit. The first entry is the application list (that is, the container list). Click on one to go to the following page:

  • Click on Development data, similar to the following

  • Select Record Types (a bit like tables in relational data), create a new type Restaurant, and add several Field's.

  • Select Records (data in the type table), add several pieces of data, and pay attention to selecting public database.

Using Convenience API to obtain public Database

CloudKit provides two APIs for development to interact with iCloud: the convenience API and the operational API.

  • The Convenience API is commonly invoked in the following way:

      let cloudContainer = CKContainer.default()
      let publicDatabase = cloudContainer.publicCloudDatabase
      let predicate = NSPredicate(value: true)
      let query = CKQuery(recordType: "Restaurant", predicate: predicate)
      publicDatabase.perform(query, inZoneWith: nil, completionHandler: {
                  (results, error) -> Void in
      // Process the records
      })
    
    • CKContainer.default() Gets the Container of the application.
    • Public Cloud Database represents the default public database.
    • NSPredicate and CKQuery are search conditions
  • Create a new DiscoverTableViewController, inherit it to UITableViewController, associate the table view controller in discover.storyboard, and modify its prototype cell identifier to Cell.

  • Add import CloudKit to DiscoverTableViewController.swift and define an array variable of CKRecord:

    var restaurants:[CKRecord] = []
    
  • Add a function to get Records:

      func fetchRecordsFromCloud() {
          
          let cloudContainer = CKContainer.default()
          let publicDatabase = cloudContainer.publicCloudDatabase
          let predicate = NSPredicate(value: true)
          let query = CKQuery(recordType: "Restaurant", predicate: predicate)
          publicDatabase.perform(query, inZoneWith: nil, completionHandler: {
              (results, error) -> Void in
              
              if error != nil {
                  print(error)
                  return
              }
              
              if let results = results {
                  print("Completed the download of Restaurant data")
                  self.restaurants = results
                  
                  OperationQueue.main.addOperation {
                      self.spinner.stopAnimating()
                      self.tableView.reloadData()
                  }
              }
          })
      }
    

    In perform ance, when it is determined that the data is obtained, it is assigned to restaurants and the table is refreshed.

  • Add: fetchRecords FromCloud () in viewDidLoad.

  • Add table view related proxy method:

override func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return restaurants.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for:
indexPath)
    // Configure the cell...
    let restaurant = restaurants[indexPath.row]
    cell.textLabel?.text = restaurant.object(forKey: "name") as? String
    if let image = restaurant.object(forKey: "image") {
        let imageAsset = image as! CKAsset
        if let imageData = try? Data.init(contentsOf: imageAsset.fileURL) {
            cell.imageView?.image = UIImage(data: imageData)
        } 
    }
    return cell
}
  • object(forKey:) is the method of obtaining Record Field value in CKRecord.
  • Picture objects are converted to CKAsset.

Why slow?

Testing the above code, we found that the print information "Completed the download of Restaurant data" in the fetchRecords FromCloud function has been displayed on the console, but it will take a while for App to display, that is to say, it will take some time to get the data from iCloud to start preparing table view loading.


This requires the use of the concept of multithreading. In iOS, UI updates (like table reloads) must be performed in the main thread. In this way, UI updates are synchronized when threads that get iCloud data are in progress.

OperationQueue.main.addOperation {
    self.tableView.reloadData()
}

Using the operational API to obtain public Database

** Convenience API** is only suitable for simple and small queries.

  • Update the fetchRecordsFromCloud method:
      func fetchRecordsFromCloud() {
          
          let cloudContainer = CKContainer.default()
          let publicDatabase = cloudContainer.publicCloudDatabase
          let predicate = NSPredicate(value: true)
          let query = CKQuery(recordType: "Restaurant", predicate: predicate)
          // Create the query operation with the query
          let queryOperation = CKQueryOperation(query: query)
          queryOperation.desiredKeys = ["name", "image"]
          queryOperation.queuePriority = .veryHigh
          queryOperation.resultsLimit = 50
          queryOperation.recordFetchedBlock = { (record) -> Void in
              self.restaurants.append(record)
          }
          queryOperation.queryCompletionBlock = { (cursor, error) -> Void in
              if let error = error {
                  print("Failed to get data from iCloud - \(error.localizedDescription)")
                  return
              }
              print("Successfully retrieve the data from iCloud")
              OperationQueue.main.addOperation {
                  self.tableView.reloadData()
              }
          }
          // Execute the query
          publicDatabase.add(queryOperation)
      }
    
    
    • By replacing the perform ance method with CKQueryOperation, it provides many query options.
    • desiredKeys represent fields that need to be queried.
    • resultsLimit represents the maximum number of records queried in turn

Loading instructions (chrysanthemum turn)

  • You can add the following types of code in viewDidLoad:
    let spinner:UIActivityIndicatorView = UIActivityIndicatorView()
    spinner.activityIndicatorViewStyle = .gray
    spinner.center = view.center
    spinner.hidesWhenStopped = true
    view.addSubview(spinner)
    spinner.startAnimating()
    
  • You can also add:

After adding the discovery ** activity indicator view on the controller, which is called The Extra Views ** in Xcode


Add interfaces to DiscoverTableViewController and associate them.

  @IBOutlet var spinner: UIActivityIndicatorView!

Add code in viewDidLoad:

  spinner.hidesWhenStopped = true
  spinner.center = view.center
  tableView.addSubview(spinner)
  spinner.startAnimating()
  • Hide the loading prompt when the data is loaded:
    OperationQueue.main.addOperation {
      self.spinner.stopAnimating()
      self.tableView.reloadData()
     }
    

Lazy loading pictures

Lazy loading image is to first load a local default image, temporarily do not load remote images, when the picture is ready to update the picture view.


  • Modify the request field desireKeys so that no pictures are added at the beginning: queryOperation.desiredKeys = ["name"]

  • Update tableView (: CellForRowAt:):

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        
        let restaurant = restaurants[indexPath.row]
        cell.textLabel?.text = restaurant.object(forKey: "name") as? String
        
        
        // Set the default image
        cell.imageView?.image = UIImage(named: "photoalbum")
        // Fetch Image from Cloud in background
        let publicDatabase = CKContainer.default().publicCloudDatabase
        let fetchRecordsImageOperation = CKFetchRecordsOperation(recordIDs:[restaurant.recordID])
        fetchRecordsImageOperation.desiredKeys = ["image"]
        fetchRecordsImageOperation.queuePriority = .veryHigh
        fetchRecordsImageOperation.perRecordCompletionBlock = { (record, recordID, error) -> Void in
            if let error = error {
                print("Failed to get restaurant image: \(error.localizedDescription)")
                return
            }
            if let restaurantRecord = record {
                OperationQueue.main.addOperation() {
                    if let image = restaurantRecord.object(forKey: "image") {
                        let imageAsset = image as! CKAsset
                        print(imageAsset.fileURL)
                        if let imageData = try? Data.init(contentsOf:
                            imageAsset.fileURL) {
                            cell.imageView?.image = UIImage(data: imageData)
                        }
                    }
                }
            }
        }
        publicDatabase.add(fetchRecordsImageOperation)
        return cell
    }
  • CKFetchRecords Operation obtains a specific Record through the recordID.
  • Some methods of CKFetchRecords Operation are similar to CKQuery Operation.

After lazy loading, it is found that the images are displayed in other views and then slowly loaded and displayed.


Dropdown refresh

UIRefreshControl provides a standard drop-down refresh feature.

  • Add:
        // Pull To Refresh Control
        refreshControl = UIRefreshControl()
        refreshControl?.backgroundColor = UIColor.white
        refreshControl?.tintColor = UIColor.gray
        refreshControl?.addTarget(self, action: #selector(fetchRecordsFromCloud), for: UIControlEvents.valueChanged)

Each drop-down displays the chrysanthemum turn and calls the fetchRecordsFromCloud method.

  • Remove chrysanthemum transcoding after adding data to queryCompletion Block of fetchRecords FromCloud method:
if let refreshControl = self.refreshControl {
    if refreshControl.isRefreshing {
        refreshControl.endRefreshing()
    }
}
  • Refreshing results in duplicate data, which is cleaned up at the beginning of the fetchRecords FromCloud method:
restaurants.removeAll()
tableView.reloadData()

Use CloudKit to save data to iCloud

The save (: completionHandler:) method of CKDatabase can be used to save data to iCloud.
When the user adds new data, both local Core Data and iCloud are saved.

  • Add: import CloudKit to AddRestaurant Controller.

  • Add methods in AddRestaurantController:

      // Save to Core Data as well as iCloud
      func saveRecordToCloud(restaurant:RestaurantMO!) -> Void {
          // Prepare the record to save
          let record = CKRecord(recordType: "Restaurant")
          record.setValue(restaurant.name, forKey: "name")
          record.setValue(restaurant.type, forKey: "type")
          record.setValue(restaurant.location, forKey: "location")
          record.setValue(restaurant.phone, forKey: "phone")
          let imageData = restaurant.image as! Data
          // Resize the image
          let originalImage = UIImage(data: imageData)!
          let scalingFactor = (originalImage.size.width > 1024) ? 1024 /
              originalImage.size.width : 1.0
          let scaledImage = UIImage(data: imageData, scale: scalingFactor)!
          // Write the image to local file for temporary use
          let imageFilePath = NSTemporaryDirectory() + restaurant.name!
          let imageFileURL = URL(fileURLWithPath: imageFilePath)
          try? UIImageJPEGRepresentation(scaledImage, 0.8)?.write(to: imageFileURL)
          // Create image asset for upload
          let imageAsset = CKAsset(fileURL: imageFileURL)
          record.setValue(imageAsset, forKey: "image")
          // Get the Public iCloud Database
          let publicDatabase = CKContainer.default().publicCloudDatabase
          // Save the record to iCloud
          publicDatabase.save(record, completionHandler: { (record, error) -> Void in
              try? FileManager.default.removeItem(at: imageFileURL)
          })
      }
    
  • Before dismiss(animated:completion:) of save method, add:

     saveRecordToCloud(restaurant: restaurant)
    

sort

CKQuery has the attribute sortDescriptors that can be used for sorting.
In the fetchRecords FromCloud method of DiscoverTableViewController, after the query definition, add:

    query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]

Creation Date is the default creation time field.

Exercise: Modifying Discover Styles


  • Create a new Discover Table ViewCell, inherit it to UITable ViewCell, and associate Discover cell.
  • Modify the appropriate cell style, such as the following

  • Create four new interfaces in DiscoverTable ViewCell and associate them.
    @IBOutlet var nameLabel: UILabel!
    @IBOutlet var locationLabel: UILabel!
    @IBOutlet var typeLabel: UILabel!
    @IBOutlet var thumbnailImageView: UIImageView!
  • Update tableView (: CellForRowAt:) and fetchRecords FromCloud related code

Code

Beginning-iOS-Programming-with-Swift

Explain

This article is about learning. appcode A book published on the website <Beginning iOS 10 Programming with Swift> A Record

Catalogue of Series Articles

Keywords: iOS Swift Database Programming

Added by SuNcO on Sat, 08 Jun 2019 03:19:50 +0300