web-dev-qa-db-fra.com

Comment enregistrer une image dans un album personnalisé?

J'ai une toute nouvelle application iOS qui génère des images et permet aux utilisateurs de les enregistrer dans le Camera SavedPhotosAlbum. Cependant, je veux faire quelque chose comme Snapchat et Frontback, et enregistrer ces images également dans un album personnalisé.

Donc, voici mon code:

let imageToSave = self.currentPreviewImage

let softwareContext = CIContext(options:[kCIContextUseSoftwareRenderer: true])
let cgimg = softwareContext.createCGImage(imageToSave, fromRect:imageToSave.extent())

ALAssetsLibrary().writeImageToSavedPhotosAlbum(cgimg, metadata:imageToSave.properties(), completionBlock:nil)

J'ai vu quelques exemples de personnes le faire dans Objective-C, mais rien que je puisse traduire en Swift, et j'ai vérifié les signatures de la méthode writeImageToSavedPhotosAlbum et aucun d'entre elles ne semblent autoriser l'enregistrement dans un album personnalisé.

25
rodrigoalves

Je suis venu avec cette classe singleton pour le gérer:

import Photos

class CustomPhotoAlbum {

    static let albumName = "Flashpod"
    static let sharedInstance = CustomPhotoAlbum()

    var assetCollection: PHAssetCollection!

    init() {

        func fetchAssetCollectionForAlbum() -> PHAssetCollection! {

            let fetchOptions = PHFetchOptions()
            fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName)
            let collection = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions)

            if let firstObject: AnyObject = collection.firstObject {
                return collection.firstObject as! PHAssetCollection
            }

            return nil
        }

        if let assetCollection = fetchAssetCollectionForAlbum() {
            self.assetCollection = assetCollection
            return
        }

        PHPhotoLibrary.sharedPhotoLibrary().performChanges({
            PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(CustomPhotoAlbum.albumName)
        }) { success, _ in
            if success {
                self.assetCollection = fetchAssetCollectionForAlbum()
            }
        }
    }

    func saveImage(image: UIImage) {

        if assetCollection == nil {
            return   // If there was an error upstream, skip the save.
        }

        PHPhotoLibrary.sharedPhotoLibrary().performChanges({
            let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
            let assetPlaceholder = assetChangeRequest.placeholderForCreatedAsset
            let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection)
            albumChangeRequest.addAssets([assetPlaceholder])
        }, completionHandler: nil)
    }


}

Lors de la première instanciation de la classe, l’album personnalisé sera créé s’il n’existe pas déjà. Vous pouvez enregistrer une image comme ceci:

CustomPhotoAlbum.sharedInstance.saveImage(image)

REMARQUE: La classe CustomPhotoAlbum suppose que l'application est déjà autorisée à accéder à la photothèque. Traiter avec les autorisations sort un peu du cadre de cette question/réponse. Donc, assurez-vous que PHPhotoLibrary.authorizationStatus () == .Authorize avant de l’utiliser. Et demander une autorisation si nécessaire.

28
scootermg

Latest Swift 3.0 syntax. :)

import Foundation
import Photos


class CustomPhotoAlbum: NSObject {
    static let albumName = "Album Name"
    static let sharedInstance = CustomPhotoAlbum()

    var assetCollection: PHAssetCollection!

    override init() {
        super.init()

        if let assetCollection = fetchAssetCollectionForAlbum() {
            self.assetCollection = assetCollection
            return
        }

        if PHPhotoLibrary.authorizationStatus() != PHAuthorizationStatus.authorized {
            PHPhotoLibrary.requestAuthorization({ (status: PHAuthorizationStatus) -> Void in
                ()
            })
        }

        if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized {
            self.createAlbum()
        } else {
            PHPhotoLibrary.requestAuthorization(requestAuthorizationHandler)
        }
    }

    func requestAuthorizationHandler(status: PHAuthorizationStatus) {
        if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized {
            // ideally this ensures the creation of the photo album even if authorization wasn't prompted till after init was done
            print("trying again to create the album")
            self.createAlbum()
        } else {
            print("should really Prompt the user to let them know it's failed")
        }
    }

