如何将图像保存到自定义相册?

Posted

技术标签:

【中文标题】如何将图像保存到自定义相册?【英文标题】:How to save image to custom album? 【发布时间】:2015-02-25 00:11:25 【问题描述】:

我有一个全新的 ios 应用程序,它可以生成图像并让用户将它们保存到相机 SavedPhotosAlbum 中。但是,我想做 Snapchat 和 Frontback 之类的东西,并将这些图像也保存到自定义命名的相册中。

这是我现在的代码:

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)

我已经看到一些人在 Objective-C 中执行此操作的示例,但我无法将其转换为 Swift,并且我检查了 writeImageToSavedPhotosAlbum 方法签名,它们似乎都不允许保存到自定义相册.

【问题讨论】:

ALAssetsLibrary 有一个非常烦人的 API。最简单的方法可能是使用ALAssetsLibrary-CustomPhotoAlbum,或者至少看看它在做什么。 你发现了吗? 由于 iOS 8.1 更容易,UIKit 有 UIImageWriteToSavedPhotosAlbum() 错误,UIImageWriteToSavedPhotosAlbum 是 iOS 2 之后的版本,developer.apple.com/documentation/uikit/… 【参考方案1】:

我想出了这个单例类来处理它:

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)
    



当您第一次实例化该类时,如果自定义相册尚​​不存在,则会创建它。您可以像这样保存图像:

CustomPhotoAlbum.sharedInstance.saveImage(image)

注意:CustomPhotoAlbum 类假定应用程序已经有权访问照片库。处理权限有点超出这个问题/答案的范围。因此,在使用之前请确保 PHPhotoLibrary.authorizationStatus() == .Authorize。并在必要时请求授权。

【讨论】:

这在 swift 1.2 中完美运行,但在 swift 2.0 中无法编译;首先它说albumChangeRequest 没有解包并建议添加!,如果我添加它,那么它会失败并出现错误argument type '[PHObjectPlaceholder?]' does not conform to expected type 'NSFastEnumeration'。因此,我尝试对as! NSFastEnumeration 进行类型转换,但这会使应用程序崩溃,并显示一条消息说它无法将PHObjectPlaceholder 转换为NSFastEnumeration。我真的不知道我在做什么(显然),并且还没有在网上找到任何有用的东西。有什么想法吗? 我想我终于弄明白了——我把那行改成了albumChangeRequest!.addAssets([assetPlaceHolder!])——我想第二个感叹号让它继续前进并尝试将assetPlaceHolder视为NSFastEnumeration对象?跨度> 很高兴你知道了。我还没有使用 Swift 2.0。当我到达那里时,我将不得不再玩一些。 其实这段代码还有一个bug:应用程序第一次运行时,它会请求对照片库的授权,但是实际创建相册的闭包在获得许可之前就被调用了,所以它在重新启动应用程序之前不会创建相册(或保存任何图片)(如果您在应用程序设置中切换权限也会发生这种情况)。我试图添加一个方便的初始化程序来稍后运行专辑创建部分,但它不起作用(因为我真的不知道我在做什么)。 @AvinashMishra 看看 Swift 3 版本的这个类:github.com/SagarSDagdu/SDPhotosHelper【参考方案2】:

最新的 Swift 3.0 语法。 :)

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)
    

【讨论】:

如何获取此已保存图像的 URL。在这种方法中:func save(image: UIImage) @mady,在你创建了上面显示的类之后 - 在你的 VC 中创建一个变量,比如'photoAlbum',你可以使用'self.photoAlbum.save(image:"YourImage" );' 它将图像存储在相机胶卷中【参考方案3】:

这是一个更新版本,在 Swift 2.1 中工作,避免了第一次启动时没有创建相册和没有保存图像的错误(当首次请求/授予写入照片库的授权时)。

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)
    

【讨论】:

如果你用这个更新你的 createAlbum 函数,你可以避免重复的相册。 func createAlbum() if let assetCollection = fetchAssetCollectionForAlbum() print("already have") self.assetCollection = assetCollection else PHPhotoLibrary.sharedPhotoLibrary().performChanges( PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(CustomPhotoAlbum.albumName) ) success, error in if success self.assetCollection = self.fetchAssetCollectionForAlbum() 它并不能避免第一次调用时图像没有保存的错误。这取决于你如何使用它。如果你第一次使用这个单例是调用sharedInstance.saveImage,那么init会第一次被调用,然后立即saveImage,但是assetCollection仍然是nil,因为init的回调会在后面被调用。为了解决这个问题,我建议更早地访问 sharedInstance,例如在 viewDidLoad 中,让 _ = CustomPhotoAlbum.sharedInstance。【参考方案4】:

