ios 如何将图像保存到自定义相册?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28708846/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to save image to custom album?
提问by rodrigoalvesvieira
I have a brand new iOS app that generates images and lets the users save them into the Camera SavedPhotosAlbum. However, I wanna do something like Snapchat and Frontback, and save these images also to a custom-named album.
我有一个全新的 iOS 应用程序,可以生成图像并让用户将它们保存到 Camera SavedPhotosAlbum 中。但是,我想做一些像 Snapchat 和 Frontback 之类的事情,并将这些图像也保存到自定义命名的相册中。
So this is my code right now:
所以这是我现在的代码:
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)
I've seen a few examples of people doing this in Objective-C but nothing that I could translate to Swift, and I've check the writeImageToSavedPhotosAlbum
method signatures and none of them seem to allow saving to a custom album.
我见过一些人在 Objective-C 中这样做的例子,但没有任何东西可以转换为 Swift,我检查了writeImageToSavedPhotosAlbum
方法签名,但似乎没有一个允许保存到自定义相册。
回答by scootermg
I came up with this singleton class to handle it:
我想出了这个单例类来处理它:
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)
}
}
When you first instantiate the class, the custom album will be created if it doesn't already exist. You can save an image like this:
当您第一次实例化该类时,如果自定义相册尚不存在,则会创建它。您可以像这样保存图像:
CustomPhotoAlbum.sharedInstance.saveImage(image)
NOTE: The CustomPhotoAlbum class assumes the app already has permission to access the Photo Library. Dealing with the permissions is a bit outside the scope of this question/answer. So make sure PHPhotoLibrary.authorizationStatus() == .Authorize before you use it. And request authorization if necessary.
注意:CustomPhotoAlbum 类假定应用程序已经有权访问照片库。处理权限有点超出此问题/答案的范围。因此,在使用之前请确保 PHPhotoLibrary.authorizationStatus() == .Authorize。并在必要时请求授权。
回答by Jakub Pr??a
Latest Swift 3.0syntax. :)
最新的 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)
}
}
回答by Cody
This is an updated version, works in Swift 2.1, and avoids the bug where the album is not created and images are not saved on the first launch (when authorization to write to the photo library is first requested/granted).
这是一个更新版本,适用于 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)
}
}
回答by Damien
The previous answers were great and helped me a lot, but still there was a problem saving the image on the first call. The following solution is not perfectly clean but addresses the issue. Works with Swift 3/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)
}
}
}
}
回答by jbouaziz
I found that some proposed solutions here were working but I wanted to rewrite a reusable version of it. Here is how you use it:
我发现这里提出的一些解决方案正在起作用,但我想重写它的可重用版本。以下是您如何使用它:
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)
When saving an image, it request the user's photo access (which returns immediately if previously authorized) and tries to create an album if one doesn't exist yet. Below is the full source code written in Swift 3 and compatible with Objective-C.
保存图像时,它会请求用户的照片访问权限(如果先前获得授权,则立即返回)并尝试创建相册(如果尚不存在)。以下是用 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
}
}
回答by Rebeloper
Improved upon @Damien answer. Works with UIImage
and video (with url) too. Swift4
tested:
改进了@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)")
}
})
}
}
}
}
Usage:
用法:
MyAwesomeAlbum.shared.save(image: image)
or
或者
MyAwesomeAlbum.shared.saveMovieToLibrary(movieURL: url)
回答by Anthony Krivonos
For those of you looking for a one-function solutionusing Swift 4, I have condensed some of the above code into a function that simply takes in a UIImage, String-type album name, and a callback indicating success/failure.
对于那些正在寻找使用 Swift 4的单功能解决方案的人,我将上面的一些代码压缩到一个函数中,该函数只接收 UIImage、字符串类型的专辑名称和指示成功/失败的回调。
Note: this function is more complex so it will obviously have a slower runtime than the previous solutions, but I have posted it here for other peoples' convenience.
注意:这个函数更复杂,所以它的运行时间显然比以前的解决方案慢,但我把它贴在这里是为了方便其他人。
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)
}
}
}
}
回答by digitalHound
If you're interested in a protocol oriented approach that allows for simple saving to multiple albums with different names that's up to date with Swift 4 and avoids singleton use then read on.
如果您对面向协议的方法感兴趣,该方法允许简单地保存到多个不同名称的专辑中,这些专辑与 Swift 4 是最新的,并避免单例使用,请继续阅读。
This approach checks for and obtains user authorization, checks for or creates the photo album then saves the image to the requested album. If at any point an error is triggered the completion block is run with the corresponding error.
这种方法检查并获得用户授权,检查或创建相册,然后将图像保存到请求的相册中。如果在任何时候触发了错误,则完成块会以相应的错误运行。
An upside of this approach is that the user is not prompted for photos access as soon as the instance is created, instead they are prompted when they are actually trying to save their image, if authorization is required.
这种方法的一个好处是,在创建实例后不会立即提示用户访问照片,而是在他们实际尝试保存图像时(如果需要授权)提示他们。
This method also allows you to define a very simple class that encapsulates a photo album, conforming to the PhotoAlbumHandler protocol and thus getting all the photo album interaction logic for free, like this:
这个方法还可以让你定义一个非常简单的类来封装相册,符合PhotoAlbumHandler协议,从而免费获取所有相册交互逻辑,如下所示:
class PhotoAlbum: PhotoAlbumHandler {
var albumName: String
init(named: String) {
albumName = named
}
}
You can also then create an enum that manages and encapsulates all your photo albums. Adding support for another album is as simple as adding a new case to the enum and defining the corresponding albumName.
然后,您还可以创建一个枚举来管理和封装您的所有相册。添加对另一个专辑的支持就像在枚举中添加一个新案例并定义相应的专辑名称一样简单。
Like this:
像这样:
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)
}
}
Using this approach makes managing your photo albums a breeze, in your viewModel (or view controller if you're not using view models) you can create references to your albums like this:
使用这种方法可以轻而易举地管理您的相册,在您的视图模型(或视图控制器,如果您不使用视图模型)中,您可以像这样创建对相册的引用:
let landscapeAlbum = PhotoAlbums.landscapes.album()
let portraitAlbum = PhotoAlbums.portraits.album()
Then to save an image to one of the albums you could do something like this:
然后要将图像保存到其中一个相册,您可以执行以下操作:
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"
}
}
For error handling I opted to encapsulate any possible errors in an error enum:
对于错误处理,我选择将任何可能的错误封装在错误枚举中:
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."
}
}
}
The protocol that defines the interface and the protocol extension that handles interaction with the system Photo Album functionality is here:
定义接口的协议和处理与系统相册功能交互的协议扩展在这里:
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)
}
}
If you'd like to look through a sample Xcode project you can find one here: https://github.com/appteur/ios_photo_album_sample
如果您想查看示例 Xcode 项目,可以在此处找到:https: //github.com/appteur/ios_photo_album_sample
回答by Oscar van der Meer
Thanks, was trying to use this code, but found some logic errors. Here is the cleaned up code
谢谢,正在尝试使用此代码,但发现了一些逻辑错误。这是清理后的代码
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..."
}
}
}
}
}
}
}
回答by elliott-io
Swift 5 update, with added completion handler.
Swift 5 更新,添加了完成处理程序。
Usage:
用法:
CustomPhotoAlbum.sharedInstance.save(image, completion: { result, error in
if let e = error {
// handle error
return
}
// save successful, do something (such as inform user)
})
Singleton class:
单例类:
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)
})
}
}