iOS - Uiimagewritetosavedphotosalbum

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 the completionTarget will generally be self

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



Leave a reply



Submit