NEW: Join my free 100 Days of SwiftUI challenge today! >>

How to save images to the user’s photo library

Paul Hudson    @twostraws   

Before we’re done with the techniques for this project, there’s one last piece of UIKit joy we need to tackle: once we’ve processed the user’s image we’ll get a UIImage back, but we need a way to save that processed image to the user’s photo library. This uses a UIKit function called UIImageWriteToSavedPhotosAlbum(), which in its simplest form is trivial to use, but in order to make it work usefully you need to wade back into UIKit. At the very least it will make you really appreciate how much better SwiftUI is!

Before we write any code, I need you to make one small change to the Info.plist file for your project. You see, writing to the photo library is a protected operation, which means we can’t do it without explicit permission from the user.

iOS will take care of asking for permission and checking the response, but we need to provide a short string explaining to users why we want to write images in the first place. To do that, open Info.plist, right-click on some empty space, then choose Add Row. You’ll see a dropdown list of options to choose from – I’d like you to scroll down and select “Privacy - Photo Library Additions Usage Description”. For the value on its right, please enter the text “We want to save the filtered photo.”

With that done, we can now use the UIImageWriteToSavedPhotosAlbum() method to write out a picture. We already have this loadImage() method from our previous work:

func loadImage() {
    guard let inputImage = inputImage else { return }
    image = Image(uiImage: inputImage)
}

We could modify that so it immediately saves the image that got loaded, effectively creating a duplicate. Add this line to the end of the method:

UIImageWriteToSavedPhotosAlbum(inputImage, nil, nil, nil)

And that’s it – every time you import an image, our app will save it back to the photo library. The first time you try it, iOS will automatically prompt the user for permission to write the photo and show the string we added to the Info.plist file.

Now, you might look at that and think “that was easy!” And you’d be right. But the reason it’s easy is because we did the least possible work: we provided the image to save as the first parameter to UIImageWriteToSavedPhotosAlbum(), then provided nil as the other three.

Those nil parameters matter, or at least the first two do: they tell Swift what method should be called when saving completes, which in turn will tell us whether the save operation succeeded or failed. If you don’t care about that then you’re done – passing nil for all three is fine. But remember: users can deny access to their photo library, so if you don’t catch the save error they’ll wonder why your app isn’t working properly.

The reason it takes UIKit two parameters to know which function to call is because this code is old – way older than Swift, and in fact so old it even pre-dates Objective-C’s equivalent of closures. So instead, this uses a completely different way of referring to functions: in place of the first nil we should point to an object, and in place of the second nil we should point to the name of the method that should be called.

If that sounds bad, I’m afraid you only know half the story. You see, both of those two parameters have their own complexities:

  • The object we provide must be a class, and it must inherit from NSObject. This means we can’t point to a SwiftUI view struct.
  • The method is provided as a method name, not an actual method. This method name was used by Objective-C to find the actual code at runtime, which could then be run. That method needs to have a specific signature (list of parameters) otherwise our code just won’t work.

But wait: there’s more! For performance reasons, Swift prefers not to generate code in a way that Objective-C can read – that whole “look up methods at runtime” thing was really neat, but also really slow. And so, when it comes to writing the method name we need to do two things:

  1. Mark the method using a special compiler directive called #selector, which asks Swift to make sure the method name exists where we say it does.
  2. Add an attribute called @objc to the method, which tells Swift to generate code that can be read by Objective-C.

You know, I wrote UIKit code for over a decade before I switched to SwiftUI, and already writing out all this explanation makes this old API seem like a crime against humanity. Sadly it is what it is, and we’re stuck with it.

Before I show you the code, I want to mention the fourth parameter. So, the first one is the image to save, the second one is an object that should be notified about the result of the save, the third one is the method on the object that should be run, and then there’s the fourth one. We aren’t going to be using it here, but you need to be aware of what it does: we can provide any sort of data here, and it will be passed back to us when our completion method is called.

This is what UIKit calls “context”, and it helps you identify one image save operation from another. You can provide literally anything you want here, so UIKit uses the most hands-off type you can imagine: a raw chunk of memory that Swift makes no guarantees about whatsoever. This has its own special type name in Swift: UnsafeRawPointer. Honestly, if it weren’t for the fact that we had to use it here I simply wouldn’t even tell you it existed, because it’s not really useful at this point in your app development career.

Anyway, that’s more than enough talk. Before you decide to throw this project away and go straight to the next one, let’s get this over and done with.

As I’ve said, to write an image to the photo library and read the response, we need some sort of class that inherits from NSObject. Inside there we need a method with a precise signature that’s marked with @objc, and we can then call that from UIImageWriteToSavedPhotosAlbum().

Putting all that together, please add this class somewhere outside of ContentView:

class ImageSaver: NSObject {
    func writeToPhotoAlbum(image: UIImage) {
        UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveError), nil)
    }

    @objc func saveError(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
        print("Save finished!")
    }
}

With that in place we can now use it from SwiftUI, like this:

let imageSaver = ImageSaver()
imageSaver.writeToPhotoAlbum(image: inputImage)

If you run the code now, you should see the “Save finished!” message output as soon as you select an image. Yes, that is remarkably little code given how much explanation it needed, but on the bright side that completes the overview for this project so at long (long, long!) last we can get into the actual implementation. Please go ahead and put your project back to its default state so we have a clean slate to work from.

LEARN SWIFTUI FOR FREE I have a massive, free SwiftUI video collection on YouTube teaching you how to build complete apps with SwiftUI – check it out!

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5