【问题标题】: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 方法签名,它们似乎都不允许保存到自定义相册.

【问题讨论】:

标签: ios swift ios8


【解决方案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")
                }
            }
        })
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-30
      • 2010-11-04
      • 1970-01-01
      相关资源
      最近更新 更多