TLPhotoLibrary.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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 mediaPredicate = NSPredicate(format: "mediaType = %i", mediaType.rawValue)
  124. options.merge(predicate: mediaPredicate)
  125. }
  126. if configure.allowedVideo == false {
  127. let notVideoPredicate = NSPredicate(format: "mediaType != %i", PHAssetMediaType.video.rawValue)
  128. options.merge(predicate: notVideoPredicate)
  129. }
  130. if configure.allowedLivePhotos == false {
  131. let notLivePhotoPredicate = NSPredicate(format: "mediaType = %i OR mediaSubtype != %i",
  132. PHAssetMediaType.video.rawValue,
  133. PHAssetMediaSubtype.photoLive.rawValue)
  134. options.merge(predicate: notLivePhotoPredicate)
  135. }
  136. if let maxVideoDuration = configure.maxVideoDuration {
  137. let durationPredicate = NSPredicate(format: "duration < %f", maxVideoDuration)
  138. options.merge(predicate: durationPredicate)
  139. }
  140. return options
  141. }
  142. func fetchResult(collection: TLAssetsCollection?, configure: TLPhotosPickerConfigure) -> PHFetchResult<PHAsset>? {
  143. guard let phAssetCollection = collection?.phAssetCollection else { return nil }
  144. let options = getOption(configure: configure)
  145. return PHAsset.fetchAssets(in: phAssetCollection, options: options)
  146. }
  147. func fetchCollection(configure: TLPhotosPickerConfigure) {
  148. let useCameraButton = configure.usedCameraButton
  149. let options = getOption(configure: configure)
  150. func getAlbum(subType: PHAssetCollectionSubtype, result: inout [TLAssetsCollection]) {
  151. let fetchCollection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: subType, options: nil)
  152. var collections = [PHAssetCollection]()
  153. fetchCollection.enumerateObjects { (collection, index, _) in
  154. if configure.allowedAlbumCloudShared == false && collection.assetCollectionSubtype == .albumCloudShared {
  155. }else {
  156. collections.append(collection)
  157. }
  158. }
  159. for collection in collections {
  160. if !result.contains(where: { $0.localIdentifier == collection.localIdentifier }) {
  161. var assetsCollection = TLAssetsCollection(collection: collection)
  162. assetsCollection.fetchResult = PHAsset.fetchAssets(in: collection, options: options)
  163. if assetsCollection.count > 0 {
  164. result.append(assetsCollection)
  165. }
  166. }
  167. }
  168. }
  169. @discardableResult
  170. func getSmartAlbum(subType: PHAssetCollectionSubtype, useCameraButton: Bool = false, result: inout [TLAssetsCollection]) -> TLAssetsCollection? {
  171. let fetchCollection = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: subType, options: nil)
  172. if let collection = fetchCollection.firstObject, !result.contains(where: { $0.localIdentifier == collection.localIdentifier }) {
  173. var assetsCollection = TLAssetsCollection(collection: collection)
  174. assetsCollection.fetchResult = PHAsset.fetchAssets(in: collection, options: options)
  175. if assetsCollection.count > 0 || useCameraButton {
  176. result.append(assetsCollection)
  177. return assetsCollection
  178. }
  179. }
  180. return nil
  181. }
  182. if let fetchCollectionTypes: [(PHAssetCollectionType,PHAssetCollectionSubtype)] = configure.fetchCollectionTypes {
  183. DispatchQueue.global(qos: .userInteractive).async { [weak self] in
  184. var assetCollections = [TLAssetsCollection]()
  185. for (type,subType) in fetchCollectionTypes {
  186. if type == .smartAlbum {
  187. getSmartAlbum(subType: subType, result: &assetCollections)
  188. }else {
  189. getAlbum(subType: subType, result: &assetCollections)
  190. }
  191. }
  192. DispatchQueue.main.async {
  193. self?.delegate?.loadCompleteAllCollection(collections: assetCollections)
  194. }
  195. }
  196. }else {
  197. DispatchQueue.global(qos: .userInteractive).async { [weak self] in
  198. var assetCollections = [TLAssetsCollection]()
  199. //Camera Roll
  200. let camerarollCollection = getSmartAlbum(subType: .smartAlbumUserLibrary, useCameraButton: useCameraButton, result: &assetCollections)
  201. if var cameraRoll = camerarollCollection {
  202. cameraRoll.useCameraButton = useCameraButton
  203. assetCollections[0] = cameraRoll
  204. DispatchQueue.main.async {
  205. self?.delegate?.loadCameraRollCollection(collection: cameraRoll)
  206. }
  207. }
  208. //Selfies
  209. getSmartAlbum(subType: .smartAlbumSelfPortraits, result: &assetCollections)
  210. //Panoramas
  211. getSmartAlbum(subType: .smartAlbumPanoramas, result: &assetCollections)
  212. //Favorites
  213. getSmartAlbum(subType: .smartAlbumFavorites, result: &assetCollections)
  214. //CloudShared
  215. getSmartAlbum(subType: .albumCloudShared, result: &assetCollections)
  216. //get all another albums
  217. getAlbum(subType: .any, result: &assetCollections)
  218. if configure.allowedVideo {
  219. //Videos
  220. getSmartAlbum(subType: .smartAlbumVideos, result: &assetCollections)
  221. }
  222. //Album
  223. let albumsResult = PHCollectionList.fetchTopLevelUserCollections(with: nil)
  224. albumsResult.enumerateObjects({ (collection, index, stop) -> Void in
  225. guard let collection = collection as? PHAssetCollection else { return }
  226. var assetsCollection = TLAssetsCollection(collection: collection)
  227. assetsCollection.fetchResult = PHAsset.fetchAssets(in: collection, options: options)
  228. if assetsCollection.count > 0, !assetCollections.contains(where: { $0.localIdentifier == collection.localIdentifier }) {
  229. assetCollections.append(assetsCollection)
  230. }
  231. })
  232. DispatchQueue.main.async {
  233. self?.delegate?.loadCompleteAllCollection(collections: assetCollections)
  234. }
  235. }
  236. }
  237. }
  238. }