    func createAlbum() {
        PHPhotoLibrary.shared().performChanges({
            PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: CustomPhotoAlbum.albumName)   // create an asset collection with the album name
        }) { success, error in
            if success {
                self.assetCollection = self.fetchAssetCollectionForAlbum()
            } else {
                print("error \(error)")
            }
        }
    }

    func fetchAssetCollectionForAlbum() -> PHAssetCollection? {
        let fetchOptions = PHFetchOptions()
        fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName)
        let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)

        if let _: AnyObject = collection.firstObject {
            return collection.firstObject
        }
        return nil
    }

    func save(image: UIImage) {
        if assetCollection == nil {
            return                          // if there was an error upstream, skip the save
        }

        PHPhotoLibrary.shared().performChanges({
            let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
            let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
            let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
            let enumeration: NSArray = [assetPlaceHolder!]
            albumChangeRequest!.addAssets(enumeration)

        }, completionHandler: nil)
    }
}
31
Jakub Průša

Ceci est une version mise à jour, fonctionne dans Swift 2.1 et évite le bogue dans lequel l'album n'est pas créé et les images ne sont pas enregistrées lors du premier lancement (lorsque l'autorisation d'écrire dans la photothèque est demandée/accordée pour la première fois).

class CustomPhotoAlbum: NSObject {
    static let albumName = "Name of Custom Album"
    static let sharedInstance = CustomPhotoAlbum()

    var assetCollection: PHAssetCollection!

    override init() {
        super.init()

        if let assetCollection = fetchAssetCollectionForAlbum() {
            self.assetCollection = assetCollection
            return
        }

        if PHPhotoLibrary.authorizationStatus() != PHAuthorizationStatus.Authorized {
            PHPhotoLibrary.requestAuthorization({ (status: PHAuthorizationStatus) -> Void in
                status
            })
        }

        if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.Authorized {
            self.createAlbum()
        } else {
            PHPhotoLibrary.requestAuthorization(requestAuthorizationHandler)
        }
    }

    func requestAuthorizationHandler(status: PHAuthorizationStatus) {
        if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.Authorized {
            // ideally this ensures the creation of the photo album even if authorization wasn't prompted till after init was done
            print("trying again to create the album")
            self.createAlbum()
        } else {
            print("should really Prompt the user to let them know it's failed")
        }
    }

    func createAlbum() {
        PHPhotoLibrary.sharedPhotoLibrary().performChanges({
        PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(CustomPhotoAlbum.albumName)   // create an asset collection with the album name
            }) { success, error in
                if success {
                    self.assetCollection = self.fetchAssetCollectionForAlbum()
                } else {
                    print("error \(error)")
                }
        }
    }

    func fetchAssetCollectionForAlbum() -> PHAssetCollection! {
        let fetchOptions = PHFetchOptions()
        fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName)
        let collection = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions)

        if let _: AnyObject = collection.firstObject {
            return collection.firstObject as! PHAssetCollection
        }        
        return nil
    }

    func saveImage(image: UIImage, metadata: NSDictionary) {
        if assetCollection == nil {
            return                          // if there was an error upstream, skip the save
        }

        PHPhotoLibrary.sharedPhotoLibrary().performChanges({                                                                    
            let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image)                   
            let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset             
            let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection)       
            albumChangeRequest!.addAssets([assetPlaceHolder!])                                                      
        }, completionHandler: nil)
    }
}
10
Cody

Les réponses précédentes étaient excellentes et m'ont beaucoup aidé, mais il y avait toujours un problème pour enregistrer l'image au premier appel .. La solution suivante n'est pas parfaitement nette, mais résout le problème. Fonctionne avec Swift 3/4.

import Photos

class CustomPhotoAlbum: NSObject {
    static let albumName = "Album name"
    static let shared = CustomPhotoAlbum()

    private var assetCollection: PHAssetCollection!

    private override init() {
        super.init()

        if let assetCollection = fetchAssetCollectionForAlbum() {
            self.assetCollection = assetCollection
            return
        }
    }

    private func checkAuthorizationWithHandler(completion: @escaping ((_ success: Bool) -> Void)) {
        if PHPhotoLibrary.authorizationStatus() == .notDetermined {
            PHPhotoLibrary.requestAuthorization({ (status) in
                self.checkAuthorizationWithHandler(completion: completion)
            })
        }
        else if PHPhotoLibrary.authorizationStatus() == .authorized {
            self.createAlbumIfNeeded()
            completion(true)
        }
        else {
            completion(false)
        }
    }

