» » Write Images to Camera Roll in iOS

Write Images to Camera Roll in iOS

Writing an image to the camera roll is fairly simple in iOS development, but when it comes to detecting when the image has been written to the camera roll, it is not as simple as we think. Surprisingly, Apple does not give us any closure-based APIs that we can leverage to know exactly when the write operation is completed.

To get the job done, we must make use of some APIs created way back in Objective-C time. Let’s buckle up and get ready to deal with some old school UIKit API that involves target, selector and UnsafeRawPointer.


Getting Write Access to Camera Roll

Before we can start writing images to the camera roll, it is mandatory to obtain a write permission from the users. As you might have expected, we need to add the NSPhotoLibraryAddUsageDescription key into “info.plist” and provide a reason why we want to access the camera roll.

Our app will crash with the following error message if we try to access the camera roll without the user’s permission.

This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSPhotoLibraryAddUsageDescription key with a string value explaining to the user how the app uses this data.

With that out of the way, it’s time to write some images to the camera roll.


Detecting Camera Roll Write Operation Completion

Writing an image to the camera roll only required 1 line of code:

UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)

This single line of code will trigger a background operation that writes a given image to the camera roll. If we take a look at the function declaration:

func UIImageWriteToSavedPhotosAlbum(_ image: UIImage, 
                                    _ completionTarget: Any?, 
                                    _ completionselector: Selector?, 
                                    _ contextInfo: UnsafeMutableRawPointer?)

You will notice that the 2nd and 3rd parameters are what we need in order to detect when an image is successfully written to the camera roll.

The completionTarget is the object whose selector should be called after the image has been written to the camera roll. Whereas the completionselector is the method selector of the completionTarget object to call and it must conform to the following signature:

func image(_ image: UIImage,
           didFinishPhotoLibrarySavingWithError error: Error?,
           contextInfo: UnsafeRawPointer)

With that in mind, detecting the completion of the write operation should be fairly straightforward:

func writeImageToCameraRoll(_ image: UIImage) {
    
    UIImageWriteToSavedPhotosAlbum(
        image,
        self,
        #selector(image(_:didFinishPhotoLibrarySavingWithError:contextInfo:)),
        nil
    )
}

/// This will trigger when finish writing 1 image to photo library
@objc private func image(_ image: UIImage,
                         didFinishPhotoLibrarySavingWithError error: Error?,
                         contextInfo: UnsafeRawPointer) {
    
    print("Image successfully written to camera roll")
}

Make sure to mark the completion selector with the @objc attribute. This is to let the compiler know that we want to interact with the Objective-C runtime.

That’s it! That’s how we can detect an image has been successfully written to the camera roll.

Now, imagine a situation where we want to write multiple images to the camera roll, and we want to know exactly when each individual image has been successfully saved. How should we go about that? This is where the 4th parameter (contextInfo) comes into play.


Passing Data Using UnsafeRawPointer

If Swift is the first language you use for iOS development, most likely you have not used or seen UnsafeRawPointer before. In fact, it is not that commonly used in Objective-C either. Therefore, I think it is not worth it to spend time explaining what an UnsafeRawPointer is. Instead, I will focus on showing you how to use UnsafeRawPointer in this specific use case.

Let’s say all the images that we are writing to the camera roll have a unique ID. Then we can pass this ID to the completion selector and use it to identify the image that triggers the selector.

First, let’s create a simple class to hold this unique ID:

final class CameraRollContext {
    let imageId: String
    
    init(imageId: String) {
        self.imageId = imageId
    }
}

Note that CameraRollContext must be a reference type so that we can convert it to become UnsafeRawPointer and pass it into UIImageWriteToSavedPhotosAlbum() like so:

let context = CameraRollContext(imageId: imageId)

// Convert CameraRollContext to UnsafeRawPointer
let rawPointer = UnsafeMutableRawPointer(Unmanaged.passRetained(context).toOpaque())

UIImageWriteToSavedPhotosAlbum(
    image,
    self,
    #selector(image(_:didFinishPhotoLibrarySavingWithError:contextInfo:)),
    rawPointer
)

The UnsafeRawPointer that we pass in will then be given back to us as contextInfo in the completion selector. With that, all that’s left to do is convert contextInfo back to CameraRollContext.

@objc private func image(_ image: UIImage,
                         didFinishPhotoLibrarySavingWithError error: Error?,
                         contextInfo: UnsafeRawPointer) {
    
    // Convert contextInfo to CameraRollContext
    let context: CameraRollContext = Unmanaged<CameraRollContext>.fromOpaque(contextInfo).takeRetainedValue()
    let imageId = context.imageId

    print("\(imageId) successfully written to camera roll")
}

Creating a Wrapper Class for Writing Images

If you feel that the sample code above is not Swifty and difficult to read, there is nothing stopping you from creating your own wrapper class that triggers a closure-based callback when an image is written to the camera roll.

However, do keep in mind that your wrapper class must be a subclass of NSObject. Failing to do so will give you the following runtime error when writing images to the camera roll:

NSForwarding: warning: object 0x280d3b120 of class 'DemoProject.CameraRollWrapperClass' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector -[DemoProject.CameraRollWrapperClass methodSignatureForSelector:]

Wrapping Up

There you have it!

 

Related Articles

Add Your Comment

reload, if the code cannot be seen

All comments will be moderated before being published.