[英]How to save image to custom album?
我有一個全新的 iOS 應用程序,可以生成圖像並讓用戶將它們保存到 Camera SavedPhotosAlbum 中。 但是,我想做一些像 Snapchat 和 Frontback 之類的事情,並將這些圖像也保存到自定義命名的相冊中。
所以這是我現在的代碼:
let imageToSave = self.currentPreviewImage
let softwareContext = CIContext(options:[kCIContextUseSoftwareRenderer: true])
let cgimg = softwareContext.createCGImage(imageToSave, fromRect:imageToSave.extent())
ALAssetsLibrary().writeImageToSavedPhotosAlbum(cgimg, metadata:imageToSave.properties(), completionBlock:nil)
我見過一些人在 Objective-C 中這樣做的例子,但沒有任何東西可以轉換為 Swift,我檢查了writeImageToSavedPhotosAlbum
方法簽名,但似乎沒有一個允許保存到自定義相冊。
我想出了這個單例類來處理它:
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 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)
}
}
這是一個更新版本,適用於 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)
}
}
以前的答案很好,對我幫助很大,但在第一次通話時保存圖像仍然存在問題。 以下解決方案並不完全干凈,但解決了該問題。 適用於 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)
}
}
}
}
我發現這里提出的一些解決方案正在起作用,但我想重寫它的可重用版本。 以下是您如何使用它:
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
}
}
改進了@Damien answer 。 也適用於UIImage
和視頻(帶網址)。 Swift4
測試:
import Photos
class MyAwesomeAlbum: NSObject {
static let albumName = "My Awesome Album"
static let shared = MyAwesomeAlbum()
private var assetCollection: PHAssetCollection!
private override init() {
super.init()
if let assetCollection = fetchAssetCollectionForAlbum() {
self.assetCollection = assetCollection
return
}
}
private func checkAuthorizationWithHandler(completion: @escaping ((_ success: Bool) -> Void)) {
if PHPhotoLibrary.authorizationStatus() == .notDetermined {
PHPhotoLibrary.requestAuthorization({ (status) in
self.checkAuthorizationWithHandler(completion: completion)
})
}
else if PHPhotoLibrary.authorizationStatus() == .authorized {
self.createAlbumIfNeeded { (success) in
if success {
completion(true)
} else {
completion(false)
}
}
}
else {
completion(false)
}
}
private func createAlbumIfNeeded(completion: @escaping ((_ success: Bool) -> Void)) {
if let assetCollection = fetchAssetCollectionForAlbum() {
// Album already exists
self.assetCollection = assetCollection
completion(true)
} else {
PHPhotoLibrary.shared().performChanges({
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: MyAwesomeAlbum.albumName) // create an asset collection with the album name
}) { success, error in
if success {
self.assetCollection = self.fetchAssetCollectionForAlbum()
completion(true)
} else {
// Unable to create album
completion(false)
}
}
}
}
private func fetchAssetCollectionForAlbum() -> PHAssetCollection? {
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", MyAwesomeAlbum.albumName)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if let _: AnyObject = collection.firstObject {
return collection.firstObject
}
return nil
}
func save(image: UIImage) {
self.checkAuthorizationWithHandler { (success) in
if success, self.assetCollection != nil {
PHPhotoLibrary.shared().performChanges({
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
if let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection) {
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest.addAssets(enumeration)
}
}, completionHandler: { (success, error) in
if success {
print("Successfully saved image to Camera Roll.")
} else {
print("Error writing to image library: \(error!.localizedDescription)")
}
})
}
}
}
func saveMovieToLibrary(movieURL: URL) {
self.checkAuthorizationWithHandler { (success) in
if success, self.assetCollection != nil {
PHPhotoLibrary.shared().performChanges({
if let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: movieURL) {
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
if let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection) {
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest.addAssets(enumeration)
}
}
}, completionHandler: { (success, error) in
if success {
print("Successfully saved video to Camera Roll.")
} else {
print("Error writing to movie library: \(error!.localizedDescription)")
}
})
}
}
}
}
用法:
MyAwesomeAlbum.shared.save(image: image)
或者
MyAwesomeAlbum.shared.saveMovieToLibrary(movieURL: url)
用 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)
}
})
}
}
}
對於那些正在尋找使用 Swift 4 的單功能解決方案的人,我將上面的一些代碼壓縮到一個函數中,該函數只接收 UIImage、字符串類型的專輯名稱和指示成功/失敗的回調。
注意:這個函數更復雜,所以它的運行時間顯然比以前的解決方案慢,但我把它貼在這里是為了方便其他人。
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)
}
}
}
}
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)
})
}
}
如果您對面向協議的方法感興趣,該方法允許簡單地保存到多個不同名稱的專輯中,這些專輯與 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)
}
}
使用這種方法可以輕而易舉地管理您的相冊,在您的視圖模型(或視圖控制器,如果您不使用視圖模型)中,您可以像這樣創建對相冊的引用:
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
謝謝,正在嘗試使用此代碼,但發現了一些邏輯錯誤。 這是清理后的代碼
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..."
}
}
}
}
}
}
}
即使在修復之后,我的相冊仍然無法用於第一張圖像,如果我想一次保存多個圖像,我最終會得到多個空相冊。 所以我升級了課程,我只在創建相冊后保存表情符號。
新版本:
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)
}
}
斯威夫特 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")
}
}
})
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.