    private func createAlbumIfNeeded() {
        if let assetCollection = fetchAssetCollectionForAlbum() {
            // Album already exists
            self.assetCollection = assetCollection
        } else {
            PHPhotoLibrary.shared().performChanges({
                PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: CustomPhotoAlbum.albumName)   // create an asset collection with the album name
            }) { success, error in
                if success {
                    self.assetCollection = self.fetchAssetCollectionForAlbum()
                } else {
                    // Unable to create album
                }
            }
        }
    }

    private func fetchAssetCollectionForAlbum() -> PHAssetCollection? {
        let fetchOptions = PHFetchOptions()
        fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName)
        let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)

        if let _: AnyObject = collection.firstObject {
            return collection.firstObject
        }
        return nil
    }

    func save(image: UIImage) {
        self.checkAuthorizationWithHandler { (success) in
            if success, self.assetCollection != nil {
                PHPhotoLibrary.shared().performChanges({
                    let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
                    let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
                    let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
                    let enumeration: NSArray = [assetPlaceHolder!]
                    albumChangeRequest!.addAssets(enumeration)

                }, completionHandler: nil)
            }
        }
    }
}
7
Damien

J'ai trouvé que certaines des solutions proposées ici fonctionnaient mais je voulais en réécrire une version réutilisable. Voici comment vous l'utilisez:

let image = // this is your image object

// Use the shared instance that has the default album name
CustomPhotoAlbum.shared.save(image)

// Use a custom album name
let album = CustomPhotoAlbum("some title")
album.save(image)

Lors de la sauvegarde d’une image, il demande l’accès photo de l’utilisateur (qui revient immédiatement s’il a été préalablement autorisé) et tente de créer un album s’il n’en existe pas encore un. -C.

//
//  CustomPhotoAlbum.Swift
//
//  Copyright © 2017 Et Voilapp. All rights reserved.
//

import Foundation
import Photos

@objc class CustomPhotoAlbum: NSObject {

  /// Default album title.
  static let defaultTitle = "Your title"

  /// Singleton
  static let shared = CustomPhotoAlbum(CustomPhotoAlbum.defaultTitle)

  /// The album title to use.
  private(set) var albumTitle: String

  /// This album's asset collection
  internal var assetCollection: PHAssetCollection?

  /// Initialize a new instance of this class.
  ///
  /// - Parameter title: Album title to use.
  init(_ title: String) {
    self.albumTitle = title
    super.init()
  }

  /// Save the image to this app's album.
  ///
  /// - Parameter image: Image to save.
  public func save(_ image: UIImage?) {
    guard let image = image else { return }

    // Request authorization and create the album
    requestAuthorizationIfNeeded { (_) in

      // If it all went well, we've got our asset collection
      guard let assetCollection = self.assetCollection else { return }

      PHPhotoLibrary.shared().performChanges({

        // Make sure that there's no issue while creating the request
        let request = PHAssetChangeRequest.creationRequestForAsset(from: image)
        guard let placeholder = request.placeholderForCreatedAsset,
          let albumChangeRequest = PHAssetCollectionChangeRequest(for: assetCollection) else {
            return
        }

        let enumeration: NSArray = [placeholder]
        albumChangeRequest.addAssets(enumeration)

      }, completionHandler: nil)
    }
  }
}

internal extension CustomPhotoAlbum {

  /// Request authorization and create the album if that went well.
  ///
  /// - Parameter completion: Called upon completion.
  func requestAuthorizationIfNeeded(_ completion: @escaping ((_ success: Bool) -> Void)) {

    PHPhotoLibrary.requestAuthorization { status in
      guard status == .authorized else {
        completion(false)
        return
      }

      // Try to find an existing collection first so that we don't create duplicates
      if let collection = self.fetchAssetCollectionForAlbum() {
        self.assetCollection = collection
        completion(true)

      } else {
        self.createAlbum(completion)
      }
    }
  }


  /// Creates an asset collection with the album name.
  ///
  /// - Parameter completion: Called upon completion.
  func createAlbum(_ completion: @escaping ((_ success: Bool) -> Void)) {

    PHPhotoLibrary.shared().performChanges({

      PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: self.albumTitle)

    }) { (success, error) in
      defer {
        completion(success)
      }

      guard error == nil else {
        print("error \(error!)")
        return
      }

      self.assetCollection = self.fetchAssetCollectionForAlbum()
    }
  }


  /// Fetch the asset collection matching this app's album.
  ///
  /// - Returns: An asset collection if found.
  func fetchAssetCollectionForAlbum() -> PHAssetCollection? {

    let fetchOptions = PHFetchOptions()
    fetchOptions.predicate = NSPredicate(format: "title = %@", albumTitle)

    let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
    return collection.firstObject
  }
}
4
jbouaziz

