TLPhotoLibrary.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. //
  2. // TLPhotoLibrary.swift
  3. // TLPhotosPicker
  4. //
  5. // Created by wade.hawk on 2017. 5. 3..
  6. // Copyright © 2017년 wade.hawk. All rights reserved.
  7. //
  8. import Foundation
  9. import Photos
  10. protocol TLPhotoLibraryDelegate: class {
  11. func loadCameraRollCollection(collection: TLAssetsCollection)
  12. func loadCompleteAllCollection(collections: [TLAssetsCollection])
  13. }
  14. class TLPhotoLibrary {
  15. weak var delegate: TLPhotoLibraryDelegate? = nil
  16. lazy var imageManager: PHCachingImageManager = {
  17. return PHCachingImageManager()
  18. }()
  19. deinit {
  20. // print("deinit TLPhotoLibrary")
  21. }
  22. @discardableResult
  23. func livePhotoAsset(asset: PHAsset, size: CGSize = CGSize(width: 720, height: 1280), progressBlock: Photos.PHAssetImageProgressHandler? = nil, completionBlock:@escaping (PHLivePhoto,Bool)-> Void ) -> PHImageRequestID {
  24. let options = PHLivePhotoRequestOptions()
  25. options.deliveryMode = .opportunistic
  26. options.isNetworkAccessAllowed = true
  27. options.progressHandler = progressBlock
  28. let scale = min(UIScreen.main.scale,2)
  29. let targetSize = CGSize(width: size.width*scale, height: size.height*scale)
  30. let requestId = self.imageManager.requestLivePhoto(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { (livePhoto, info) in
  31. let complete = (info?["PHImageResultIsDegradedKey"] as? Bool) == false
  32. if let livePhoto = livePhoto {
  33. completionBlock(livePhoto,complete)
  34. }
  35. }
  36. return requestId
  37. }
  38. @discardableResult
  39. func videoAsset(asset: PHAsset, size: CGSize = CGSize(width: 720, height: 1280), progressBlock: Photos.PHAssetImageProgressHandler? = nil, completionBlock:@escaping (AVPlayerItem?, [AnyHashable : Any]?) -> Void ) -> PHImageRequestID {
  40. let options = PHVideoRequestOptions()
  41. options.isNetworkAccessAllowed = true
  42. options.deliveryMode = .automatic
  43. options.progressHandler = progressBlock
  44. let requestId = self.imageManager.requestPlayerItem(forVideo: asset, options: options, resultHandler: { playerItem, info in
  45. completionBlock(playerItem,info)
  46. })
  47. return requestId
  48. }
  49. @discardableResult
  50. func imageAsset(asset: PHAsset, size: CGSize = CGSize(width: 160, height: 160), options: PHImageRequestOptions? = nil, completionBlock:@escaping (UIImage,Bool)-> Void ) -> PHImageRequestID {
  51. var options = options
  52. if options == nil {
  53. options = PHImageRequestOptions()
  54. options?.isSynchronous = false
  55. options?.resizeMode = .exact
  56. options?.deliveryMode = .opportunistic
  57. options?.isNetworkAccessAllowed = true
  58. }
  59. let scale = min(UIScreen.main.scale,2)
  60. let targetSize = CGSize(width: size.width*scale, height: size.height*scale)
  61. let requestId = self.imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { image, info in
  62. let complete = (info?["PHImageResultIsDegradedKey"] as? Bool) == false
  63. if let image = image {
  64. completionBlock(image,complete)
  65. }
  66. }
  67. return requestId
  68. }
  69. func cancelPHImageRequest(requestId: PHImageRequestID) {
  70. self.imageManager.cancelImageRequest(requestId)
  71. }
  72. @discardableResult
  73. class func cloudImageDownload(asset: PHAsset, size: CGSize = PHImageManagerMaximumSize, progressBlock: @escaping (Double) -> Void, completionBlock:@escaping (UIImage?)-> Void ) -> PHImageRequestID {
  74. let options = PHImageRequestOptions()
  75. options.isSynchronous = false
  76. options.isNetworkAccessAllowed = true
  77. options.deliveryMode = .opportunistic
  78. options.version = .current
  79. options.resizeMode = .exact
  80. options.progressHandler = { (progress,error,stop,info) in
  81. progressBlock(progress)
  82. }
  83. let requestId = PHCachingImageManager().requestImageData(for: asset, options: options) { (imageData, dataUTI, orientation, info) in
  84. if let data = imageData,let _ = info {
  85. completionBlock(UIImage(data: data))
  86. }else{
  87. completionBlock(nil)//error
  88. }
  89. }
  90. return requestId
  91. }
  92. @discardableResult
  93. class func fullResolutionImageData(asset: PHAsset) -> UIImage? {
  94. let options = PHImageRequestOptions()
  95. options.isSynchronous = true
  96. options.resizeMode = .none
  97. options.isNetworkAccessAllowed = false
  98. options.version = .current
  99. var image: UIImage? = nil
  100. _ = PHCachingImageManager().requestImageData(for: asset, options: options) { (imageData, dataUTI, orientation, info) in
  101. if let data = imageData {
  102. image = UIImage(data: data)
  103. }
  104. }
  105. return image
  106. }
  107. }
  108. extension PHFetchOptions {
  109. func merge(predicate: NSPredicate) {
  110. if let storePredicate = self.predicate {
  111. self.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [storePredicate, predicate])
  112. }else {
  113. self.predicate = predicate
  114. }
  115. }
  116. }
  117. //MARK: - Load Collection
  118. extension TLPhotoLibrary {
  119. func getOption(configure: TLPhotosPickerConfigure) -> PHFetchOptions {
  120. let options = configure.fetchOption ?? PHFetchOptions()
  121. options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
  122. if let mediaType = configure.mediaType {
  123. let mediaTypePredicate = configure.maxVideoDuration != nil && mediaType == PHAssetMediaType.video ? NSPredicate(format: "mediaType = %i AND duration < %f", mediaType.rawValue, configure.maxVideoDuration! + 1) : NSPredicate(format: "mediaType = %i", mediaType.rawValue)
  124. options.merge(predicate: mediaTypePredicate)
  125. } else if !configure.allowedVideo {
  126. let mediaTypePredicate = NSPredicate(format: "mediaType = %i", PHAssetMediaType.image.rawValue)
  127. options.merge(predicate: mediaTypePredicate)
  128. } else if let duration = configure.maxVideoDuration {
  129. let mediaTypePredicate = NSPredicate(format: "mediaType = %i OR (mediaType = %i AND duration < %f)", PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue, duration + 1)
  130. options.merge(predicate: mediaTypePredicate)
  131. }
  132. return options
  133. }
  134. func fetchResult(collection: TLAssetsCollection?, configure: TLPhotosPickerConfigure) -> PHFetchResult<PHAsset>? {
  135. guard let phAssetCollection = collection?.phAssetCollection else { return nil }
  136. let options = getOption(configure: configure)
  137. return PHAsset.fetchAssets(in: phAssetCollection, options: options)
  138. }
  139. func fetchCollection(configure: TLPhotosPickerConfigure) {
  140. let useCameraButton = configure.usedCameraButton
  141. let options = getOption(configure: configure)
  142. func getAlbum(subType: PHAssetCollectionSubtype, result: inout [TLAssetsCollection]) {
  143. let fetchCollection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: subType, options: nil)
  144. var collections = [PHAssetCollection]()
  145. fetchCollection.enumerateObjects { (collection, index, _) in
  146. if configure.allowedAlbumCloudShared == false && collection.assetCollectionSubtype == .albumCloudShared {
  147. }else {
  148. collections.append(collection)
  149. }
  150. }
  151. for collection in collections {
  152. if !result.contains(where: { $0.localIdentifier == collection.localIdentifier }) {
  153. var assetsCollection = TLAssetsCollection(collection: collection)
  154. assetsCollection.fetchResult = PHAsset.fetchAssets(in: collection, options: options)
  155. if assetsCollection.count > 0 {
  156. result.append(assetsCollection)
  157. }
  158. }
  159. }
  160. }
  161. @discardableResult
  162. func getSmartAlbum(subType: PHAssetCollectionSubtype, useCameraButton: Bool = false, result: inout [TLAssetsCollection]) -> TLAssetsCollection? {
  163. let fetchCollection = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: subType, options: nil)
  164. if let collection = fetchCollection.firstObject, !result.contains(where: { $0.localIdentifier == collection.localIdentifier }) {
  165. var assetsCollection = TLAssetsCollection(collection: collection)
  166. assetsCollection.fetchResult = PHAsset.fetchAssets(in: collection, options: options)
  167. if assetsCollection.count > 0 || useCameraButton {
  168. result.append(assetsCollection)
  169. return assetsCollection
  170. }
  171. }
  172. return nil
  173. }
  174. if let fetchCollectionTypes: [(PHAssetCollectionType,PHAssetCollectionSubtype)] = configure.fetchCollectionTypes {
  175. DispatchQueue.global(qos: .userInteractive).async { [weak self] in
  176. var assetCollections = [TLAssetsCollection]()
  177. for (type,subType) in fetchCollectionTypes {
  178. if type == .smartAlbum {
  179. getSmartAlbum(subType: subType, result: &assetCollections)
  180. }else {
  181. getAlbum(subType: subType, result: &assetCollections)
  182. }
  183. }
  184. DispatchQueue.main.async {
  185. self?.delegate?.loadCompleteAllCollection(collections: assetCollections)
  186. }
  187. }
  188. }else {
  189. DispatchQueue.global(qos: .userInteractive).async { [weak self] in
  190. var assetCollections = [TLAssetsCollection]()
  191. //Camera Roll
  192. let camerarollCollection = getSmartAlbum(subType: .smartAlbumUserLibrary, useCameraButton: useCameraButton, result: &assetCollections)
  193. if var cameraRoll = camerarollCollection {
  194. cameraRoll.useCameraButton = useCameraButton
  195. assetCollections[0] = cameraRoll
  196. DispatchQueue.main.async {
  197. self?.delegate?.loadCameraRollCollection(collection: cameraRoll)
  198. }
  199. }
  200. //Selfies
  201. getSmartAlbum(subType: .smartAlbumSelfPortraits, result: &assetCollections)
  202. //Panoramas
  203. getSmartAlbum(subType: .smartAlbumPanoramas, result: &assetCollections)
  204. //Favorites
  205. getSmartAlbum(subType: .smartAlbumFavorites, result: &assetCollections)
  206. //CloudShared
  207. getSmartAlbum(subType: .albumCloudShared, result: &assetCollections)
  208. //get all another albums
  209. getAlbum(subType: .any, result: &assetCollections)
  210. if configure.allowedVideo {
  211. //Videos
  212. getSmartAlbum(subType: .smartAlbumVideos, result: &assetCollections)
  213. }
  214. //Album
  215. let albumsResult = PHCollectionList.fetchTopLevelUserCollections(with: nil)
  216. albumsResult.enumerateObjects({ (collection, index, stop) -> Void in
  217. guard let collection = collection as? PHAssetCollection else { return }
  218. var assetsCollection = TLAssetsCollection(collection: collection)
  219. assetsCollection.fetchResult = PHAsset.fetchAssets(in: collection, options: options)
  220. if assetsCollection.count > 0, !assetCollections.contains(where: { $0.localIdentifier == collection.localIdentifier }) {
  221. assetCollections.append(assetsCollection)
  222. }
  223. })
  224. DispatchQueue.main.async {
  225. self?.delegate?.loadCompleteAllCollection(collections: assetCollections)
  226. }
  227. }
  228. }
  229. }
  230. }