You are currently viewing How to Write Images to Camera Roll in iOS

How to 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.

Getting camera roll write permission in iOS
Adding NSPhotoLibraryAddUsageDescription key into “info.plist”

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! If you find this article helpful, I think you will enjoy reading the following articles as well:

Feel free to follow me on Twitter, and subscribe to my monthly newsletter so that you won’t miss out on any of my upcoming iOS development-related articles

Thanks for reading. 👨🏻‍💻


[Updated: 26 March 2022]

Kudos to Filip Nemecek for pointing out that Apple does in fact provide closure-based APIs for writing images to camera roll. Make sure to check out his blog post to find out more.


👋🏻 Hey!

While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.

  • Bartender: Superpower your menu bar and take full control over your menu bar items.
  • CleanShot X: The best screen capture app I’ve ever used.
  • PixelSnap: Measure on-screen elements with ease and precision.
  • iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.