Pour ceux d'entre vous qui recherchent une solution à une seule fonction avec Swift 4, j'ai condensé une partie du code ci-dessus en une fonction qui prend simplement un UIImage, un nom d'album de type String et un rappel indiquant succès/échec. .

Note: cette fonction est plus complexe, donc son exécution est plus lente que les solutions précédentes, mais je l'ai postée ici pour la commodité des autres.

func save(image:UIImage, toAlbum:String, withCallback:((Bool)->Void)? = nil) {

    func fetchAssetCollection(forAlbum:String) -> PHAssetCollection! {

        let fetchOptions = PHFetchOptions()
        fetchOptions.predicate = NSPredicate(format: "title = %@", forAlbum)
        let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)

        if let _: AnyObject = collection.firstObject {
            return collection.firstObject
        }

        return nil
    }

    PHPhotoLibrary.shared().performChanges({
        PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: toAlbum)   // create an asset collection with the album name
    }) { success, error in
        if success {
            if success, let assetCollection = fetchAssetCollection(forAlbum: toAlbum) {

                PHPhotoLibrary.shared().performChanges({

                    let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
                    let assetPlaceholder = assetChangeRequest.placeholderForCreatedAsset
                    let albumChangeRequest = PHAssetCollectionChangeRequest(for: assetCollection)
                    let assetEnumeration:NSArray = [assetPlaceholder!]
                    albumChangeRequest!.addAssets(assetEnumeration)

                }, completionHandler: { (_ didComplete:Bool, _ error:Error?) -> Void in
                    if withCallback != nil {
                        withCallback!(didComplete && error == nil)
                    }
                })

            } else {
                if withCallback != nil {
                    // Failure to save
                    withCallback!(false)
                }
            }
        } else {
            if withCallback != nil {
                // Failure to save
                withCallback!(false)
            }
        }
    }

}
2
Anthony Krivonos

Si vous êtes intéressé par une approche basée sur un protocole qui permet une sauvegarde simple dans plusieurs albums portant des noms différents, mise à jour avec Swift 4 et évite l’utilisation d’un singleton, puis poursuivez votre lecture.

Cette approche recherche et obtient l'autorisation de l'utilisateur, recherche ou crée l'album photo, puis enregistre l'image dans l'album demandé. Si à un moment quelconque une erreur est déclenchée, le bloc d'achèvement est exécuté avec l'erreur correspondante.

L’un des inconvénients de cette approche est que l’utilisateur n’est pas invité à accéder aux photos dès la création de l’instance, mais qu’il est invité à le faire lorsqu'il tente réellement de sauvegarder son image, si une autorisation est requise.

Cette méthode vous permet également de définir une classe très simple qui encapsule un album photo, conformément au protocole PhotoAlbumHandler et permet ainsi d'obtenir gratuitement toute la logique d'interaction d'album photo, comme ceci:

class PhotoAlbum: PhotoAlbumHandler {

    var albumName: String

    init(named: String) {
        albumName = named
    }
}

Vous pouvez également créer une énumération qui gère et encapsule tous vos albums photo. Ajouter un support à un autre album est aussi simple que d’ajouter un nouveau cas à l’énumération et de définir le nom d’album correspondant. 

Comme ça:

public enum PhotoAlbums {
    case landscapes
    case portraits

    var albumName: String {
        switch self {
        case .landscapes: return "Landscapes"
        case .portraits: return "Portraits"
        }
    }

    func album() -> PhotoAlbumHandler {
        return PhotoAlbum.init(named: albumName)
    } 
}

Grâce à cette approche, la gestion de vos albums photo devient un jeu d'enfant. Dans votre viewModel (ou votre contrôleur de vue si vous n'utilisez pas de modèles d'affichage), vous pouvez créer des références à vos albums de la manière suivante:

let landscapeAlbum = PhotoAlbums.landscapes.album()
let portraitAlbum = PhotoAlbums.portraits.album()

Ensuite, pour enregistrer une image dans l'un des albums, procédez comme suit:

let photo: UIImage = UIImage.init(named: "somePhotoName")

