
This video is only available to subscribers. Start a subscription today to get access to this and 472 other videos.
Working with Images
This episode is part of a series: Hello CloudKit.
1. Hello Cloud Kit - Part 1 10 min |
2. Hello Cloud Kit - Part 2 10 min |
3. CloudKit Querying 12 min |
4. CloudKit References 7 min |
5. Fetching and Saving References 15 min |
6. Working with Images 15 min |
7. Fetching Paged Images 8 min |
8. CKRecord Upload Progress 6 min |
9. Isolating CloudKit from your Controllers 16 min |
10. Extracting CKRecordWrapper 7 min |
11. CloudKit Notes Manager 11 min |
12. CloudKit Notes Manager Continued 14 min |
Episode Links
Creating the Photo model
class Photo {
static let recordType = "Photo"
let record: CKRecord
init(fullsizeImage: UIImage, thumbnail: UIImage, restaurantID: CKRecordID) {
record = CKRecord(recordType: Photo.recordType)
record["restaurant"] = CKReference(recordID: restaurantID, action: .deleteSelf)
record["image"] = CKAsset(image: fullsizeImage, compression: 0.9)
record["thumbnail"] = CKAsset(image: thumbnail, compression: 0.7)
}
}
This relies on a helper method to create an asset from an in-memory UIImage
:
extension CKAsset {
convenience init(image: UIImage, compression: CGFloat) {
let fileURL = ImageHelper.saveToDisk(image: image, compression: compression)
self.init(fileURL: fileURL)
}
var image: UIImage? {
guard let data = try? Data(contentsOf: fileURL),
let image = UIImage(data: data) else {
return nil
}
return image
}
}
Selecting the Image to Upload
We'll use the built-in UIImagePickerController
class to select an image from the photo library:
@IBAction func uploadPhoto(_ sender: Any) {
let picker = UIImagePickerController()
picker.sourceType = .savedPhotosAlbum
picker.delegate = self
present(picker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let originalImage = info[UIImagePickerControllerOriginalImage] as! UIImage
dismiss(animated: true, completion: {
self.spinner.startAnimating()
DispatchQueue.global(qos: .userInitiated).async {
// TODO
}
})
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
Once we've grabbed the image, we need to create all the versions of this image. In our case we want a thumbnailed version of this image. We don't have a server for this app, so we'll create the versions on the client and add them to the record. Later, when we fetch the Photo
record, we can specify which version we want to use.
Resizing the Photo into Various Sizes
We start with a quick helper method to save a smaller square version of the photo:
struct ImageHelper {
static func createThumbnail(from image: UIImage, fillingSize size: CGSize) -> UIImage {
let scale = max(size.width / image.size.width, size.height / image.size.height)
let width = image.size.width * scale
let height = image.size.height * scale
let thumbnailRect = CGRect(
x: (size.width - width) / 2,
y: (size.height - height) / 2,
width: width,
height: height)
UIGraphicsBeginImageContext(size)
image.draw(in: thumbnailRect)
let thumbnail = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return thumbnail!
}
}
Creating and Saving the Photo Record
Then we create our Photo
instance...
private func createPhoto(image: UIImage) {
let thumbnail = ImageHelper.createThumbnail(from: image, fillingSize: CGSize(width: 200, height: 200))
let photo = Photo(fullsizeImage: image, thumbnail: thumbnail, restaurantID: restaurantID)
savePhoto(photo)
}
We can then save it on CloudKit:
private func savePhoto(_ photo: Photo) {
database.save(photo.record) { record, error in
if let e = error {
print("Error saving photo: \(e)")
} else {
self.prepend(photo: photo)
}
DispatchQueue.main.async {
self.spinner.stopAnimating()
}
}
}
The prepend(photo:)
method just adds to the local array and inserts an item into the collection view:
private func prepend(photo: Photo) {
photos.insert(photo.record, at: 0)
DispatchQueue.main.async {
self.collectionView?.insertItems(at: [IndexPath(item: 0, section: 0)])
}
}