如何将图像保存到自定义相册?
Posted
技术标签:
【中文标题】如何将图像保存到自定义相册?【英文标题】:How to save image to custom album? 【发布时间】:2015-02-25 00:11:25 【问题描述】:我有一个全新的 ios 应用程序,它可以生成图像并让用户将它们保存到相机 SavedPhotosAlbum 中。但是,我想做 Snapchat 和 Frontback 之类的东西,并将这些图像也保存到自定义命名的相册中。
这是我现在的代码:
let imageToSave = self.currentPreviewImage
let softwareContext = CIContext(options:[kCIContextUseSoftwareRenderer: true])
let cgimg = softwareContext.createCGImage(imageToSave, fromRect:imageToSave.extent())
ALAssetsLibrary().writeImageToSavedPhotosAlbum(cgimg, metadata:imageToSave.properties(), completionBlock:nil)
我已经看到一些人在 Objective-C 中执行此操作的示例,但我无法将其转换为 Swift,并且我检查了 writeImageToSavedPhotosAlbum
方法签名,它们似乎都不允许保存到自定义相册.
【问题讨论】:
ALAssetsLibrary 有一个非常烦人的 API。最简单的方法可能是使用ALAssetsLibrary-CustomPhotoAlbum,或者至少看看它在做什么。 你发现了吗? 由于 iOS 8.1 更容易,UIKit 有 UIImageWriteToSavedPhotosAlbum() 错误,UIImageWriteToSavedPhotosAlbum 是 iOS 2 之后的版本,developer.apple.com/documentation/uikit/… 【参考方案1】:我想出了这个单例类来处理它:
import Photos
class CustomPhotoAlbum
static let albumName = "Flashpod"
static let sharedInstance = CustomPhotoAlbum()
var assetCollection: PHAssetCollection!
init()
func fetchAssetCollectionForAlbum() -> PHAssetCollection!
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName)
let collection = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions)
if let firstObject: AnyObject = collection.firstObject
return collection.firstObject as! PHAssetCollection
return nil
if let assetCollection = fetchAssetCollectionForAlbum()
self.assetCollection = assetCollection
return
phphotoLibrary.sharedPhotoLibrary().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(CustomPhotoAlbum.albumName)
) success, _ in
if success
self.assetCollection = fetchAssetCollectionForAlbum()
func saveImage(image: UIImage)
if assetCollection == nil
return // If there was an error upstream, skip the save.
PHPhotoLibrary.sharedPhotoLibrary().performChanges(
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
let assetPlaceholder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection)
albumChangeRequest.addAssets([assetPlaceholder])
, completionHandler: nil)
当您第一次实例化该类时,如果自定义相册尚不存在,则会创建它。您可以像这样保存图像:
CustomPhotoAlbum.sharedInstance.saveImage(image)
注意:CustomPhotoAlbum 类假定应用程序已经有权访问照片库。处理权限有点超出这个问题/答案的范围。因此,在使用之前请确保 PHPhotoLibrary.authorizationStatus() == .Authorize。并在必要时请求授权。
【讨论】:
这在 swift 1.2 中完美运行,但在 swift 2.0 中无法编译;首先它说albumChangeRequest
没有解包并建议添加!
,如果我添加它,那么它会失败并出现错误argument type '[PHObjectPlaceholder?]' does not conform to expected type 'NSFastEnumeration'
。因此,我尝试对as! NSFastEnumeration
进行类型转换,但这会使应用程序崩溃,并显示一条消息说它无法将PHObjectPlaceholder
转换为NSFastEnumeration
。我真的不知道我在做什么(显然),并且还没有在网上找到任何有用的东西。有什么想法吗?
我想我终于弄明白了——我把那行改成了albumChangeRequest!.addAssets([assetPlaceHolder!])
——我想第二个感叹号让它继续前进并尝试将assetPlaceHolder
视为NSFastEnumeration
对象?跨度>
很高兴你知道了。我还没有使用 Swift 2.0。当我到达那里时,我将不得不再玩一些。
其实这段代码还有一个bug:应用程序第一次运行时,它会请求对照片库的授权,但是实际创建相册的闭包在获得许可之前就被调用了,所以它在重新启动应用程序之前不会创建相册(或保存任何图片)(如果您在应用程序设置中切换权限也会发生这种情况)。我试图添加一个方便的初始化程序来稍后运行专辑创建部分,但它不起作用(因为我真的不知道我在做什么)。
@AvinashMishra 看看 Swift 3 版本的这个类:github.com/SagarSDagdu/SDPhotosHelper【参考方案2】:
最新的 Swift 3.0 语法。 :)
import Foundation
import Photos
class CustomPhotoAlbum: NSObject
static let albumName = "Album Name"
static let sharedInstance = CustomPhotoAlbum()
var assetCollection: PHAssetCollection!
override init()
super.init()
if let assetCollection = fetchAssetCollectionForAlbum()
self.assetCollection = assetCollection
return
if PHPhotoLibrary.authorizationStatus() != PHAuthorizationStatus.authorized
PHPhotoLibrary.requestAuthorization( (status: PHAuthorizationStatus) -> Void in
()
)
if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized
self.createAlbum()
else
PHPhotoLibrary.requestAuthorization(requestAuthorizationHandler)
func requestAuthorizationHandler(status: PHAuthorizationStatus)
if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized
// ideally this ensures the creation of the photo album even if authorization wasn't prompted till after init was done
print("trying again to create the album")
self.createAlbum()
else
print("should really prompt the user to let them know it's failed")
func createAlbum()
PHPhotoLibrary.shared().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: CustomPhotoAlbum.albumName) // create an asset collection with the album name
) success, error in
if success
self.assetCollection = self.fetchAssetCollectionForAlbum()
else
print("error \(error)")
func fetchAssetCollectionForAlbum() -> PHAssetCollection?
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if let _: AnyObject = collection.firstObject
return collection.firstObject
return nil
func save(image: UIImage)
if assetCollection == nil
return // if there was an error upstream, skip the save
PHPhotoLibrary.shared().performChanges(
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest!.addAssets(enumeration)
, completionHandler: nil)
【讨论】:
如何获取此已保存图像的 URL。在这种方法中:func save(image: UIImage)
@mady,在你创建了上面显示的类之后 - 在你的 VC 中创建一个变量,比如'photoAlbum',你可以使用'self.photoAlbum.save(image:"YourImage" );'
它将图像存储在相机胶卷中【参考方案3】:
这是一个更新版本,在 Swift 2.1 中工作,避免了第一次启动时没有创建相册和没有保存图像的错误(当首次请求/授予写入照片库的授权时)。
class CustomPhotoAlbum: NSObject
static let albumName = "Name of Custom Album"
static let sharedInstance = CustomPhotoAlbum()
var assetCollection: PHAssetCollection!
override init()
super.init()
if let assetCollection = fetchAssetCollectionForAlbum()
self.assetCollection = assetCollection
return
if PHPhotoLibrary.authorizationStatus() != PHAuthorizationStatus.Authorized
PHPhotoLibrary.requestAuthorization( (status: PHAuthorizationStatus) -> Void in
status
)
if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.Authorized
self.createAlbum()
else
PHPhotoLibrary.requestAuthorization(requestAuthorizationHandler)
func requestAuthorizationHandler(status: PHAuthorizationStatus)
if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.Authorized
// ideally this ensures the creation of the photo album even if authorization wasn't prompted till after init was done
print("trying again to create the album")
self.createAlbum()
else
print("should really prompt the user to let them know it's failed")
func createAlbum()
PHPhotoLibrary.sharedPhotoLibrary().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(CustomPhotoAlbum.albumName) // create an asset collection with the album name
) success, error in
if success
self.assetCollection = self.fetchAssetCollectionForAlbum()
else
print("error \(error)")
func fetchAssetCollectionForAlbum() -> PHAssetCollection!
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName)
let collection = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions)
if let _: AnyObject = collection.firstObject
return collection.firstObject as! PHAssetCollection
return nil
func saveImage(image: UIImage, metadata: NSDictionary)
if assetCollection == nil
return // if there was an error upstream, skip the save
PHPhotoLibrary.sharedPhotoLibrary().performChanges(
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection)
albumChangeRequest!.addAssets([assetPlaceHolder!])
, completionHandler: nil)
【讨论】:
如果你用这个更新你的 createAlbum 函数,你可以避免重复的相册。func createAlbum() if let assetCollection = fetchAssetCollectionForAlbum() print("already have") self.assetCollection = assetCollection else PHPhotoLibrary.sharedPhotoLibrary().performChanges( PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(CustomPhotoAlbum.albumName) ) success, error in if success self.assetCollection = self.fetchAssetCollectionForAlbum()
它并不能避免第一次调用时图像没有保存的错误。这取决于你如何使用它。如果你第一次使用这个单例是调用sharedInstance.saveImage,那么init会第一次被调用,然后立即saveImage,但是assetCollection仍然是nil,因为init的回调会在后面被调用。为了解决这个问题,我建议更早地访问 sharedInstance,例如在 viewDidLoad 中,让 _ = CustomPhotoAlbum.sharedInstance。【参考方案4】:
以前的答案很好,对我帮助很大,但在第一次通话时保存图像仍然存在问题。 以下解决方案并不完全干净,但可以解决问题。适用于 Swift 3/4。
import Photos
class CustomPhotoAlbum: NSObject
static let albumName = "Album name"
static let shared = CustomPhotoAlbum()
private var assetCollection: PHAssetCollection!
private override init()
super.init()
if let assetCollection = fetchAssetCollectionForAlbum()
self.assetCollection = assetCollection
return
private func checkAuthorizationWithHandler(completion: @escaping ((_ success: Bool) -> Void))
if PHPhotoLibrary.authorizationStatus() == .notDetermined
PHPhotoLibrary.requestAuthorization( (status) in
self.checkAuthorizationWithHandler(completion: completion)
)
else if PHPhotoLibrary.authorizationStatus() == .authorized
self.createAlbumIfNeeded()
completion(true)
else
completion(false)
private func createAlbumIfNeeded()
if let assetCollection = fetchAssetCollectionForAlbum()
// Album already exists
self.assetCollection = assetCollection
else
PHPhotoLibrary.shared().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: CustomPhotoAlbum.albumName) // create an asset collection with the album name
) success, error in
if success
self.assetCollection = self.fetchAssetCollectionForAlbum()
else
// Unable to create album
private func fetchAssetCollectionForAlbum() -> PHAssetCollection?
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if let _: AnyObject = collection.firstObject
return collection.firstObject
return nil
func save(image: UIImage)
self.checkAuthorizationWithHandler (success) in
if success, self.assetCollection != nil
PHPhotoLibrary.shared().performChanges(
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest!.addAssets(enumeration)
, completionHandler: nil)
【讨论】:
不错。备注:您不是在等待相册创建。因此,第一个要保存的图像(正在创建相册时)不会被保存。请参阅我编辑的答案。 如何在按钮单击时保存图像我如何在其他类上调用它??【参考方案5】:我发现这里提出的一些解决方案是可行的,但我想重写它的可重用版本。以下是您的使用方法:
let image = // this is your image object
// Use the shared instance that has the default album name
CustomPhotoAlbum.shared.save(image)
// Use a custom album name
let album = CustomPhotoAlbum("some title")
album.save(image)
保存图像时,它会请求用户的照片访问权限(如果之前授权,则会立即返回)并尝试创建相册(如果相册尚不存在)。 以下是用 Swift 3 编写并兼容 Objective-C 的完整源代码。
//
// CustomPhotoAlbum.swift
//
// Copyright © 2017 Et Voilapp. All rights reserved.
//
import Foundation
import Photos
@objc class CustomPhotoAlbum: NSObject
/// Default album title.
static let defaultTitle = "Your title"
/// Singleton
static let shared = CustomPhotoAlbum(CustomPhotoAlbum.defaultTitle)
/// The album title to use.
private(set) var albumTitle: String
/// This album's asset collection
internal var assetCollection: PHAssetCollection?
/// Initialize a new instance of this class.
///
/// - Parameter title: Album title to use.
init(_ title: String)
self.albumTitle = title
super.init()
/// Save the image to this app's album.
///
/// - Parameter image: Image to save.
public func save(_ image: UIImage?)
guard let image = image else return
// Request authorization and create the album
requestAuthorizationIfNeeded (_) in
// If it all went well, we've got our asset collection
guard let assetCollection = self.assetCollection else return
PHPhotoLibrary.shared().performChanges(
// Make sure that there's no issue while creating the request
let request = PHAssetChangeRequest.creationRequestForAsset(from: image)
guard let placeholder = request.placeholderForCreatedAsset,
let albumChangeRequest = PHAssetCollectionChangeRequest(for: assetCollection) else
return
let enumeration: NSArray = [placeholder]
albumChangeRequest.addAssets(enumeration)
, completionHandler: nil)
internal extension CustomPhotoAlbum
/// Request authorization and create the album if that went well.
///
/// - Parameter completion: Called upon completion.
func requestAuthorizationIfNeeded(_ completion: @escaping ((_ success: Bool) -> Void))
PHPhotoLibrary.requestAuthorization status in
guard status == .authorized else
completion(false)
return
// Try to find an existing collection first so that we don't create duplicates
if let collection = self.fetchAssetCollectionForAlbum()
self.assetCollection = collection
completion(true)
else
self.createAlbum(completion)
/// Creates an asset collection with the album name.
///
/// - Parameter completion: Called upon completion.
func createAlbum(_ completion: @escaping ((_ success: Bool) -> Void))
PHPhotoLibrary.shared().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: self.albumTitle)
) (success, error) in
defer
completion(success)
guard error == nil else
print("error \(error!)")
return
self.assetCollection = self.fetchAssetCollectionForAlbum()
/// Fetch the asset collection matching this app's album.
///
/// - Returns: An asset collection if found.
func fetchAssetCollectionForAlbum() -> PHAssetCollection?
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", albumTitle)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
return collection.firstObject
【讨论】:
【参考方案6】:在@Damien answer 基础上改进。也适用于UIImage
和视频(带 url)。 Swift4
已测试:
import Photos
class MyAwesomeAlbum: NSObject
static let albumName = "My Awesome Album"
static let shared = MyAwesomeAlbum()
private var assetCollection: PHAssetCollection!
private override init()
super.init()
if let assetCollection = fetchAssetCollectionForAlbum()
self.assetCollection = assetCollection
return
private func checkAuthorizationWithHandler(completion: @escaping ((_ success: Bool) -> Void))
if PHPhotoLibrary.authorizationStatus() == .notDetermined
PHPhotoLibrary.requestAuthorization( (status) in
self.checkAuthorizationWithHandler(completion: completion)
)
else if PHPhotoLibrary.authorizationStatus() == .authorized
self.createAlbumIfNeeded (success) in
if success
completion(true)
else
completion(false)
else
completion(false)
private func createAlbumIfNeeded(completion: @escaping ((_ success: Bool) -> Void))
if let assetCollection = fetchAssetCollectionForAlbum()
// Album already exists
self.assetCollection = assetCollection
completion(true)
else
PHPhotoLibrary.shared().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: MyAwesomeAlbum.albumName) // create an asset collection with the album name
) success, error in
if success
self.assetCollection = self.fetchAssetCollectionForAlbum()
completion(true)
else
// Unable to create album
completion(false)
private func fetchAssetCollectionForAlbum() -> PHAssetCollection?
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", MyAwesomeAlbum.albumName)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if let _: AnyObject = collection.firstObject
return collection.firstObject
return nil
func save(image: UIImage)
self.checkAuthorizationWithHandler (success) in
if success, self.assetCollection != nil
PHPhotoLibrary.shared().performChanges(
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
if let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest.addAssets(enumeration)
, completionHandler: (success, error) in
if success
print("Successfully saved image to Camera Roll.")
else
print("Error writing to image library: \(error!.localizedDescription)")
)
func saveMovieToLibrary(movieURL: URL)
self.checkAuthorizationWithHandler (success) in
if success, self.assetCollection != nil
PHPhotoLibrary.shared().performChanges(
if let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: movieURL)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
if let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest.addAssets(enumeration)
, completionHandler: (success, error) in
if success
print("Successfully saved video to Camera Roll.")
else
print("Error writing to movie library: \(error!.localizedDescription)")
)
用法:
MyAwesomeAlbum.shared.save(image: image)
或
MyAwesomeAlbum.shared.saveMovieToLibrary(movieURL: url)
【讨论】:
在 swift 5 中,我在 URL(string: sampleURL)! 中收到错误消息。错误是写入电影库时出错:操作无法完成。 (PHPhotosErrorDomain 错误 -1。)【参考方案7】:用 Swift 5 编写的 100% 工作和完善的解决方案。完成块和错误处理正确。我切换到普通类,因为我只在我的应用程序的特定点需要它,但如果你经常使用它,你可以转换为单例。
class PhotoManager
private var albumName: String
private var album: PHAssetCollection?
init(albumName: String)
self.albumName = albumName
if let album = getAlbum()
self.album = album
return
private func getAlbum() -> PHAssetCollection?
let options = PHFetchOptions()
options.predicate = NSPredicate(format: "title = %@", albumName)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: options)
return collection.firstObject ?? nil
private func createAlbum(completion: @escaping (Bool) -> ())
PHPhotoLibrary.shared().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: self.albumName)
, completionHandler: (result, error) in
if let error = error
print("error: \(error.localizedDescription)")
else
self.album = self.getAlbum()
completion(result)
)
private func add(image: UIImage, completion: @escaping (Bool, Error?) -> ())
PHPhotoLibrary.shared().performChanges(
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
if let album = self.album, let placeholder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: album)
let enumeration = NSArray(object: placeholder)
albumChangeRequest?.addAssets(enumeration)
, completionHandler: (result, error) in
completion(result, error)
)
func save(_ image: UIImage, completion: @escaping (Bool, Error?) -> ())
PHPhotoLibrary.requestAuthorization status in
guard status == .authorized else
// fail and redirect to app settings
return
if let _ = self.album
self.add(image: image) (result, error) in
completion(result, error)
return
self.createAlbum(completion: _ in
self.add(image: image) (result, error) in
completion(result, error)
)
【讨论】:
能否添加一种下载视频/mp4的方法。【参考方案8】:对于那些寻找使用 Swift 4 的单功能解决方案的人,我将上面的一些代码压缩成一个函数,该函数只接受 UIImage、String 类型的专辑名称和指示成功/失败的回调。
注意:这个函数更复杂,所以它的运行时间显然比以前的解决方案要慢,但我把它贴在这里是为了方便其他人。
func save(image:UIImage, toAlbum:String, withCallback:((Bool)->Void)? = nil)
func fetchAssetCollection(forAlbum:String) -> PHAssetCollection!
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", forAlbum)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if let _: AnyObject = collection.firstObject
return collection.firstObject
return nil
PHPhotoLibrary.shared().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: toAlbum) // create an asset collection with the album name
) success, error in
if success
if success, let assetCollection = fetchAssetCollection(forAlbum: toAlbum)
PHPhotoLibrary.shared().performChanges(
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceholder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: assetCollection)
let assetEnumeration:NSArray = [assetPlaceholder!]
albumChangeRequest!.addAssets(assetEnumeration)
, completionHandler: (_ didComplete:Bool, _ error:Error?) -> Void in
if withCallback != nil
withCallback!(didComplete && error == nil)
)
else
if withCallback != nil
// Failure to save
withCallback!(false)
else
if withCallback != nil
// Failure to save
withCallback!(false)
【讨论】:
此解决方案在首次运行时不处理授权问题。【参考方案9】:Swift 5 更新,添加了完成处理程序。
用法:
CustomPhotoAlbum.sharedInstance.save(image, completion: result, error in
if let e = error
// handle error
return
// save successful, do something (such as inform user)
)
单例类:
import Foundation
import Photos
import UIKit
class CustomPhotoAlbum: NSObject
static let albumName = "Album Name"
static let sharedInstance = CustomPhotoAlbum()
var assetCollection: PHAssetCollection!
override init()
super.init()
if let assetCollection = fetchAssetCollectionForAlbum()
self.assetCollection = assetCollection
return
if PHPhotoLibrary.authorizationStatus() != PHAuthorizationStatus.authorized
PHPhotoLibrary.requestAuthorization( (status: PHAuthorizationStatus) -> Void in
()
)
if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized
self.createAlbum()
else
PHPhotoLibrary.requestAuthorization(requestAuthorizationHandler)
func requestAuthorizationHandler(status: PHAuthorizationStatus)
if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized
// ideally this ensures the creation of the photo album even if authorization wasn't prompted till after init was done
print("trying again to create the album")
self.createAlbum()
else
print("should really prompt the user to let them know it's failed")
func createAlbum()
PHPhotoLibrary.shared().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: CustomPhotoAlbum.albumName) // create an asset collection with the album name
) success, error in
if success
self.assetCollection = self.fetchAssetCollectionForAlbum()
else
print("error \(String(describing: error))")
func fetchAssetCollectionForAlbum() -> PHAssetCollection?
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if let _: AnyObject = collection.firstObject
return collection.firstObject
return nil
func save(_ image: UIImage, completion: @escaping ((Bool, Error?) -> ()))
if assetCollection == nil
// if there was an error upstream, skip the save
return
PHPhotoLibrary.shared().performChanges(
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest!.addAssets(enumeration)
, completionHandler: result, error in
completion(result, error)
)
【讨论】:
谢谢!在您的保存函数中,我们可以将图像作为数组传递吗?【参考方案10】:如果您对一种面向协议的方法感兴趣,该方法允许简单地保存到多个具有不同名称的专辑,这些专辑在 Swift 4 中是最新的并避免使用单例,然后继续阅读。
此方法检查并获得用户授权,检查或创建相册,然后将图像保存到请求的相册中。如果在任何时候触发了错误,则会运行完成块并显示相应的错误。
这种方法的一个优点是,在创建实例后不会提示用户访问照片,而是在他们实际尝试保存图像时提示他们(如果需要授权)。
这个方法还可以让你定义一个非常简单的类来封装相册,符合PhotoAlbumHandler协议,从而免费获取所有相册交互逻辑,像这样:
class PhotoAlbum: PhotoAlbumHandler
var albumName: String
init(named: String)
albumName = named
然后,您还可以创建一个枚举来管理和封装您的所有相册。添加对另一个专辑的支持就像向枚举添加一个新案例并定义相应的专辑名称一样简单。
像这样:
public enum PhotoAlbums
case landscapes
case portraits
var albumName: String
switch self
case .landscapes: return "Landscapes"
case .portraits: return "Portraits"
func album() -> PhotoAlbumHandler
return PhotoAlbum.init(named: albumName)
使用这种方法可以轻松管理您的相册,在您的 viewModel(或视图控制器,如果您不使用视图模型)中,您可以像这样创建对相册的引用:
let landscapeAlbum = PhotoAlbums.landscapes.album()
let portraitAlbum = PhotoAlbums.portraits.album()
然后要将图像保存到其中一个相册,您可以执行以下操作:
let photo: UIImage = UIImage.init(named: "somePhotoName")
landscapeAlbum.save(photo) (error) in
DispatchQueue.main.async
if let error = error
// show alert with error message or...???
self.label.text = error.message
return
self.label.text = "Saved image to album"
对于错误处理,我选择将任何可能的错误封装在错误枚举中:
public enum PhotoAlbumHandlerError
case unauthorized
case authCancelled
case albumNotExists
case saveFailed
case unknown
var title: String
return "Photo Save Error"
var message: String
switch self
case .unauthorized:
return "Not authorized to access photos. Enable photo access in the 'Settings' app to continue."
case .authCancelled:
return "The authorization process was cancelled. You will not be able to save to your photo albums without authorizing access."
case .albumNotExists:
return "Unable to create or find the specified album."
case .saveFailed:
return "Failed to save specified image."
case .unknown:
return "An unknown error occured."
定义接口的协议和处理与系统相册功能交互的协议扩展在这里:
import Photos
public protocol PhotoAlbumHandler: class
var albumName: String get set
func save(_ photo: UIImage, completion: @escaping (PhotoAlbumHandlerError?) -> Void)
extension PhotoAlbumHandler
func save(_ photo: UIImage, completion: @escaping (PhotoAlbumHandlerError?) -> Void)
// Check for permission
guard PHPhotoLibrary.authorizationStatus() == .authorized else
// not authorized, prompt for access
PHPhotoLibrary.requestAuthorization( [weak self] status in
// not authorized, end with error
guard let strongself = self, status == .authorized else
completion(.authCancelled)
return
// received authorization, try to save photo to album
strongself.save(photo, completion: completion)
)
return
// check for album, create if not exists
guard let album = fetchAlbum(named: albumName) else
// album does not exist, create album now
createAlbum(named: albumName, completion: [weak self] success, error in
// album not created, end with error
guard let strongself = self, success == true, error == nil else
completion(.albumNotExists)
return
// album created, run through again
strongself.save(photo, completion: completion)
)
return
// save the photo now... we have permission and the desired album
insert(photo: photo, in: album, completion: success, error in
guard success == true, error == nil else
completion(.saveFailed)
return
// finish with no error
completion(nil)
)
internal func fetchAlbum(named: String) -> PHAssetCollection?
let options = PHFetchOptions()
options.predicate = NSPredicate(format: "title = %@", named)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: options)
guard let album = collection.firstObject else
return nil
return album
internal func createAlbum(named: String, completion: @escaping (Bool, Error?) -> Void)
PHPhotoLibrary.shared().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: named)
, completionHandler: completion)
internal func insert(photo: UIImage, in collection: PHAssetCollection, completion: @escaping (Bool, Error?) -> Void)
PHPhotoLibrary.shared().performChanges(
let request = PHAssetChangeRequest.creationRequestForAsset(from: photo)
request.creationDate = NSDate.init() as Date
guard let assetPlaceHolder = request.placeholderForCreatedAsset,
let albumChangeRequest = PHAssetCollectionChangeRequest(for: collection) else
return
let enumeration: NSArray = [assetPlaceHolder]
albumChangeRequest.addAssets(enumeration)
, completionHandler: completion)
如果您想查看一个示例 Xcode 项目,可以在这里找到:https://github.com/appteur/ios_photo_album_sample
【讨论】:
我对您在代码guard let strongself = self
中的自我检查感兴趣...您能解释一下您这样做的原因吗?我看到您将 weak self
分配给您的闭包属性。我不太了解闭包或内存周期可能出现的问题,但假设它是为了使潜在的内存周期短路?【参考方案11】:
谢谢,正在尝试使用此代码,但发现一些逻辑错误。这是清理后的代码
import Photos
class CustomPhotoAlbum: NSObject
static let albumName = Bundle.main.infoDictionary![kCFBundleNameKey as String] as! String
static let shared = CustomPhotoAlbum()
private lazy var assetCollection = fetchAssetCollectionForAlbum()
private override init()
super.init()
private func checkAuthorizationWithHandler(completion: @escaping ((_ success: Bool) -> Void))
switch PHPhotoLibrary.authorizationStatus()
case .authorized:
completion(true)
case .notDetermined:
PHPhotoLibrary.requestAuthorization() (status) in
self.checkAuthorizationWithHandler(completion: completion)
case .denied, .restricted:
completion(false)
private func fetchAssetCollectionForAlbum() -> PHAssetCollection?
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName)
let fetch = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
return fetch.firstObject
func save(image: UIImage)
func saveIt(_ validAssets: PHAssetCollection)
PHPhotoLibrary.shared().performChanges(
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: validAssets)
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest!.addAssets(enumeration)
, completionHandler: nil)
self.checkAuthorizationWithHandler (success) in
if success
if let validAssets = self.assetCollection // Album already exists
saveIt(validAssets)
else // create an asset collection with the album name
PHPhotoLibrary.shared().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: CustomPhotoAlbum.albumName)
) success, error in
if success, let validAssets = self.fetchAssetCollectionForAlbum()
self.assetCollection = validAssets
saveIt(validAssets)
else
// TODO: send user message "Sorry, unable to create album and save image..."
【讨论】:
谢谢!这是我最终使用的解决方案。我第一次尝试保存图片时,以前的解决方案不起作用。这个从一开始就很好用;)【参考方案12】:即使在修复之后,我的 PhotoAlbum 仍然无法处理第一张图片,如果我想一次保存多张图片,我最终会得到多个空相册。所以我升级了课程,我只在相册创建后保存表情符号。
新版本:
class CustomPhotoAlbum: NSObject
static let albumName = "AlbumName"
static let shared = CustomPhotoAlbum()
private var assetCollection: PHAssetCollection!
private override init()
super.init()
if let assetCollection = fetchAssetCollectionForAlbum()
self.assetCollection = assetCollection
return
private func checkAuthorizationWithHandler(completion: @escaping ((_ success: Bool) -> Void))
if PHPhotoLibrary.authorizationStatus() == .notDetermined
PHPhotoLibrary.requestAuthorization( (status) in
self.checkAuthorizationWithHandler(completion: completion)
)
else if PHPhotoLibrary.authorizationStatus() == .authorized
self.createAlbumIfNeeded()
completion(true)
else
completion(false)
private func createAlbumIfNeeded()
/* if let assetCollection = fetchAssetCollectionForAlbum()
// Album already exists
self.assetCollection = assetCollection
else
PHPhotoLibrary.shared().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: CustomPhotoAlbum.albumName) // create an asset collection with the album name
) success, error in
if success
self.assetCollection = self.fetchAssetCollectionForAlbum()
else
// Unable to create album
*/
private func fetchAssetCollectionForAlbum() -> PHAssetCollection?
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if let _: AnyObject = collection.firstObject
return collection.firstObject
return nil
func save(image: UIImage)
self.checkAuthorizationWithHandler (success) in
if success
if let assetCollection = self.fetchAssetCollectionForAlbum()
// Album already exists
self.assetCollection = assetCollection
PHPhotoLibrary.shared().performChanges(
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest!.addAssets(enumeration)
, completionHandler: nil)
else
PHPhotoLibrary.shared().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: CustomPhotoAlbum.albumName) // create an asset collection with the album name
) success, error in
if success
self.assetCollection = self.fetchAssetCollectionForAlbum()
PHPhotoLibrary.shared().performChanges(
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest!.addAssets(enumeration)
, completionHandler: nil)
else
// Unable to create album
如果您想一次保存多个图像,这是我的代码。这里的关键是延迟保存不是第一个图像的其他图像,因为我们必须先创建相册。 (否则我们最终会得到重复的相册,因为所有保存过程都会尝试创建自定义相册)。这是我的应用程序的代码,所以你可以理解逻辑:
var overFirstSave = false
for stickerName in filenames
let url = self.getDocumentsDirectory().appendingPathComponent(stickerName as! String)
do
if !overFirstSave
CustomPhotoAlbum.shared.save(image: UIImage(contentsOfFile: url.path)!)
overFirstSave = true
else
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3), execute:
CustomPhotoAlbum.shared.save(image: UIImage(contentsOfFile: url.path)!)
)
catch
print(error)
【讨论】:
谢谢,这个代码也适用于我的第一次调用。,其他人没有。【参考方案13】:Swift 4 和 5
点击按钮这样调用
@IBAction func btnSave(_ sender: Any)
createAlbum()
self.saveGIFInAlbum(url: gifUrl)
func createAlbum()
if let assetCollection = fetchAssetCollectionForAlbum()
self.assetCollection = assetCollection
return
if PHPhotoLibrary.authorizationStatus() != PHAuthorizationStatus.authorized
PHPhotoLibrary.requestAuthorization( (status: PHAuthorizationStatus) -> Void in
()
)
if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized
self.createAlbumIfNeeded()
else
PHPhotoLibrary.requestAuthorization(requestAuthorizationHandler)
func requestAuthorizationHandler(status: PHAuthorizationStatus)
if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized
// ideally this ensures the creation of the photo album even if authorization wasn't prompted till after init was done
print("trying again to create the album")
self.createAlbumIfNeeded()
else
print("should really prompt the user to let them know it's failed")
func createAlbumIfNeeded()
PHPhotoLibrary.shared().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: "PicBloom") // create an asset collection with the album name
) success, error in
if success
self.assetCollection = self.fetchAssetCollectionForAlbum()
else
print("error \(String(describing: error))")
func fetchAssetCollectionForAlbum() -> PHAssetCollection?
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", "PicBloom")
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if let _: AnyObject = collection.firstObject
return collection.firstObject
return nil
func saveGIFInAlbum(url: URL)
PHPhotoLibrary.shared().performChanges(
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: url)
let assetPlaceHolder = assetChangeRequest?.placeholderForCreatedAsset
if(self.assetCollection != nil)
let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest!.addAssets(enumeration)
, completionHandler: success, error in
DispatchQueue.main.async
if success
let message = "Gif has been saved to your gallery!"
let alert = UIAlertController(title: "", message: message, preferredStyle: .alert)
self.present(alert, animated: true)
let duration: Double = 1.5
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration)
alert.dismiss(animated: true)
else if error != nil
print("handle error since couldn't save GIF")
)
【讨论】:
以上是关于如何将图像保存到自定义相册?的主要内容,如果未能解决你的问题,请参考以下文章