landscapeAlbum.save(photo) { (error) in
    DispatchQueue.main.async {
        if let error = error {
            // show alert with error message or...???
            self.label.text = error.message
            return
        }

        self.label.text = "Saved image to album"
    }
}

Pour la gestion des erreurs, j'ai choisi d'encapsuler toutes les erreurs possibles dans une énumération d'erreurs:

public enum PhotoAlbumHandlerError {
    case unauthorized
    case authCancelled
    case albumNotExists
    case saveFailed
    case unknown

    var title: String {
        return "Photo Save Error"
    }

    var message: String {
        switch self {
        case .unauthorized:
            return "Not authorized to access photos. Enable photo access in the 'Settings' app to continue."
        case .authCancelled:
            return "The authorization process was cancelled. You will not be able to save to your photo albums without authorizing access."
        case .albumNotExists:
            return "Unable to create or find the specified album."
        case .saveFailed:
            return "Failed to save specified image."
        case .unknown:
            return "An unknown error occured."
        }
    }
}

Le protocole qui définit l'interface et l'extension de protocole qui gère l'interaction avec la fonctionnalité système Album photo se trouve ici:

import Photos

public protocol PhotoAlbumHandler: class {
    var albumName: String { get set }

    func save(_ photo: UIImage, completion: @escaping (PhotoAlbumHandlerError?) -> Void)
}

extension PhotoAlbumHandler {

    func save(_ photo: UIImage, completion: @escaping (PhotoAlbumHandlerError?) -> Void) {

        // Check for permission
        guard PHPhotoLibrary.authorizationStatus() == .authorized else {

            // not authorized, Prompt for access
            PHPhotoLibrary.requestAuthorization({ [weak self] status in

                // not authorized, end with error
                guard let strongself = self, status == .authorized else {
                    completion(.authCancelled)
                    return
                }

                // received authorization, try to save photo to album
                strongself.save(photo, completion: completion)
            })
            return
        }

        // check for album, create if not exists
        guard let album = fetchAlbum(named: albumName) else {

            // album does not exist, create album now
            createAlbum(named: albumName, completion: { [weak self] success, error in

                // album not created, end with error
                guard let strongself = self, success == true, error == nil else {
                    completion(.albumNotExists)
                    return
                }

                // album created, run through again
                strongself.save(photo, completion: completion)
            })
            return
        }

        // save the photo now... we have permission and the desired album
        insert(photo: photo, in: album, completion: { success, error in

            guard success == true, error == nil else {
                completion(.saveFailed)
                return
            }

            // finish with no error
            completion(nil)
        })
    }

    internal func fetchAlbum(named: String) -> PHAssetCollection? {
        let options = PHFetchOptions()
        options.predicate = NSPredicate(format: "title = %@", named)
        let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: options)

        guard let album = collection.firstObject else {
            return nil
        }

        return album
    }

    internal func createAlbum(named: String, completion: @escaping (Bool, Error?) -> Void) {
        PHPhotoLibrary.shared().performChanges({
            PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: named)
        }, completionHandler: completion)
    }

    internal func insert(photo: UIImage, in collection: PHAssetCollection, completion: @escaping (Bool, Error?) -> Void) {
        PHPhotoLibrary.shared().performChanges({
            let request = PHAssetChangeRequest.creationRequestForAsset(from: photo)
            request.creationDate = NSDate.init() as Date

            guard let assetPlaceHolder = request.placeholderForCreatedAsset,
                  let albumChangeRequest = PHAssetCollectionChangeRequest(for: collection) else {
                    return
            }
            let enumeration: NSArray = [assetPlaceHolder]
            albumChangeRequest.addAssets(enumeration)

        }, completionHandler: completion)
    }
}

Si vous souhaitez parcourir un exemple de projet Xcode, vous pouvez en trouver un ici: https://github.com/appteur/ios_photo_album_sample

1
digitalHound

Merci, essayait d'utiliser ce code, mais a trouvé des erreurs de logique. Voici le code nettoyé

import Photos

class CustomPhotoAlbum: NSObject {
    static let albumName = Bundle.main.infoDictionary![kCFBundleNameKey as String] as! String
    static let shared = CustomPhotoAlbum()

    private lazy var assetCollection = fetchAssetCollectionForAlbum()

    private override init() {
        super.init()
    }

