可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
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.
回答1:
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.
回答2:
Latest Swift 3.0 syntax. :)
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)
}
}
回答3:
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).
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)
}
}
回答4:
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.
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:
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.
//
// 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:
For those of you looking for a one-function solution using 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.
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)
}
}
}
}
回答7:
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.
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:
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
回答8:
Improved upon @Damien answer. Works with UIImage
and video (with url) too. Swift4
tested:
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)
回答9:
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..."
}
}
}
}
}
}
}
回答10:
Even after the fixes, my PhotoAlbum still didn't work for the first image and if I wanted to save more than one images at once I've ended up with multiple empty albums. So I've upgraded the class and I only save the emoji after the album has been created.
New version:
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
}
}
}
}
}
}
}
If you want to save multiple images at once, here is my code for that. The key here is to delay the saving of the other images which are not the first, because we have to create the album first. (otherwise we end up with duplicate albums, because all the saving processes will try to create a Custom Album). This is the code from my app, so you can understand the logic:
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)
}
}