iOS - UIImageWriteToSavedPhotosAlbum
- The
completionSelector
is the selector (method) to call when the writing of the image has finished. - The
completionTarget
is the object on which to call this method.
Generally:
- Either you don't need to be notified when the writing of the image is finished (in many cases that's not useful), so you use
nil
for both parameters - Or you really want to be notified when the image file has been written to the photo album (or ended up with a writing error), and in such case, you generally implement the callback (= the method to call on completion) in the same class that you called the
UIImageWriteToSavedPhotosAlbum
function from, so thecompletionTarget
will generally beself
As the documentation states, the completionSelector
is a selector representing a method with the signature described in the documentation, so it has to have a signature like:
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo: (void *) contextInfo;
It does not have to have this exact name, but it has to use the same signature, namely take 3 parameters (the first being an UIImage
, the second an NSError
and the third being of void*
type) and return nothing (void
).
Example
You may for example declare and implement a method that you could call anything like this :
- (void)thisImage:(UIImage *)image hasBeenSavedInPhotoAlbumWithError:(NSError *)error usingContextInfo:(void*)ctxInfo {
if (error) {
// Do anything needed to handle the error or display it to the user
} else {
// .... do anything you want here to handle
// .... when the image has been saved in the photo album
}
}
And when you call UIImageWriteToSavedPhotosAlbum
you will use it like this:
UIImageWriteToSavedPhotosAlbum(theImage,
self, // send the message to 'self' when calling the callback
@selector(thisImage:hasBeenSavedInPhotoAlbumWithError:usingContextInfo:), // the selector to tell the method to call on completion
NULL); // you generally won't need a contextInfo here
Note the multiple ':' in the @selector(...)
syntax. The colons are part of the method name so don't forget to add these ':' in the @selector (event the trainling one) when you write this line!
UIImageWriteToSavedPhotosAlbum not working
Swift version: 5.2
The problem is QRCode is CIImage so we need to convert it to UIImage in-order to save to Photos Album
QRCodeGenerator.swift
import UIKit
final class QrCodeGenerator {
func generateQRCode(from string: String) -> UIImage? {
let data = string.data(using: String.Encoding.ascii)
if let filter = CIFilter(name: "CIQRCodeGenerator") {
filter.setValue(data, forKey: "inputMessage")
let transform = CGAffineTransform(scaleX: 5, y: 5) // Scale according to imgView
if let output = filter.outputImage?.transformed(by: transform) {
return convert(output)
}
}
return nil
}
private func convert(_ cmage:CIImage) -> UIImage? {
let context:CIContext = CIContext(options: nil)
guard let cgImage:CGImage = context.createCGImage(cmage, from: cmage.extent) else { return nil }
let image:UIImage = UIImage(cgImage: cgImage)
return image
}
}
ImageSaver.swift
import UIKit
final class ImageSaver: NSObject {
func writeToPhotoAlbum(image: UIImage) {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveError), nil)
}
@objc func saveError(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
if let error = error {
print("error: \(error.localizedDescription)")
} else {
print("Save completed!")
}
}
}
QrCodeViewController
import UIKit
class QrCodeViewController: UIViewController {
@IBOutlet weak var qrCodeImageView: UIImageView!
private let qrCodeGenerator = QrCodeGenerator()
private let imageSaver = ImageSaver()
init() {
super.init(nibName: "QrCodeViewController", bundle: nil)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save Photo", style: .plain, target: self, action: #selector(addTapped))
guard let image = qrCodeGenerator.generateQRCode(from: "mock") else { return }
qrCodeImageView.image = image
}
@objc func addTapped() {
guard let inputImage = QrCodeGenerator().generateQRCode(from: "mock") else { assertionFailure("null image"); return }
imageSaver.writeToPhotoAlbum(image: inputImage)
}
}
For letting the app to be able to write (add) photos into the device photos library we have to add the NSPhotoLibraryAddUsageDescription
into the application's plist file:
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Our application needs permission to write photos...</string>
Using completionSelector and completionTarget with UIImageWriteToSavedPhotosAlbum
import UIKit
class ViewController: UIViewController {
@IBAction func buttonPressed(sender: AnyObject) {
presentImagePickerController()
}
}
extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func presentImagePickerController() {
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
imagePickerController.sourceType = .PhotoLibrary
imagePickerController.allowsEditing = false
presentViewController(imagePickerController, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
UIImageWriteToSavedPhotosAlbum(image, self, "image:didFinishSavingWithError:contextInfo:", nil)
}
func imagePickerControllerDidCancel(picker: UIImagePickerController) {
self.dismissViewControllerAnimated(true, completion: nil)
}
func image(image: UIImage, didFinishSavingWithError error: NSError?, contextInfo:UnsafePointer<Void>) {
guard error == nil else {
//Error saving image
return
}
//Image saved successfully
}
}
iOS10 UIImageWriteToSavedPhotosAlbum TCC__CRASHING_DUE_TO_PRIVACY_VIOLATION
You can save any UIImage to the photo album but first you must ask the user for permission to do as this as it is indeed a privacy issue. If they don't give you access then you can't save the image at all.
The most sensible approach is to add the required privacy key to the info.plist.
This is the info.plist xml definition although it's easier to add the keys in the property list:
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Message requesting the ability to add to the photo library</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Message requestion the ability to access the photo library</string>
If you add these then when you first try to access or add to the photo library a popup will display with your message allowing the user to decide if they want your app to have access.
One good reason to put it in the info.plist file is that all the requests for access are then in a single easily visible place instead of somewhere random in your project.
EDIT
Here is how to save the image in documents which does not raise and privacy issues:
NSData *imageData = UIImagePNGRepresentation(image);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0]; //Get the docs directory
NSString *filePath = [documentsPath stringByAppendingPathComponent:@"image.png"]; //Add the file name
[imageData writeToFile:filePath atomically:YES]; //Write the file
If you want a jpg instead of a png use this instead:
NSData *imageData = UIImageJPEGRepresentation(image, 0.9); // Use whatever compression ratio you want instead of 0.9
UIImageWriteToSavedPhotosAlbum save as PNG with transparency?
This is a problem I have noticed before and reported on the Apple Developer Forums about a year ago. As far as I know it is still an open issue.
If you have a moment, please take the time to file a feature request at Apple Bug Report. If more people report this issue, it is more likely that Apple will fix this method to output non-lossy, alpha-capable PNG.
EDIT
If you can compose your image in memory, I think something like the following would work or at least get you started:
- (UIImage *) composeImageWithWidth:(NSInteger)_width andHeight:(NSInteger)_height {
CGSize _size = CGSizeMake(_width, _height);
UIGraphicsBeginImageContext(_size);
// Draw image with Quartz 2D routines over here...
UIImage *_compositeImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return _compositeImage;
}
//
// cf. https://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/FilesandNetworking/FilesandNetworking.html#//apple_ref/doc/uid/TP40007072-CH21-SW20
//
- (BOOL) writeApplicationData:(NSData *)data toFile:(NSString *)fileName {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
if (!documentsDirectory) {
NSLog(@"Documents directory not found!");
return NO;
}
NSString *appFile = [documentsDirectory stringByAppendingPathComponent:fileName];
return ([data writeToFile:appFile atomically:YES]);
}
// ...
NSString *_imageName = @"myImageName.png";
NSData *_imageData = [NSData dataWithData:UIImagePNGRepresentation([self composeImageWithWidth:100 andHeight:100)];
if (![self writeApplicationData:_imageData toFile:_imageName]) {
NSLog(@"Save failed!");
}
UIImageWriteToSavedPhotosAlbum with UIImageView in iOS 9
You need to take a snapshot of your UIImageView
before calling UIImageWriteToSavedPhotosAlbum
. Add following extension to UIView
and call this function:
func takeSnapshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.myImageView.bounds.size, false, UIScreen.mainScreen().scale);
self.drawViewHierarchyInRect(self.myImageView.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
Related Topics
Swift Array Append Overwriting Other Array Values
Alamofire: Sending JSON as Request Parameter
Gmsgeocoder Reversegeocodecoordinate: Completionhandler: on Background Thread
Retrieve User Defaults Information from an Apple Watch
Save an Object in Nsuserdefaults and Realm
Opening a PDF Document When Clicking a Button
Waiting for Asynchronous Function Call to Complete
Swift:Non-Nil Optional Value Raising a Nil Exception
How to Control Shadow Spread and Blur
Cordova: Start Specific iOS Emulator Image
How to Draw a Smooth Circle with Cashapelayer and Uibezierpath
Over the Air (Ota) iOS IPA File Distribution for Public
Using Tesseract to Recognize License Plates
How to Use Charles Proxy on the Xcode 6 (iOS 8) Simulator
Sizing a Container View with a Controller of Dynamic Size Inside a Scrollview
Populating Tableview with Multiple Sections and Multiple Dictionary in an Array in Swift