    private func checkAuthorizationWithHandler(completion: @escaping ((_ success: Bool) -> Void)) {
        switch PHPhotoLibrary.authorizationStatus() {
        case .authorized:
            completion(true)
        case .notDetermined:
            PHPhotoLibrary.requestAuthorization(){ (status) in
                self.checkAuthorizationWithHandler(completion: completion)
            }
        case .denied, .restricted:
            completion(false)
        }
    }

    private func fetchAssetCollectionForAlbum() -> PHAssetCollection? {
        let fetchOptions = PHFetchOptions()
        fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName)
        let fetch = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
        return fetch.firstObject
    }

    func save(image: UIImage) {
        func saveIt(_ validAssets: PHAssetCollection){
            PHPhotoLibrary.shared().performChanges({
                let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
                let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
                let albumChangeRequest = PHAssetCollectionChangeRequest(for: validAssets)
                let enumeration: NSArray = [assetPlaceHolder!]
                albumChangeRequest!.addAssets(enumeration)

            }, completionHandler: nil)
        }
        self.checkAuthorizationWithHandler { (success) in
            if success {
                if let validAssets = self.assetCollection { // Album already exists
                    saveIt(validAssets)
                } else {                                    // create an asset collection with the album name
                    PHPhotoLibrary.shared().performChanges({
                        PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: CustomPhotoAlbum.albumName)
                    }) { success, error in
                        if success, let validAssets = self.fetchAssetCollectionForAlbum() {
                            self.assetCollection = validAssets
                            saveIt(validAssets)
                        } else {
                            // TODO: send user message "Sorry, unable to create album and save image..."
                        }
                    }
                }
            }
        }
    }
}
1

Amélioré sur @Damien answer . Fonctionne avec UIImage et la vidéo (avec url) aussi. Swift4 testé:

import Photos

class MyAwesomeAlbum: NSObject {
  static let albumName = "My Awesome Album"
  static let shared = MyAwesomeAlbum()

  private var assetCollection: PHAssetCollection!

  private override init() {
    super.init()

    if let assetCollection = fetchAssetCollectionForAlbum() {
      self.assetCollection = assetCollection
      return
    }
  }

  private func checkAuthorizationWithHandler(completion: @escaping ((_ success: Bool) -> Void)) {
    if PHPhotoLibrary.authorizationStatus() == .notDetermined {
      PHPhotoLibrary.requestAuthorization({ (status) in
        self.checkAuthorizationWithHandler(completion: completion)
      })
    }
    else if PHPhotoLibrary.authorizationStatus() == .authorized {
      self.createAlbumIfNeeded { (success) in
        if success {
          completion(true)
        } else {
          completion(false)
        }

      }

    }
    else {
      completion(false)
    }
  }

  private func createAlbumIfNeeded(completion: @escaping ((_ success: Bool) -> Void)) {
    if let assetCollection = fetchAssetCollectionForAlbum() {
      // Album already exists
      self.assetCollection = assetCollection
      completion(true)
    } else {
      PHPhotoLibrary.shared().performChanges({
        PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: MyAwesomeAlbum.albumName)   // create an asset collection with the album name
      }) { success, error in
        if success {
          self.assetCollection = self.fetchAssetCollectionForAlbum()
          completion(true)
        } else {
          // Unable to create album
          completion(false)
        }
      }
    }
  }

  private func fetchAssetCollectionForAlbum() -> PHAssetCollection? {
    let fetchOptions = PHFetchOptions()
    fetchOptions.predicate = NSPredicate(format: "title = %@", MyAwesomeAlbum.albumName)
    let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)

    if let _: AnyObject = collection.firstObject {
      return collection.firstObject
    }
    return nil
  }

  func save(image: UIImage) {
    self.checkAuthorizationWithHandler { (success) in
      if success, self.assetCollection != nil {
        PHPhotoLibrary.shared().performChanges({
          let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
          let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
          if let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection) {
            let enumeration: NSArray = [assetPlaceHolder!]
            albumChangeRequest.addAssets(enumeration)
          }

        }, completionHandler: { (success, error) in
          if success {
            print("Successfully saved image to Camera Roll.")
          } else {
            print("Error writing to image library: \(error!.localizedDescription)")
          }
        })

      }
    }
  }

  func saveMovieToLibrary(movieURL: URL) {

    self.checkAuthorizationWithHandler { (success) in
      if success, self.assetCollection != nil {

        PHPhotoLibrary.shared().performChanges({

          if let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: movieURL) {
            let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
            if let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection) {
              let enumeration: NSArray = [assetPlaceHolder!]
              albumChangeRequest.addAssets(enumeration)
            }

          }

        }, completionHandler:  { (success, error) in
          if success {
            print("Successfully saved video to Camera Roll.")
          } else {
            print("Error writing to movie library: \(error!.localizedDescription)")
          }
        })


      }
    }

  }
}