以前的答案很好,对我帮助很大,但在第一次通话时保存图像仍然存在问题。 以下解决方案并不完全干净,但可以解决问题。适用于 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)
            
        
    

【讨论】:

不错。备注:您不是在等待相册创建。因此,第一个要保存的图像(正在创建相册时)不会被保存。请参阅我编辑的答案。 如何在按钮单击时保存图像我如何在其他类上调用它??【参考方案5】:

我发现这里提出的一些解决方案是可行的,但我想重写它的可重用版本。以下是您的使用方法:

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)

保存图像时,它会请求用户的照片访问权限(如果之前授权,则会立即返回)并尝试创建相册(如果相册尚不存在)。 以下是用 Swift 3 编写并兼容 Objective-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
  

【讨论】:

【参考方案6】:

在@Damien answer 基础上改进。也适用于UIImage 和视频(带 url)。 Swift4 已测试:

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)")
          
        )


      
    

  

用法:

MyAwesomeAlbum.shared.save(image: image)

MyAwesomeAlbum.shared.saveMovieToLibrary(movieURL: url)

【讨论】:

在 swift 5 中,我在 URL(string: sampleURL)! 中收到错误消息。错误是写入电影库时出错:操作无法完成。 (PHPhotosErrorDomain 错误 -1。)【参考方案7】:

用 Swift 5 编写的 100% 工作和完善的解决方案。完成块和错误处理正确。我切换到普通类,因为我只在我的应用程序的特定点需要它,但如果你经常使用它,你可以转换为单例。

class PhotoManager 

    private var albumName: String
    private var album: PHAssetCollection?

    init(albumName: String) 
        self.albumName = albumName

        if let album = getAlbum() 
            self.album = album
            return
        
    

    private func getAlbum() -> PHAssetCollection? 
        let options = PHFetchOptions()
        options.predicate = NSPredicate(format: "title = %@", albumName)
        let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: options)
        return collection.firstObject ?? nil
    

    private func createAlbum(completion: @escaping (Bool) -> ()) 
        PHPhotoLibrary.shared().performChanges(
            PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: self.albumName)
        , completionHandler:  (result, error) in
            if let error = error 
                print("error: \(error.localizedDescription)")
             else 
                self.album = self.getAlbum()
                completion(result)
            
        )
    

    private func add(image: UIImage, completion: @escaping (Bool, Error?) -> ()) 
        PHPhotoLibrary.shared().performChanges(
            let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
            if let album = self.album, let placeholder = assetChangeRequest.placeholderForCreatedAsset 
                let albumChangeRequest = PHAssetCollectionChangeRequest(for: album)
                let enumeration = NSArray(object: placeholder)
                albumChangeRequest?.addAssets(enumeration)
            
        , completionHandler:  (result, error) in
            completion(result, error)
        )
    

    func save(_ image: UIImage, completion: @escaping (Bool, Error?) -> ()) 
        PHPhotoLibrary.requestAuthorization  status in
            guard status == .authorized else 
                // fail and redirect to app settings
                return
            

            if let _ = self.album 
                self.add(image: image)  (result, error) in
                    completion(result, error)
                
                return
            

            self.createAlbum(completion:  _ in
                self.add(image: image)  (result, error) in
                    completion(result, error)
                
            )
        
    

【讨论】:

能否添加一种下载视频/mp4的方法。【参考方案8】:

对于那些寻找使用 Swift 4 的单功能解决方案的人,我将上面的一些代码压缩成一个函数,该函数只接受 UIImage、String 类型的专辑名称和指示成功/失败的回调。

注意:这个函数更复杂,所以它的运行时间显然比以前的解决方案要慢,但我把它贴在这里是为了方便其他人。

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)
            
        
    


【讨论】:

此解决方案在首次运行时不处理授权问题。【参考方案9】:

Swift 5 更新,添加了完成处理程序。

用法:

CustomPhotoAlbum.sharedInstance.save(image, completion:  result, error in
    if let e = error 
        // handle error
        return
    
    // save successful, do something (such as inform user)
)

单例类:

import Foundation
import Photos
import UIKit

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 \(String(describing: 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, completion: @escaping ((Bool, Error?) -> ())) 
        if assetCollection == nil 
            // if there was an error upstream, skip the save
            return
        

        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:  result, error in
            completion(result, error)
        )
    

【讨论】:

谢谢!在您的保存函数中,我们可以将图像作为数组传递吗?【参考方案10】:

如果您对一种面向协议的方法感兴趣,该方法允许简单地保存到多个具有不同名称的专辑,这些专辑在 Swift 4 中是最新的并避免使用单例,然后继续阅读。

