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

时间:2015-02-25 00:11:26

标签: ios swift ios8

我有一个全新的iOS应用程序,可以生成图像并让用户将它们保存到Camera 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方法签名,似乎没有一个允许保存到自定义相册。

13 个答案:

答案 0 :(得分:31)

最新的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)
    }
}

答案 1 :(得分:30)

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

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。并在必要时请求授权。

答案 2 :(得分:10)

这是一个更新版本,适用于Swift 2.1,并且避免了在第一次启动时未创建相册并且未保存图像的错误(首次请求/授予写入照片库的授权时)。 / p>

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

答案 3 :(得分:7)

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

答案 4 :(得分:6)

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

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
  }
}

答案 5 :(得分:4)

使用Swift 5编写的100%工作和完善的解决方案。完成块和错误得到正确处理。我之所以改用普通班,是因为我只在应用的特定位置需要它,但是如果您大部分使用它,则可以转换为Singleton。

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

答案 6 :(得分:3)

对于那些使用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)
            }
        }
    }

}

答案 7 :(得分:2)

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

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

这种方法的好处是,一旦创建实例,就不会提示用户进行照片访问,而是在他们实际尝试保存图像时会提示他们,如果需要授权的话。

此方法还允许您定义一个非常简单的类,该类封装相册,符合PhotoAlbumHandler协议,从而免费获取所有相册交互逻辑,如下所示:

class PhotoAlbum: PhotoAlbumHandler {

    var albumName: String

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

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

像这样:

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

答案 8 :(得分:2)

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

答案 9 :(得分:1)

改进@Damien answer。适用于UIImage和视频(带网址)。 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)

答案 10 :(得分:1)

谢谢,试图使用此代码,但发现了一些逻辑错误。这是清理过的代码

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

答案 11 :(得分:0)

即使在修复后,我的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)
                    }
                }

答案 12 :(得分:0)

快速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")
            }
        }
    })
}