Usage:

MyAwesomeAlbum.shared.save(image: image)

ou

MyAwesomeAlbum.shared.saveMovieToLibrary(movieURL: url)
1
Rebeloper

Même après les correctifs, mon PhotoAlbum ne fonctionnait toujours pas pour la première image et si je voulais enregistrer plusieurs images à la fois, je me retrouvais avec plusieurs albums vides. J'ai donc mis à niveau la classe et je n'ai sauvegardé les emoji qu'une fois l'album créé.

Nouvelle version:

class CustomPhotoAlbum: NSObject {
static let albumName = "AlbumName"
static let shared = CustomPhotoAlbum()

private var assetCollection: PHAssetCollection!

private override init() {
    super.init()

    if let assetCollection = fetchAssetCollectionForAlbum() {
        self.assetCollection = assetCollection
        return
    }
}

private func checkAuthorizationWithHandler(completion: @escaping ((_ success: Bool) -> Void)) {
    if PHPhotoLibrary.authorizationStatus() == .notDetermined {
        PHPhotoLibrary.requestAuthorization({ (status) in
            self.checkAuthorizationWithHandler(completion: completion)
        })
    }
    else if PHPhotoLibrary.authorizationStatus() == .authorized {
        self.createAlbumIfNeeded()
        completion(true)
    }
    else {
        completion(false)
    }
}

private func createAlbumIfNeeded() {
   /* if let assetCollection = fetchAssetCollectionForAlbum() {
        // Album already exists
        self.assetCollection = assetCollection
    } else {
        PHPhotoLibrary.shared().performChanges({
            PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: CustomPhotoAlbum.albumName)   // create an asset collection with the album name
        }) { success, error in
            if success {
                self.assetCollection = self.fetchAssetCollectionForAlbum()
            } else {
                // Unable to create album
            }
        }
    }*/
}

private func fetchAssetCollectionForAlbum() -> PHAssetCollection? {
    let fetchOptions = PHFetchOptions()
    fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName)
    let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)

    if let _: AnyObject = collection.firstObject {
        return collection.firstObject
    }
    return nil
}

func save(image: UIImage) {
    self.checkAuthorizationWithHandler { (success) in
        if success {
            if let assetCollection = self.fetchAssetCollectionForAlbum() {
                // Album already exists
                self.assetCollection = assetCollection
                PHPhotoLibrary.shared().performChanges({
                    let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
                    let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
                    let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
                    let enumeration: NSArray = [assetPlaceHolder!]
                    albumChangeRequest!.addAssets(enumeration)

                }, completionHandler: nil)
            } else {
                PHPhotoLibrary.shared().performChanges({
                    PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: CustomPhotoAlbum.albumName)   // create an asset collection with the album name
                }) { success, error in
                    if success {
                        self.assetCollection = self.fetchAssetCollectionForAlbum()
                        PHPhotoLibrary.shared().performChanges({
                            let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
                            let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
                            let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
                            let enumeration: NSArray = [assetPlaceHolder!]
                            albumChangeRequest!.addAssets(enumeration)

                        }, completionHandler: nil)
                    } else {
                        // Unable to create album
                    }
                }
            }
        }
    }
}

}

Si vous souhaitez enregistrer plusieurs images à la fois, voici mon code pour cela. La clé ici est de retarder l'enregistrement des autres images qui ne sont pas les premières, car nous devons d'abord créer l'album. (sinon, on se retrouve avec des albums en double, car tous les processus de sauvegarde essaieront de créer un album personnalisé). Ceci est le code de mon application, afin que vous puissiez comprendre la logique:

var overFirstSave = false
                for stickerName in filenames {
                    let url = self.getDocumentsDirectory().appendingPathComponent(stickerName as! String)
                    do{
                        if !overFirstSave{
                            CustomPhotoAlbum.shared.save(image: UIImage(contentsOfFile: url.path)!)
                            overFirstSave = true
                        }else{
                            DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3), execute: {
                                CustomPhotoAlbum.shared.save(image: UIImage(contentsOfFile: url.path)!)
                            })
                        }
                    }catch {
                        print(error)
                    }
                }
0
user3341586