此方法检查并获得用户授权,检查或创建相册,然后将图像保存到请求的相册中。如果在任何时候触发了错误,则会运行完成块并显示相应的错误。

这种方法的一个优点是,在创建实例后不会提示用户访问照片,而是在他们实际尝试保存图像时提示他们(如果需要授权)。

这个方法还可以让你定义一个非常简单的类来封装相册,符合PhotoAlbumHandler协议,从而免费获取所有相册交互逻辑,像这样:

class PhotoAlbum: PhotoAlbumHandler 

    var albumName: String

    init(named: String) 
        albumName = named
    

然后,您还可以创建一个枚举来管理和封装您的所有相册。添加对另一个专辑的支持就像向枚举添加一个新案例并定义相应的专辑名称一样简单。

像这样:

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)
     

使用这种方法可以轻松管理您的相册,在您的 viewModel(或视图控制器,如果您不使用视图模型)中,您可以像这样创建对相册的引用:

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

然后要将图像保存到其中一个相册,您可以执行以下操作:

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"
    

对于错误处理,我选择将任何可能的错误封装在错误枚举中:

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."
        
    

定义接口的协议和处理与系统相册功能交互的协议扩展在这里:

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)
    

如果您想查看一个示例 Xcode 项目,可以在这里找到:https://github.com/appteur/ios_photo_album_sample

【讨论】:

我对您在代码guard let strongself = self 中的自我检查感兴趣...您能解释一下您这样做的原因吗?我看到您将 weak self 分配给您的闭包属性。我不太了解闭包或内存周期可能出现的问题,但假设它是为了使潜在的内存周期短路?【参考方案11】:

谢谢,正在尝试使用此代码,但发现一些逻辑错误。这是清理后的代码

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..."
                        
                    
                
            
        
    

【讨论】:

谢谢!这是我最终使用的解决方案。我第一次尝试保存图片时,以前的解决方案不起作用。这个从一开始就很好用;)【参考方案12】:

即使在修复之后,我的 PhotoAlbum 仍然无法处理第一张图片,如果我想一次保存多张图片,我最终会得到多个空相册。所以我升级了课程,我只在相册创建后保存表情符号。

新版本:

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
                    
                
            
        
    

如果您想一次保存多个图像,这是我的代码。这里的关键是延迟保存不是第一个图像的其他图像,因为我们必须先创建相册。 (否则我们最终会得到重复的相册,因为所有保存过程都会尝试创建自定义相册)。这是我的应用程序的代码,所以你可以理解逻辑:

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)
                    
                

【讨论】:

谢谢,这个代码也适用于我的第一次调用。,其他人没有。【参考方案13】:

Swift 4 和 5

点击按钮这样调用

@IBAction func btnSave(_ sender: Any) 
    createAlbum()
    self.saveGIFInAlbum(url: gifUrl)


func createAlbum() 

    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.createAlbumIfNeeded()
     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.createAlbumIfNeeded()
     else 
        print("should really prompt the user to let them know it's failed")
    


func createAlbumIfNeeded() 
    PHPhotoLibrary.shared().performChanges(
        PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: "PicBloom")   // create an asset collection with the album name
    )  success, error in
        if success 
            self.assetCollection = self.fetchAssetCollectionForAlbum()
         else 
            print("error \(String(describing: error))")
        
    


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

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


func saveGIFInAlbum(url: URL)

    PHPhotoLibrary.shared().performChanges(
        let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: url)
        let assetPlaceHolder = assetChangeRequest?.placeholderForCreatedAsset

        if(self.assetCollection != nil) 
            let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
            let enumeration: NSArray = [assetPlaceHolder!]
            albumChangeRequest!.addAssets(enumeration)
        
    , completionHandler:  success, error in
        DispatchQueue.main.async 
            if success 
                let message = "Gif has been saved to your gallery!"
                let alert = UIAlertController(title: "", message: message, preferredStyle: .alert)
                self.present(alert, animated: true)
                let duration: Double = 1.5
                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration) 
                    alert.dismiss(animated: true)
                
            else if error != nil 
                print("handle error since couldn't save GIF")
            
        
    )

【讨论】:

以上是关于如何将图像保存到自定义相册?的主要内容,如果未能解决你的问题,请参考以下文章

Phonegap(cordova)将图像保存到自定义相册

Swift 3 或 4 保存到自定义相册会创建重复的图像

将视频保存到自定义相册 iOS

将屏幕截图保存到自定义相册

swift 将图片保存到自定义相册中

如何快速将电影保存到自定义命名专辑中