PQPhotoMaterialController.swift 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. //
  2. // PQPhotoMaterialController.swift
  3. // PQSpeed
  4. //
  5. // Created by SanW on 2021/4/26.
  6. // Copyright © 2021 BytesFlow. All rights reserved.
  7. //
  8. import Photos
  9. import UIKit
  10. class PQPhotoMaterialController: PQBaseViewController {
  11. // 是否显示素材标识
  12. var isShowMediaTag: Bool = true
  13. // 是否是已经添加过的素材
  14. var isAdded: Bool = false
  15. // 默认图片时长
  16. var imageDuration: Float64 = 2.0
  17. // 已选图片时长
  18. var selectedImageDataCount: Int = 0
  19. var itemSpacing: CGFloat = cDefaultMargin / 2 // 间隔
  20. var itemSize = CGSize(width: (cScreenWidth - cDefaultMargin) / 3, height: (cScreenWidth - cDefaultMargin) / 3) // cell 大小
  21. var previousPreheatRect = CGRect.zero
  22. var allPhotos: PHFetchResult<PHAsset> = PHFetchResult<PHAsset>.init() // 所有的图片
  23. var photoData: [PQEditVisionTrackMaterialsModel] = Array<PQEditVisionTrackMaterialsModel>.init() // 相册数据
  24. // 选中的相册数据
  25. var selectedPhotoData: [PQEditVisionTrackMaterialsModel] = Array<PQEditVisionTrackMaterialsModel>.init()
  26. // 选中的总时长
  27. var selectedTotalDuration: Float64 = 0
  28. var selectedMaterialHandle: ((_ currentMaterialData: PQEditVisionTrackMaterialsModel?, _ selectedPhotoData: [PQEditVisionTrackMaterialsModel], _ selectedTotalDuration: Float64, _ imageCount: Int) -> Void)? // 选中/取消选中的回调
  29. var detailMaterialHandle: ((_ indexPath: IndexPath, _ currentMaterialData: PQEditVisionTrackMaterialsModel?) -> Void)? // 点击详情
  30. /// 滑动的回调
  31. var scrollViewDidScroll: ((_ contentOffset: CGPoint) -> Void)?
  32. /// 空白页面点击的回调
  33. var emptyRefreshHandle: ((_ msgType: material_msgType) -> Void)?
  34. var msgType: material_msgType = .all { // 默认类型
  35. didSet {
  36. photoData.removeAll()
  37. allPhotos = PHFetchResult<PHAsset>.init()
  38. photoCollectionView.setContentOffset(CGPoint.zero, animated: false)
  39. photoCollectionView.reloadData()
  40. loadLocalData()
  41. }
  42. }
  43. var assetCollection: PHAssetCollection? // 相簿
  44. lazy var imageManager: PHCachingImageManager = { // 图库缓存管理
  45. (PHCachingImageManager.default() as? PHCachingImageManager) ?? PHCachingImageManager()
  46. }()
  47. lazy var photoFlowLayout: UICollectionViewFlowLayout = {
  48. let photoFlowLayout = UICollectionViewFlowLayout()
  49. photoFlowLayout.sectionInset = UIEdgeInsets.zero
  50. photoFlowLayout.minimumLineSpacing = itemSpacing
  51. photoFlowLayout.minimumInteritemSpacing = itemSpacing
  52. photoFlowLayout.scrollDirection = .vertical
  53. photoFlowLayout.itemSize = itemSize
  54. return photoFlowLayout
  55. }()
  56. lazy var photoCollectionView: UICollectionView = {
  57. let photoCollectionView = UICollectionView(frame: CGRect(x: itemSpacing, y: 0, width: view.frame.width, height: view.frame.height), collectionViewLayout: photoFlowLayout)
  58. photoCollectionView.register(PQChoseMaterialCell.self, forCellWithReuseIdentifier: String(describing: PQChoseMaterialCell.self))
  59. photoCollectionView.showsVerticalScrollIndicator = false
  60. photoCollectionView.showsHorizontalScrollIndicator = false
  61. photoCollectionView.delegate = self
  62. photoCollectionView.dataSource = self
  63. photoCollectionView.backgroundColor = UIColor.clear
  64. if #available(iOS 11.0, *) {
  65. photoCollectionView.contentInsetAdjustmentBehavior = .never
  66. } else {
  67. automaticallyAdjustsScrollViewInsets = false
  68. }
  69. return photoCollectionView
  70. }()
  71. var anthorEmptyData: PQEmptyModel = {
  72. let anthorEmptyData = PQEmptyModel()
  73. anthorEmptyData.title = "挑选相册素材"
  74. anthorEmptyData.summary = "要挑选相册素材,请先授予相册使用权限"
  75. anthorEmptyData.emptyImage = "icon_authorError"
  76. anthorEmptyData.isRefreshHidden = false
  77. anthorEmptyData.refreshBgColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue)
  78. return anthorEmptyData
  79. }()
  80. var emptyData: PQEmptyModel = {
  81. let emptyData = PQEmptyModel()
  82. emptyData.title = "此相册中什么都没有"
  83. emptyData.isRefreshHidden = false
  84. emptyData.refreshTitle = NSMutableAttributedString(string: "刷新")
  85. emptyData.refreshBgColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue)
  86. emptyData.emptyImage = "material_empty"
  87. return emptyData
  88. }()
  89. lazy var emptyRemindView: PQEmptyRemindView = {
  90. let remindView = PQEmptyRemindView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height))
  91. remindView.isHidden = true
  92. photoCollectionView.addSubview(remindView)
  93. remindView.backgroundColor = PQBFConfig.shared.styleBackGroundColor
  94. remindView.fullRefreshBloc = { [weak self] _, _ in
  95. if self?.emptyRemindView.refreshBtn.currentTitle == "授予权限" {
  96. openAppSetting()
  97. } else if self?.emptyRemindView.refreshBtn.currentAttributedTitle == NSMutableAttributedString(string: "刷新") {
  98. self?.loadLocalData()
  99. } else if self?.emptyRefreshHandle != nil {
  100. self?.emptyRefreshHandle!(self?.msgType ?? .all)
  101. }
  102. }
  103. remindView.remindLab.textColor = UIColor.hexColor(hexadecimal: "#999999")
  104. remindView.refreshBtn.addCorner(corner: 4)
  105. return remindView
  106. }()
  107. override func viewDidLoad() {
  108. super.viewDidLoad()
  109. view.addSubview(photoCollectionView)
  110. hiddenNavigation()
  111. view.backgroundColor = PQBFConfig.shared.styleBackGroundColor
  112. }
  113. /// <#Description#>
  114. /// - Returns: <#description#>
  115. func loadLocalData() {
  116. let authStatus = PHPhotoLibrary.authorizationStatus()
  117. if authStatus == .notDetermined {
  118. // 第一次触发授权 alert
  119. PHPhotoLibrary.requestAuthorization { [weak self] (status: PHAuthorizationStatus) -> Void in
  120. if status == .authorized {
  121. if (self?.allPhotos == nil) || (self?.allPhotos.count ?? 0) <= 0 {
  122. self?.loadPhotoData()
  123. }
  124. }
  125. DispatchQueue.main.async { [weak self] in
  126. self?.showEmpthView()
  127. }
  128. }
  129. } else if authStatus == .authorized {
  130. BFLog(message: "授权成功,开始请求相册数据-\(allPhotos)")
  131. // if allPhotos.count <= 0 {
  132. loadPhotoData()
  133. // }
  134. } else {
  135. showEmpthView()
  136. }
  137. PHPhotoLibrary.shared().register(self)
  138. }
  139. /// 加载图库数据
  140. /// - Returns: <#description#>
  141. func loadPhotoData() {
  142. DispatchQueue.main.async { [weak self] in
  143. PQLoadingHUB.shared.showHUB(superView: self!.view, isVerticality: false)
  144. }
  145. DispatchQueue.global().async { [weak self] in
  146. self?.allPhotos = self?.assetCollection != nil ? PHAsset.fetchAssets(in: (self?.assetCollection)!, options: self?.fetchOptions()) : PHAsset.fetchAssets(with: self?.fetchOptions())
  147. DispatchQueue.main.async { [weak self] in
  148. if self?.view != nil {
  149. PQLoadingHUB.shared.dismissHUB(superView: self!.view)
  150. }
  151. self?.photoCollectionView.reloadData()
  152. self?.showEmpthView()
  153. // self?.updateCachedAssets()
  154. }
  155. }
  156. }
  157. /// 获取option
  158. /// - Returns: <#description#>
  159. func fetchOptions() -> PHFetchOptions {
  160. if msgType != .all {
  161. let fetchOptions = PHFetchOptions()
  162. fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
  163. fetchOptions.predicate = NSPredicate(format: "mediaType = %d", msgType == .video ? PHAssetMediaType.video.rawValue : PHAssetMediaType.image.rawValue)
  164. return fetchOptions
  165. } else {
  166. return creaFetchOptions
  167. }
  168. }
  169. /// 展示空页面
  170. func showEmpthView() {
  171. emptyRemindView.isHidden = allPhotos.count > 0
  172. if !emptyRemindView.isHidden {
  173. let authStatus = PHPhotoLibrary.authorizationStatus()
  174. if authStatus == .denied {
  175. emptyRemindView.emptyData = anthorEmptyData
  176. emptyRemindView.refreshBtn.setTitle("授予权限", for: .normal)
  177. } else {
  178. switch msgType {
  179. case .video:
  180. emptyData.title = "此相册中没有视频"
  181. emptyData.emptyImage = "stuckPoint_video_empty"
  182. emptyData.refreshTitle = NSMutableAttributedString(string: "去选照片")
  183. case .image:
  184. emptyData.title = "此相册中没有照片"
  185. emptyData.emptyImage = "stuckPoint_image_empty"
  186. emptyData.refreshTitle = NSMutableAttributedString(string: "去选视频")
  187. default:
  188. emptyData.title = "此相册中什么都没有"
  189. emptyData.emptyImage = "material_empty"
  190. emptyData.refreshTitle = NSMutableAttributedString(string: "刷新")
  191. }
  192. emptyData.summary = nil
  193. emptyRemindView.emptyData = emptyData
  194. }
  195. }
  196. }
  197. /// 更新frame
  198. /// - Parameter newFrame: <#newFrame description#>
  199. /// - Returns: <#description#>
  200. func updateFrame(newFrame: CGRect, isAnimate: Bool = false, isScroll _: Bool = false) {
  201. if isAnimate {
  202. UIView.animate(withDuration: 0.5, delay: 0, options: .allowUserInteraction) { [weak self] in
  203. // 调整位置
  204. self?.view.frame = newFrame
  205. self?.photoCollectionView.frame = self?.view.bounds ?? CGRect.zero
  206. self?.emptyRemindView.frame = self?.photoCollectionView.bounds ?? CGRect.zero
  207. } completion: { _ in
  208. }
  209. } else {
  210. view.frame = newFrame
  211. photoCollectionView.frame = view.bounds
  212. emptyRemindView.frame = photoCollectionView.bounds
  213. }
  214. }
  215. }
  216. extension PQPhotoMaterialController: UICollectionViewDelegate, UICollectionViewDataSource, UIScrollViewDelegate {
  217. func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int {
  218. return allPhotos.count
  219. }
  220. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  221. let cell = PQChoseMaterialCell.choseMaterialCell(collectionView: collectionView, indexPath: indexPath)
  222. cell.isShowMediaTag = isShowMediaTag
  223. cell.isAdded = isAdded
  224. if photoData.count <= indexPath.item {
  225. let itemData: PQEditVisionTrackMaterialsModel!
  226. let asset = allPhotos.object(at: indexPath.item)
  227. let selectedItem = selectedPhotoData.first { (item) -> Bool in
  228. item.asset == asset
  229. }
  230. if selectedItem != nil {
  231. itemData = selectedItem
  232. } else {
  233. itemData = PQEditVisionTrackMaterialsModel()
  234. itemData.downloadState = .compelte
  235. itemData.asset = asset
  236. itemData.height = Float(itemData.asset?.pixelHeight ?? 0)
  237. itemData.width = Float(itemData.asset?.pixelWidth ?? 0)
  238. itemData.type = asset.mediaType == .image ? StickerType.IMAGE.rawValue : StickerType.VIDEO.rawValue
  239. itemData.volumeGain = asset.mediaType == .image ? 0 : 100
  240. itemData.duration = asset.mediaType == .image ? imageDuration : asset.duration
  241. itemData.out = itemData.duration
  242. }
  243. photoData.append(itemData)
  244. }
  245. if photoData.count > indexPath.item {
  246. let itemData = photoData[indexPath.item]
  247. cell.materialData = itemData
  248. cell.representedAssetIdentifier = itemData.asset?.localIdentifier
  249. if itemData.coverImageUI == nil, itemData.asset != nil {
  250. imageManager.requestImage(for: itemData.asset!, targetSize: itemSize, contentMode: .aspectFill, options: nil) { image, info in
  251. if info?.keys.contains("PHImageResultIsDegradedKey") ?? false, "\(info?["PHImageResultIsDegradedKey"] ?? "0")" == "0", cell.representedAssetIdentifier == itemData.asset?.localIdentifier {
  252. if image != nil {
  253. itemData.coverImageUI = image
  254. cell.materialImageView.image = image
  255. } else if image == nil, info?.keys.contains("PHImageResultIsInCloudKey") ?? false {
  256. let option = PHImageRequestOptions()
  257. option.isNetworkAccessAllowed = true
  258. option.resizeMode = .fast
  259. self.imageManager.requestImageData(for: itemData.asset!, options: option) { data, _, _, _ in
  260. if data != nil {
  261. let image = UIImage(data: data!)
  262. itemData.coverImageUI = image
  263. cell.materialImageView.image = image
  264. }
  265. }
  266. }
  267. }
  268. }
  269. }
  270. } else {
  271. cell.materialData = PQEditVisionTrackMaterialsModel()
  272. }
  273. cell.materialClicHandle = { [weak self] _, materialData in
  274. if (self?.photoData.count ?? 0) > indexPath.item {
  275. // 处理相册选择
  276. self?.dealWithSelectedMaterial(indexPath: indexPath, materialData: materialData)
  277. } else {
  278. // collectionView.reloadData()
  279. }
  280. }
  281. return cell
  282. }
  283. func collectionView(_: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  284. if photoData.count <= indexPath.item {
  285. // collectionView.reloadData()
  286. return
  287. }
  288. if detailMaterialHandle != nil {
  289. detailMaterialHandle!(indexPath, photoData[indexPath.item])
  290. }
  291. }
  292. func collectionView(_: UICollectionView, willDisplay _: UICollectionViewCell, forItemAt _: IndexPath) {
  293. if !isAdded, !isShowMediaTag {
  294. // // 卡点音乐素材选择曝光上报
  295. // PQEventTrackViewModel.baseReportUpload(businessType: .bt_windowView, objectType: .ot_view_selectSyncedUpMaterial, pageSource: .sp_stuck_selectMaterial, extParams: nil, remindmsg: "卡点音乐素材选择曝光上报")
  296. }
  297. }
  298. /// 点击选择的回调
  299. /// - Parameter materialData: <#materialData description#>
  300. /// - Returns: description
  301. func dealWithSelectedMaterial(indexPath: IndexPath?, materialData: PQEditVisionTrackMaterialsModel?) {
  302. let ratio = (materialData?.width ?? 0) / (materialData?.height ?? 1)
  303. if ratio < 0.4 || ratio > 4.2 {
  304. cShowHUB(superView: nil, msg: "暂不支持该比例的素材")
  305. return
  306. }
  307. //add by ak
  308. if materialData?.asset?.mediaType == .video && ((materialData?.asset?.pixelWidth ?? 0) > 3000 || (materialData?.asset?.pixelHeight ?? 0) > 3000) {
  309. cShowHUB(superView: nil, msg: "暂不支持该类型素材")
  310. return
  311. }
  312. if materialData != nil, indexPath != nil {
  313. materialData?.isSelected = !(materialData?.isSelected ?? false)
  314. if materialData?.isSelected ?? false {
  315. selectedPhotoData.append(materialData!)
  316. if materialData?.type == StickerType.IMAGE.rawValue {
  317. selectedImageDataCount = selectedImageDataCount + 1
  318. }
  319. materialData?.selectedIndex = selectedPhotoData.count
  320. if materialData?.asset != nil && materialData?.asset?.mediaType != .video && (materialData?.coverImageUI == nil || (materialData?.locationPath.count ?? 0) <= 0) {
  321. PQPHAssetVideoParaseUtil.requestAssetOringinImage(asset: (materialData?.asset)!) { [weak self] _, data, image, _ in
  322. if image == nil {
  323. BFLog(message: "图片数据为空!!!!!")
  324. }
  325. // 1,图片,gif的原文件数据,和视频的封面
  326. var newImage: UIImage?
  327. if image != nil {
  328. newImage = UIImage.nx_fixOrientation(image, isFront: false).nx_scaleWithMaxLength(maxLength: 1920)
  329. }
  330. materialData?.coverImageUI = newImage
  331. materialData?.originalData = data
  332. let timeInterval: TimeInterval = Date().timeIntervalSince1970
  333. let imageCacheName = "images_\(timeInterval)"
  334. let imageCachePath = downloadImagesDirectory + imageCacheName
  335. if !directoryIsExists(dicPath: downloadImagesDirectory) {
  336. BFLog(message: "文件夹不存在 \(downloadImagesDirectory)")
  337. createDirectory(path: downloadImagesDirectory)
  338. }
  339. // 创建目录
  340. if newImage != nil {
  341. try? newImage?.pngData()?.write(to: URL(fileURLWithPath: imageCachePath))
  342. } else {
  343. try? data?.write(to: URL(fileURLWithPath: imageCachePath))
  344. }
  345. if materialData?.asset?.mediaType != .video, newImage != nil || data != nil {
  346. materialData?.locationPath = imageCachePath.replacingOccurrences(of: documensDirectory, with: "")
  347. }
  348. materialData?.width = Float(materialData?.asset?.pixelWidth ?? 0)
  349. materialData?.height = Float(materialData?.asset?.pixelHeight ?? 0)
  350. if self?.selectedMaterialHandle != nil {
  351. self?.selectedTotalDuration = (self?.selectedTotalDuration ?? 0) + (materialData?.duration ?? 0.0)
  352. if indexPath != nil {
  353. self?.photoCollectionView.reloadItems(at: [indexPath!])
  354. }
  355. // 处理已选择的数据
  356. self?.dealWithSelectedData(materialData: materialData)
  357. }
  358. }
  359. } else {
  360. selectedTotalDuration = selectedTotalDuration + (materialData?.duration ?? 0.0)
  361. if indexPath != nil {
  362. photoCollectionView.reloadItems(at: [indexPath!])
  363. }
  364. // 处理已选择的数据
  365. dealWithSelectedData(materialData: materialData)
  366. }
  367. } else {
  368. // 处理取消选择数据
  369. deSeletedMaterialData(materialData: materialData)
  370. }
  371. }
  372. }
  373. /// 处理取消选择数据
  374. /// - Parameter materialData: <#materialData description#>
  375. /// - Returns: <#description#>
  376. func deSeletedMaterialData(materialData: PQEditVisionTrackMaterialsModel?) {
  377. selectedPhotoData.removeAll(where: { (item) -> Bool in
  378. item.asset == materialData?.asset
  379. })
  380. for item in photoData {
  381. if item.isSelected, item.selectedIndex > (materialData?.selectedIndex ?? 1) {
  382. item.selectedIndex = item.selectedIndex - 1
  383. }
  384. }
  385. if materialData?.type == StickerType.IMAGE.rawValue {
  386. selectedImageDataCount = selectedImageDataCount - 1
  387. }
  388. selectedTotalDuration = selectedTotalDuration - (materialData?.duration ?? 0.0)
  389. photoCollectionView.reloadData()
  390. // 处理已选择的数据
  391. dealWithSelectedData(materialData: materialData)
  392. }
  393. /// 处理已选择的数据
  394. /// - Parameters:
  395. /// - indexPath: <#indexPath description#>
  396. /// - materialData: <#materialData description#>
  397. /// - Returns: <#description#>
  398. func dealWithSelectedData(materialData: PQEditVisionTrackMaterialsModel?) {
  399. // if materialData?.asset != nil && materialData?.asset?.mediaType == .video && (materialData?.locationPath == nil || (materialData?.locationPath.count ?? 0) <= 0) {
  400. // PQPHAssetVideoParaseUtil.parasToAVAsset(phAsset: (materialData?.asset)!) { [weak self] avAsset, _, _, _ in
  401. // if avAsset is AVURLAsset {
  402. // materialData?.locationPath = (avAsset as? AVURLAsset)?.url.absoluteString.replacingOccurrences(of: "file:///", with: "") ?? ""
  403. // if self?.selectedMaterialHandle != nil {
  404. // DispatchQueue.main.async {
  405. // self?.selectedMaterialHandle!(materialData, self?.selectedPhotoData ?? [], self?.selectedTotalDuration ?? 0)
  406. // }
  407. // }
  408. // } else if self?.selectedMaterialHandle != nil {
  409. // DispatchQueue.main.async {
  410. // self?.selectedMaterialHandle!(materialData, self?.selectedPhotoData ?? [], self?.selectedTotalDuration ?? 0)
  411. // }
  412. // }
  413. // }
  414. // } else
  415. if selectedMaterialHandle != nil {
  416. selectedMaterialHandle!(materialData, selectedPhotoData, selectedTotalDuration, selectedImageDataCount)
  417. }
  418. }
  419. /// <#Description#>
  420. /// - Parameter scrollView: <#scrollView description#>
  421. func scrollViewDidScroll(_ scrollView: UIScrollView) {
  422. if scrollViewDidScroll != nil {
  423. scrollViewDidScroll!(scrollView.contentOffset)
  424. }
  425. }
  426. /// 更新数据
  427. /// - Parameter isMaterialSelected:
  428. /// - Parameter materialData: <#materialData description#>
  429. /// - Returns: <#description#>
  430. func updateMaterials(isSelected: Bool, materialData: PQEditVisionTrackMaterialsModel?) {
  431. let photoIndexPath = selectedPhotoData.firstIndex { (item) -> Bool in
  432. materialData?.asset == item.asset
  433. }
  434. materialData?.isSelected = isSelected
  435. if isSelected {
  436. if photoIndexPath == nil {
  437. selectedPhotoData.append(materialData!)
  438. }
  439. materialData?.selectedIndex = selectedPhotoData.count
  440. selectedTotalDuration = selectedTotalDuration + (materialData?.duration ?? 0.0)
  441. } else if photoIndexPath != nil {
  442. selectedPhotoData.remove(at: photoIndexPath ?? 0)
  443. for item in selectedPhotoData {
  444. if item.isSelected, item.selectedIndex > (materialData?.selectedIndex ?? 1) {
  445. item.selectedIndex = item.selectedIndex - 1
  446. }
  447. }
  448. selectedTotalDuration = selectedTotalDuration - (materialData?.duration ?? 0.0)
  449. }
  450. photoCollectionView.reloadData()
  451. if selectedMaterialHandle != nil {
  452. selectedMaterialHandle!(materialData, selectedPhotoData, selectedTotalDuration, selectedImageDataCount)
  453. }
  454. }
  455. }
  456. extension PQPhotoMaterialController: PHPhotoLibraryChangeObserver {
  457. func photoLibraryDidChange(_ changeInstance: PHChange) {
  458. DispatchQueue.main.sync {
  459. // Check each of the three top-level fetches for changes.
  460. guard let collectionChanges = changeInstance.changeDetails(for: allPhotos)
  461. else {
  462. return
  463. }
  464. if !collectionChanges.hasIncrementalChanges || collectionChanges.hasMoves {
  465. return
  466. }
  467. // Update the cached fetch result.
  468. allPhotos = collectionChanges.fetchResultAfterChanges
  469. photoData.removeAll()
  470. photoCollectionView.reloadData()
  471. // photoCollectionView.setContentOffset(CGPoint.zero, animated: false)
  472. }
  473. }
  474. private func resetCachedAssets() {
  475. imageManager.stopCachingImagesForAllAssets()
  476. previousPreheatRect = .zero
  477. }
  478. private func updateCachedAssets() {
  479. if allPhotos.count <= 0 {
  480. return
  481. }
  482. guard isViewLoaded, view.window != nil else { return }
  483. let visibleRect = CGRect(origin: photoCollectionView.contentOffset, size: photoCollectionView.bounds.size)
  484. let preheatRect = visibleRect.insetBy(dx: 0, dy: -0.5 * visibleRect.height)
  485. let delta = abs(preheatRect.midY - previousPreheatRect.midY)
  486. guard delta > view.bounds.height / 3 else { return }
  487. let (addedRects, removedRects) = differencesBetweenRects(previousPreheatRect, preheatRect)
  488. let addedAssets: [PHAsset]? = addedRects
  489. .flatMap { rect in photoCollectionView.indexPathsForElements(in: rect) }
  490. .map { indexPath in
  491. if indexPath.item < allPhotos.count {
  492. return allPhotos.object(at: indexPath.item)
  493. } else {
  494. return PHAsset()
  495. }
  496. }
  497. let removedAssets: [PHAsset]? = removedRects
  498. .flatMap { rect in photoCollectionView.indexPathsForElements(in: rect) }
  499. .map { indexPath in
  500. if indexPath.item < allPhotos.count {
  501. return allPhotos.object(at: indexPath.item)
  502. } else {
  503. return PHAsset()
  504. }
  505. }
  506. if addedAssets != nil, (addedAssets?.count ?? 0) > 0 {
  507. imageManager.startCachingImages(for: addedAssets!,
  508. targetSize: itemSize, contentMode: .aspectFill, options: nil)
  509. }
  510. if removedAssets != nil, (removedAssets?.count ?? 0) > 0 {
  511. imageManager.stopCachingImages(for: removedAssets!,
  512. targetSize: itemSize, contentMode: .aspectFill, options: nil)
  513. }
  514. previousPreheatRect = preheatRect
  515. }
  516. private func differencesBetweenRects(_ old: CGRect, _ new: CGRect) -> (added: [CGRect], removed: [CGRect]) {
  517. if old.intersects(new) {
  518. var added = [CGRect]()
  519. if new.maxY > old.maxY {
  520. added += [CGRect(x: new.origin.x, y: old.maxY,
  521. width: new.width, height: new.maxY - old.maxY)]
  522. }
  523. if old.minY > new.minY {
  524. added += [CGRect(x: new.origin.x, y: new.minY,
  525. width: new.width, height: old.minY - new.minY)]
  526. }
  527. var removed = [CGRect]()
  528. if new.maxY < old.maxY {
  529. removed += [CGRect(x: new.origin.x, y: new.maxY,
  530. width: new.width, height: old.maxY - new.maxY)]
  531. }
  532. if old.minY < new.minY {
  533. removed += [CGRect(x: new.origin.x, y: old.minY,
  534. width: new.width, height: new.minY - old.minY)]
  535. }
  536. return (added, removed)
  537. } else {
  538. return ([new], [old])
  539. }
  540. }
  541. }