TLPhotosPickerViewController.swift 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169
  1. //
  2. // TLPhotosPickerViewController.swift
  3. // TLPhotosPicker
  4. //
  5. // Created by wade.hawk on 2017. 4. 14..
  6. // Copyright © 2017년 wade.hawk. All rights reserved.
  7. //
  8. import UIKit
  9. import Photos
  10. import PhotosUI
  11. import MobileCoreServices
  12. import SwiftyMediator
  13. public protocol TLPhotosPickerViewControllerDelegate: class {
  14. func dismissPhotoPicker(withPHAssets: [PHAsset])
  15. func dismissPhotoPicker(withTLPHAssets: [TLPHAsset])
  16. func dismissComplete()
  17. func photoPickerDidCancel()
  18. func canSelectAsset(phAsset: PHAsset) -> Bool
  19. func didExceedMaximumNumberOfSelection(picker: TLPhotosPickerViewController)
  20. func handleNoAlbumPermissions(picker: TLPhotosPickerViewController)
  21. func handleNoCameraPermissions(picker: TLPhotosPickerViewController)
  22. }
  23. extension TLPhotosPickerViewControllerDelegate {
  24. public func deninedAuthoization() { }
  25. public func dismissPhotoPicker(withPHAssets: [PHAsset]) { }
  26. public func dismissPhotoPicker(withTLPHAssets: [TLPHAsset]) { }
  27. public func dismissComplete() { }
  28. public func photoPickerDidCancel() { }
  29. public func canSelectAsset(phAsset: PHAsset) -> Bool { return true }
  30. public func didExceedMaximumNumberOfSelection(picker: TLPhotosPickerViewController) { }
  31. public func handleNoAlbumPermissions(picker: TLPhotosPickerViewController) { }
  32. public func handleNoCameraPermissions(picker: TLPhotosPickerViewController) { }
  33. }
  34. //for log
  35. public protocol TLPhotosPickerLogDelegate: class {
  36. func selectedCameraCell(picker: TLPhotosPickerViewController)
  37. func deselectedPhoto(picker: TLPhotosPickerViewController, at: Int)
  38. func selectedPhoto(picker: TLPhotosPickerViewController, at: Int)
  39. func selectedAlbum(picker: TLPhotosPickerViewController, title: String, at: Int)
  40. }
  41. extension TLPhotosPickerLogDelegate {
  42. func selectedCameraCell(picker: TLPhotosPickerViewController) { }
  43. func deselectedPhoto(picker: TLPhotosPickerViewController, at: Int) { }
  44. func selectedPhoto(picker: TLPhotosPickerViewController, at: Int) { }
  45. func selectedAlbum(picker: TLPhotosPickerViewController, collections: [TLAssetsCollection], at: Int) { }
  46. }
  47. public struct TLPhotosPickerConfigure {
  48. public var customLocalizedTitle: [String: String] = ["Camera Roll": "Camera Roll"]
  49. public var tapHereToChange = "Tap here to change"
  50. public var cancelTitle = "Cancel"
  51. public var doneTitle = "Done"
  52. public var emptyMessage = "No albums"
  53. public var emptyImage: UIImage? = nil
  54. public var usedCameraButton = true
  55. public var usedPrefetch = false
  56. public var allowedLivePhotos = true
  57. public var allowedVideo = true
  58. public var allowedAlbumCloudShared = false
  59. public var allowedVideoRecording = true
  60. public var recordingVideoQuality: UIImagePickerController.QualityType = .typeMedium
  61. public var maxVideoDuration:TimeInterval? = nil
  62. public var autoPlay = true
  63. public var muteAudio = true
  64. public var mediaType: PHAssetMediaType? = nil
  65. public var numberOfColumn = 3
  66. public var singleSelectedMode = false
  67. public var maxSelectedAssets: Int? = nil
  68. public var fetchOption: PHFetchOptions? = nil
  69. public var fetchCollectionOption: [FetchCollectionType: PHFetchOptions] = [:]
  70. public var selectedColor = UIColor(red: 88/255, green: 144/255, blue: 255/255, alpha: 1.0)
  71. public var cameraBgColor = UIColor(red: 221/255, green: 223/255, blue: 226/255, alpha: 1)
  72. public var cameraIcon = TLBundle.podBundleImage(named: "camera")
  73. public var videoIcon = TLBundle.podBundleImage(named: "video")
  74. public var placeholderIcon = TLBundle.podBundleImage(named: "insertPhotoMaterial")
  75. public var nibSet: (nibName: String, bundle:Bundle)? = nil
  76. public var cameraCellNibSet: (nibName: String, bundle:Bundle)? = nil
  77. public var fetchCollectionTypes: [(PHAssetCollectionType,PHAssetCollectionSubtype)]? = nil
  78. public var groupByFetch: PHFetchedResultGroupedBy? = nil
  79. public var supportedInterfaceOrientations: UIInterfaceOrientationMask = .portrait
  80. public var popup: [PopupConfigure] = []
  81. public var isPreview : Bool = false //是否预览
  82. public var isAllPreview : Bool = false //是否预览
  83. public init() {
  84. }
  85. }
  86. public enum PopupConfigure {
  87. case animation(TimeInterval)
  88. }
  89. public struct Platform {
  90. public static var isSimulator: Bool {
  91. return TARGET_OS_SIMULATOR != 0 // Use this line in Xcode 7 or newer
  92. }
  93. }
  94. open class TLPhotosPickerViewController: UIViewController {
  95. @IBOutlet open var navigationBar: UINavigationBar!
  96. @IBOutlet open var titleView: UIView!
  97. @IBOutlet open var titleLabel: UILabel!
  98. @IBOutlet open var subTitleStackView: UIStackView!
  99. @IBOutlet open var subTitleLabel: UILabel!
  100. @IBOutlet open var subTitleArrowImageView: UIImageView!
  101. @IBOutlet open var albumPopView: TLAlbumPopView!
  102. @IBOutlet open var collectionView: UICollectionView!
  103. @IBOutlet open var indicator: UIActivityIndicatorView!
  104. @IBOutlet open var popArrowImageView: UIImageView!
  105. @IBOutlet open var customNavItem: UINavigationItem!
  106. @IBOutlet open var doneButton: UIBarButtonItem!
  107. @IBOutlet open var cancelButton: UIBarButtonItem!
  108. @IBOutlet open var navigationBarTopConstraint: NSLayoutConstraint!
  109. @IBOutlet open var emptyView: UIView!
  110. @IBOutlet open var emptyImageView: UIImageView!
  111. @IBOutlet open var emptyMessageLabel: UILabel!
  112. public weak var delegate: TLPhotosPickerViewControllerDelegate? = nil
  113. public weak var logDelegate: TLPhotosPickerLogDelegate? = nil
  114. open var selectedAssets = [TLPHAsset]()
  115. public var configure = TLPhotosPickerConfigure()
  116. public var customDataSouces: TLPhotopickerDataSourcesProtocol? = nil
  117. private var usedCameraButton: Bool {
  118. get {
  119. return self.configure.usedCameraButton
  120. }
  121. }
  122. private var allowedVideo: Bool {
  123. get {
  124. return self.configure.allowedVideo
  125. }
  126. }
  127. private var usedPrefetch: Bool {
  128. get {
  129. return self.configure.usedPrefetch
  130. }
  131. set {
  132. self.configure.usedPrefetch = newValue
  133. }
  134. }
  135. private var allowedLivePhotos: Bool {
  136. get {
  137. return self.configure.allowedLivePhotos
  138. }
  139. set {
  140. self.configure.allowedLivePhotos = newValue
  141. }
  142. }
  143. @objc open var canSelectAsset: ((PHAsset) -> Bool)? = nil
  144. @objc open var didExceedMaximumNumberOfSelection: ((TLPhotosPickerViewController) -> Void)? = nil
  145. @objc open var handleNoAlbumPermissions: ((TLPhotosPickerViewController) -> Void)? = nil
  146. @objc open var handleNoCameraPermissions: ((TLPhotosPickerViewController) -> Void)? = nil
  147. @objc open var dismissCompletion: (() -> Void)? = nil
  148. private var completionWithPHAssets: (([PHAsset]) -> Void)? = nil
  149. private var completionWithTLPHAssets: (([TLPHAsset]) -> Void)? = nil
  150. private var didCancel: (() -> Void)? = nil
  151. private var collections = [TLAssetsCollection]()
  152. private var focusedCollection: TLAssetsCollection? = nil
  153. private var requestIDs = SynchronizedDictionary<IndexPath,PHImageRequestID>()
  154. private var playRequestID: (indexPath: IndexPath, requestID: PHImageRequestID)? = nil
  155. private var photoLibrary = TLPhotoLibrary()
  156. private var queue = DispatchQueue(label: "tilltue.photos.pikcker.queue")
  157. private var queueForGroupedBy = DispatchQueue(label: "tilltue.photos.pikcker.queue.for.groupedBy", qos: .utility)
  158. private var thumbnailSize = CGSize.zero
  159. private var placeholderThumbnail: UIImage? = nil
  160. private var cameraImage: UIImage? = nil
  161. deinit {
  162. //print("deinit TLPhotosPickerViewController")
  163. PHPhotoLibrary.shared().unregisterChangeObserver(self)
  164. }
  165. required public init?(coder aDecoder: NSCoder) {
  166. fatalError("init(coder:) has not been implemented")
  167. }
  168. public init() {
  169. super.init(nibName: "TLPhotosPickerViewController", bundle: TLBundle.bundle())
  170. }
  171. @objc convenience public init(withPHAssets: (([PHAsset]) -> Void)? = nil, didCancel: (() -> Void)? = nil) {
  172. self.init()
  173. self.completionWithPHAssets = withPHAssets
  174. self.didCancel = didCancel
  175. }
  176. convenience public init(withTLPHAssets: (([TLPHAsset]) -> Void)? = nil, didCancel: (() -> Void)? = nil) {
  177. self.init()
  178. self.completionWithTLPHAssets = withTLPHAssets
  179. self.didCancel = didCancel
  180. }
  181. override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
  182. return self.configure.supportedInterfaceOrientations
  183. }
  184. open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
  185. super.traitCollectionDidChange(previousTraitCollection)
  186. if #available(iOS 13.0, *) {
  187. let userInterfaceStyle = self.traitCollection.userInterfaceStyle
  188. let image = TLBundle.podBundleImage(named: "pop_arrow")
  189. if userInterfaceStyle.rawValue == 2 {
  190. self.popArrowImageView.image = image?.colorMask(color: .systemBackground)
  191. self.view.backgroundColor = .black
  192. self.collectionView.backgroundColor = .black
  193. }else {
  194. self.popArrowImageView.image = image?.colorMask(color: .white)
  195. self.view.backgroundColor = .white
  196. self.collectionView.backgroundColor = .white
  197. }
  198. }
  199. }
  200. override open func didReceiveMemoryWarning() {
  201. super.didReceiveMemoryWarning()
  202. self.stopPlay()
  203. }
  204. func checkAuthorization() {
  205. switch PHPhotoLibrary.authorizationStatus() {
  206. case .notDetermined:
  207. PHPhotoLibrary.requestAuthorization { [weak self] status in
  208. switch status {
  209. case .authorized:
  210. self?.initPhotoLibrary()
  211. default:
  212. self?.handleDeniedAlbumsAuthorization()
  213. }
  214. }
  215. case .authorized:
  216. self.initPhotoLibrary()
  217. case .restricted: fallthrough
  218. case .denied:
  219. handleDeniedAlbumsAuthorization()
  220. @unknown default:
  221. break
  222. }
  223. }
  224. override open func viewDidLoad() {
  225. super.viewDidLoad()
  226. makeUI()
  227. checkAuthorization()
  228. }
  229. override open func viewDidLayoutSubviews() {
  230. super.viewDidLayoutSubviews()
  231. if self.thumbnailSize == CGSize.zero {
  232. initItemSize()
  233. }
  234. if #available(iOS 11.0, *) {
  235. } else if self.navigationBarTopConstraint.constant == 0 {
  236. self.navigationBarTopConstraint.constant = 20
  237. }
  238. }
  239. override open func viewWillAppear(_ animated: Bool) {
  240. super.viewWillAppear(animated)
  241. if self.photoLibrary.delegate == nil {
  242. initPhotoLibrary()
  243. }
  244. }
  245. private func findIndexAndReloadCells(phAsset: PHAsset) {
  246. if
  247. var index = self.focusedCollection?.fetchResult?.index(of: phAsset),
  248. index != NSNotFound
  249. {
  250. index += (getfocusedIndex() == 0 && self.configure.usedCameraButton) ? 1 : 0
  251. self.collectionView.reloadItems(at: [IndexPath(row: index, section: 0)])
  252. }
  253. }
  254. open func deselectWhenUsingSingleSelectedMode() {
  255. if
  256. self.configure.singleSelectedMode == true,
  257. let selectedPHAsset = self.selectedAssets.first?.phAsset
  258. {
  259. self.selectedAssets.removeAll()
  260. findIndexAndReloadCells(phAsset: selectedPHAsset)
  261. }
  262. }
  263. open func maxCheck() -> Bool {
  264. deselectWhenUsingSingleSelectedMode()
  265. if let max = self.configure.maxSelectedAssets, max <= self.selectedAssets.count {
  266. self.delegate?.didExceedMaximumNumberOfSelection(picker: self)
  267. self.didExceedMaximumNumberOfSelection?(self)
  268. return true
  269. }
  270. return false
  271. }
  272. }
  273. // MARK: - UI & UI Action
  274. extension TLPhotosPickerViewController {
  275. @objc public func registerNib(nibName: String, bundle: Bundle) {
  276. self.collectionView.register(UINib(nibName: nibName, bundle: bundle), forCellWithReuseIdentifier: nibName)
  277. }
  278. private func centerAtRect(image: UIImage?, rect: CGRect, bgColor: UIColor = UIColor.white) -> UIImage? {
  279. guard let image = image else { return nil }
  280. UIGraphicsBeginImageContextWithOptions(rect.size, false, image.scale)
  281. bgColor.setFill()
  282. UIRectFill(CGRect(x: 0, y: 0, width: rect.size.width, height: rect.size.height))
  283. image.draw(in: CGRect(x:rect.size.width/2 - image.size.width/2, y:rect.size.height/2 - image.size.height/2, width:image.size.width, height:image.size.height))
  284. let result = UIGraphicsGetImageFromCurrentImageContext()
  285. UIGraphicsEndImageContext()
  286. return result
  287. }
  288. private func initItemSize() {
  289. guard let layout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
  290. return
  291. }
  292. let count = CGFloat(self.configure.numberOfColumn)
  293. let width = (self.view.frame.size.width-(1*(count-1)))/count
  294. self.thumbnailSize = CGSize(width: width, height: width)
  295. layout.itemSize = self.thumbnailSize
  296. self.collectionView.collectionViewLayout = layout
  297. self.placeholderThumbnail = centerAtRect(image: self.configure.placeholderIcon, rect: CGRect(x: 0, y: 0, width: width, height: width))
  298. self.cameraImage = centerAtRect(image: self.configure.cameraIcon, rect: CGRect(x: 0, y: 0, width: width, height: width), bgColor: self.configure.cameraBgColor)
  299. }
  300. @objc open func makeUI() {
  301. registerNib(nibName: "TLPhotoCollectionViewCell", bundle: TLBundle.bundle())
  302. if let nibSet = self.configure.nibSet {
  303. registerNib(nibName: nibSet.nibName, bundle: nibSet.bundle)
  304. }
  305. if let nibSet = self.configure.cameraCellNibSet {
  306. registerNib(nibName: nibSet.nibName, bundle: nibSet.bundle)
  307. }
  308. self.indicator.startAnimating()
  309. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(titleTap))
  310. self.titleView.addGestureRecognizer(tapGesture)
  311. self.titleLabel.text = self.configure.customLocalizedTitle["Camera Roll"]
  312. self.subTitleLabel.text = self.configure.tapHereToChange
  313. self.cancelButton.title = self.configure.cancelTitle
  314. self.doneButton.title = self.configure.doneTitle
  315. self.doneButton.setTitleTextAttributes([NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: UIFont.labelFontSize)], for: .normal)
  316. self.emptyView.isHidden = true
  317. self.emptyImageView.image = self.configure.emptyImage
  318. self.emptyMessageLabel.text = self.configure.emptyMessage
  319. self.albumPopView.tableView.delegate = self
  320. self.albumPopView.tableView.dataSource = self
  321. self.popArrowImageView.image = TLBundle.podBundleImage(named: "pop_arrow")
  322. self.subTitleArrowImageView.image = TLBundle.podBundleImage(named: "arrow")
  323. if #available(iOS 10.0, *), self.usedPrefetch {
  324. self.collectionView.isPrefetchingEnabled = true
  325. self.collectionView.prefetchDataSource = self
  326. } else {
  327. self.usedPrefetch = false
  328. }
  329. if #available(iOS 9.0, *), self.allowedLivePhotos {
  330. }else {
  331. self.allowedLivePhotos = false
  332. }
  333. self.customDataSouces?.registerSupplementView(collectionView: self.collectionView)
  334. }
  335. private func updateTitle() {
  336. guard self.focusedCollection != nil else { return }
  337. self.titleLabel.text = self.focusedCollection?.title
  338. }
  339. private func reloadCollectionView() {
  340. guard self.focusedCollection != nil else {
  341. return
  342. }
  343. if let groupedBy = self.configure.groupByFetch, self.usedPrefetch == false {
  344. queueForGroupedBy.async { [weak self] in
  345. self?.focusedCollection?.reloadSection(groupedBy: groupedBy)
  346. DispatchQueue.main.async {
  347. self?.collectionView.reloadData()
  348. }
  349. }
  350. }else {
  351. self.collectionView.reloadData()
  352. }
  353. }
  354. private func reloadTableView() {
  355. let count = min(5, self.collections.count)
  356. var frame = self.albumPopView.popupView.frame
  357. frame.size.height = CGFloat(count * 75)
  358. self.albumPopView.popupViewHeight.constant = CGFloat(count * 75)
  359. UIView.animate(withDuration: self.albumPopView.show ? 0.1:0) {
  360. self.albumPopView.popupView.frame = frame
  361. self.albumPopView.setNeedsLayout()
  362. }
  363. self.albumPopView.tableView.reloadData()
  364. self.albumPopView.setupPopupFrame()
  365. }
  366. private func initPhotoLibrary() {
  367. if PHPhotoLibrary.authorizationStatus() == .authorized {
  368. self.photoLibrary.delegate = self
  369. self.photoLibrary.fetchCollection(configure: self.configure)
  370. }else{
  371. //self.dismiss(animated: true, completion: nil)
  372. }
  373. }
  374. private func registerChangeObserver() {
  375. PHPhotoLibrary.shared().register(self)
  376. }
  377. private func getfocusedIndex() -> Int {
  378. guard let focused = self.focusedCollection, let result = self.collections.firstIndex(where: { $0 == focused }) else { return 0 }
  379. return result
  380. }
  381. private func getCollection(section: Int) -> PHAssetCollection? {
  382. guard section < self.collections.count else {
  383. return nil
  384. }
  385. return self.collections[section].phAssetCollection
  386. }
  387. private func focused(collection: TLAssetsCollection) {
  388. func resetRequest() {
  389. cancelAllImageAssets()
  390. }
  391. resetRequest()
  392. self.collections[getfocusedIndex()].recentPosition = self.collectionView.contentOffset
  393. var reloadIndexPaths = [IndexPath(row: getfocusedIndex(), section: 0)]
  394. self.focusedCollection = collection
  395. self.focusedCollection?.fetchResult = self.photoLibrary.fetchResult(collection: collection, configure: self.configure)
  396. reloadIndexPaths.append(IndexPath(row: getfocusedIndex(), section: 0))
  397. self.albumPopView.tableView.reloadRows(at: reloadIndexPaths, with: .none)
  398. self.albumPopView.show(false, duration: self.configure.popup.duration)
  399. self.updateTitle()
  400. self.reloadCollectionView()
  401. self.collectionView.contentOffset = collection.recentPosition
  402. }
  403. private func cancelAllImageAssets() {
  404. self.requestIDs.forEach{ (indexPath, requestID) in
  405. self.photoLibrary.cancelPHImageRequest(requestID: requestID)
  406. }
  407. self.requestIDs.removeAll()
  408. }
  409. // User Action
  410. @objc func titleTap() {
  411. guard collections.count > 0 else { return }
  412. self.albumPopView.show(self.albumPopView.isHidden, duration: self.configure.popup.duration)
  413. }
  414. @IBAction open func cancelButtonTap() {
  415. self.stopPlay()
  416. self.dismiss(done: false)
  417. }
  418. @IBAction open func doneButtonTap() {
  419. self.stopPlay()
  420. self.dismiss(done: true)
  421. }
  422. private func dismiss(done: Bool) {
  423. if done {
  424. #if swift(>=4.1)
  425. self.delegate?.dismissPhotoPicker(withPHAssets: self.selectedAssets.compactMap{ $0.phAsset })
  426. #else
  427. self.delegate?.dismissPhotoPicker(withPHAssets: self.selectedAssets.flatMap{ $0.phAsset })
  428. #endif
  429. self.delegate?.dismissPhotoPicker(withTLPHAssets: self.selectedAssets)
  430. self.completionWithTLPHAssets?(self.selectedAssets)
  431. #if swift(>=4.1)
  432. self.completionWithPHAssets?(self.selectedAssets.compactMap{ $0.phAsset })
  433. #else
  434. self.completionWithPHAssets?(self.selectedAssets.flatMap{ $0.phAsset })
  435. #endif
  436. }else {
  437. self.delegate?.photoPickerDidCancel()
  438. self.didCancel?()
  439. }
  440. self.dismiss(animated: true) { [weak self] in
  441. self?.delegate?.dismissComplete()
  442. self?.dismissCompletion?()
  443. }
  444. }
  445. private func canSelect(phAsset: PHAsset) -> Bool {
  446. if let closure = self.canSelectAsset {
  447. return closure(phAsset)
  448. }else if let delegate = self.delegate {
  449. return delegate.canSelectAsset(phAsset: phAsset)
  450. }
  451. return true
  452. }
  453. private func focusFirstCollection() {
  454. if self.focusedCollection == nil, let collection = self.collections.first {
  455. self.focusedCollection = collection
  456. self.updateTitle()
  457. self.reloadCollectionView()
  458. }
  459. }
  460. }
  461. // MARK: - TLPhotoLibraryDelegate
  462. extension TLPhotosPickerViewController: TLPhotoLibraryDelegate {
  463. func loadCameraRollCollection(collection: TLAssetsCollection) {
  464. self.collections = [collection]
  465. self.focusFirstCollection()
  466. self.indicator.stopAnimating()
  467. self.reloadTableView()
  468. }
  469. func loadCompleteAllCollection(collections: [TLAssetsCollection]) {
  470. self.collections = collections
  471. self.focusFirstCollection()
  472. let isEmpty = self.collections.count == 0
  473. self.subTitleStackView.isHidden = isEmpty
  474. self.emptyView.isHidden = !isEmpty
  475. self.emptyImageView.isHidden = self.emptyImageView.image == nil
  476. self.indicator.stopAnimating()
  477. self.reloadTableView()
  478. self.registerChangeObserver()
  479. }
  480. }
  481. // MARK: - Camera Picker
  482. extension TLPhotosPickerViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
  483. private func showCameraIfAuthorized() {
  484. let cameraAuthorization = AVCaptureDevice.authorizationStatus(for: .video)
  485. switch cameraAuthorization {
  486. case .authorized:
  487. self.showCamera()
  488. case .notDetermined:
  489. AVCaptureDevice.requestAccess(for: .video, completionHandler: { [weak self] (authorized) in
  490. DispatchQueue.main.async { [weak self] in
  491. if authorized {
  492. self?.showCamera()
  493. } else {
  494. self?.handleDeniedCameraAuthorization()
  495. }
  496. }
  497. })
  498. case .restricted, .denied:
  499. self.handleDeniedCameraAuthorization()
  500. @unknown default:
  501. break
  502. }
  503. }
  504. private func showCamera() {
  505. guard !maxCheck() else { return }
  506. let picker = UIImagePickerController()
  507. picker.sourceType = .camera
  508. picker.mediaTypes = [kUTTypeImage as String]
  509. if self.configure.allowedVideoRecording {
  510. picker.mediaTypes.append(kUTTypeMovie as String)
  511. picker.videoQuality = self.configure.recordingVideoQuality
  512. if let duration = self.configure.maxVideoDuration {
  513. picker.videoMaximumDuration = duration
  514. }
  515. }
  516. picker.allowsEditing = false
  517. picker.delegate = self
  518. self.present(picker, animated: true, completion: nil)
  519. }
  520. private func handleDeniedAlbumsAuthorization() {
  521. self.delegate?.handleNoAlbumPermissions(picker: self)
  522. self.handleNoAlbumPermissions?(self)
  523. }
  524. private func handleDeniedCameraAuthorization() {
  525. self.delegate?.handleNoCameraPermissions(picker: self)
  526. self.handleNoCameraPermissions?(self)
  527. }
  528. open func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
  529. picker.dismiss(animated: true, completion: nil)
  530. }
  531. open func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
  532. if let image = (info[.originalImage] as? UIImage) {
  533. var placeholderAsset: PHObjectPlaceholder? = nil
  534. PHPhotoLibrary.shared().performChanges({
  535. let newAssetRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
  536. placeholderAsset = newAssetRequest.placeholderForCreatedAsset
  537. }, completionHandler: { [weak self] (sucess, error) in
  538. if sucess, let `self` = self, let identifier = placeholderAsset?.localIdentifier {
  539. guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil).firstObject else { return }
  540. var result = TLPHAsset(asset: asset)
  541. result.selectedOrder = self.selectedAssets.count + 1
  542. result.isSelectedFromCamera = true
  543. self.selectedAssets.append(result)
  544. self.logDelegate?.selectedPhoto(picker: self, at: 1)
  545. }
  546. })
  547. }
  548. else if (info[.mediaType] as? String) == kUTTypeMovie as String {
  549. var placeholderAsset: PHObjectPlaceholder? = nil
  550. PHPhotoLibrary.shared().performChanges({
  551. let newAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: info[.mediaURL] as! URL)
  552. placeholderAsset = newAssetRequest?.placeholderForCreatedAsset
  553. }) { [weak self] (sucess, error) in
  554. if sucess, let `self` = self, let identifier = placeholderAsset?.localIdentifier {
  555. guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil).firstObject else { return }
  556. var result = TLPHAsset(asset: asset)
  557. result.selectedOrder = self.selectedAssets.count + 1
  558. result.isSelectedFromCamera = true
  559. self.selectedAssets.append(result)
  560. self.logDelegate?.selectedPhoto(picker: self, at: 1)
  561. }
  562. }
  563. }
  564. picker.dismiss(animated: true, completion: nil)
  565. }
  566. }
  567. // MARK: - UICollectionView Scroll Delegate
  568. extension TLPhotosPickerViewController {
  569. open func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  570. if !decelerate {
  571. videoCheck()
  572. }
  573. }
  574. open func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  575. videoCheck()
  576. }
  577. private func videoCheck() {
  578. func play(asset: (IndexPath,TLPHAsset)) {
  579. if self.playRequestID?.indexPath != asset.0 {
  580. playVideo(asset: asset.1, indexPath: asset.0)
  581. }
  582. }
  583. guard self.configure.autoPlay else { return }
  584. guard self.playRequestID == nil else { return }
  585. let visibleIndexPaths = self.collectionView.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row })
  586. #if swift(>=4.1)
  587. let boundAssets = visibleIndexPaths.compactMap{ indexPath -> (IndexPath,TLPHAsset)? in
  588. guard let asset = self.focusedCollection?.getTLAsset(at: indexPath), asset.phAsset?.mediaType == .video else { return nil }
  589. return (indexPath,asset)
  590. }
  591. #else
  592. let boundAssets = visibleIndexPaths.flatMap{ indexPath -> (IndexPath,TLPHAsset)? in
  593. guard let asset = self.focusedCollection?.getTLAsset(at: indexPath.row),asset.phAsset?.mediaType == .video else { return nil }
  594. return (indexPath,asset)
  595. }
  596. #endif
  597. if let firstSelectedVideoAsset = (boundAssets.filter{ getSelectedAssets($0.1) != nil }.first) {
  598. play(asset: firstSelectedVideoAsset)
  599. }else if let firstVideoAsset = boundAssets.first {
  600. play(asset: firstVideoAsset)
  601. }
  602. }
  603. }
  604. // MARK: - Video & LivePhotos Control PHLivePhotoViewDelegate
  605. extension TLPhotosPickerViewController: PHLivePhotoViewDelegate {
  606. private func stopPlay() {
  607. guard let playRequest = self.playRequestID else { return }
  608. self.playRequestID = nil
  609. guard let cell = self.collectionView.cellForItem(at: playRequest.indexPath) as? TLPhotoCollectionViewCell else { return }
  610. cell.stopPlay()
  611. }
  612. private func playVideo(asset: TLPHAsset, indexPath: IndexPath) {
  613. stopPlay()
  614. guard let phAsset = asset.phAsset else { return }
  615. if asset.type == .video {
  616. guard let cell = self.collectionView.cellForItem(at: indexPath) as? TLPhotoCollectionViewCell else { return }
  617. let requestID = self.photoLibrary.videoAsset(asset: phAsset, completionBlock: { (playerItem, info) in
  618. DispatchQueue.main.async { [weak self, weak cell] in
  619. guard let `self` = self, let cell = cell, cell.player == nil else { return }
  620. let player = AVPlayer(playerItem: playerItem)
  621. cell.player = player
  622. player.play()
  623. player.isMuted = self.configure.muteAudio
  624. }
  625. })
  626. if requestID > 0 {
  627. self.playRequestID = (indexPath,requestID)
  628. }
  629. }else if asset.type == .livePhoto && self.allowedLivePhotos {
  630. guard let cell = self.collectionView.cellForItem(at: indexPath) as? TLPhotoCollectionViewCell else { return }
  631. let requestID = self.photoLibrary.livePhotoAsset(asset: phAsset, size: self.thumbnailSize, completionBlock: { [weak cell] (livePhoto,complete) in
  632. cell?.livePhotoView?.isHidden = false
  633. cell?.livePhotoView?.livePhoto = livePhoto
  634. cell?.livePhotoView?.isMuted = true
  635. cell?.livePhotoView?.startPlayback(with: .hint)
  636. })
  637. if requestID > 0 {
  638. self.playRequestID = (indexPath,requestID)
  639. }
  640. }
  641. }
  642. public func livePhotoView(_ livePhotoView: PHLivePhotoView, didEndPlaybackWith playbackStyle: PHLivePhotoViewPlaybackStyle) {
  643. livePhotoView.isMuted = true
  644. livePhotoView.startPlayback(with: .hint)
  645. }
  646. public func livePhotoView(_ livePhotoView: PHLivePhotoView, willBeginPlaybackWith playbackStyle: PHLivePhotoViewPlaybackStyle) {
  647. }
  648. }
  649. // MARK: - PHPhotoLibraryChangeObserver
  650. extension TLPhotosPickerViewController: PHPhotoLibraryChangeObserver {
  651. public func photoLibraryDidChange(_ changeInstance: PHChange) {
  652. guard getfocusedIndex() == 0 else {
  653. return
  654. }
  655. let addIndex = self.usedCameraButton ? 1 : 0
  656. DispatchQueue.main.async {
  657. guard let changeFetchResult = self.focusedCollection?.fetchResult else { return }
  658. guard let changes = changeInstance.changeDetails(for: changeFetchResult) else { return }
  659. if changes.hasIncrementalChanges, self.configure.groupByFetch == nil {
  660. var deletedSelectedAssets = false
  661. var order = 0
  662. #if swift(>=4.1)
  663. self.selectedAssets = self.selectedAssets.enumerated().compactMap({ (offset,asset) -> TLPHAsset? in
  664. var asset = asset
  665. if let phAsset = asset.phAsset, changes.fetchResultAfterChanges.contains(phAsset) {
  666. order += 1
  667. asset.selectedOrder = order
  668. return asset
  669. }
  670. deletedSelectedAssets = true
  671. return nil
  672. })
  673. #else
  674. self.selectedAssets = self.selectedAssets.enumerated().flatMap({ (offset,asset) -> TLPHAsset? in
  675. var asset = asset
  676. if let phAsset = asset.phAsset, changes.fetchResultAfterChanges.contains(phAsset) {
  677. order += 1
  678. asset.selectedOrder = order
  679. return asset
  680. }
  681. deletedSelectedAssets = true
  682. return nil
  683. })
  684. #endif
  685. if deletedSelectedAssets {
  686. self.focusedCollection?.fetchResult = changes.fetchResultAfterChanges
  687. self.reloadCollectionView()
  688. }else {
  689. self.collectionView.performBatchUpdates({ [weak self] in
  690. guard let `self` = self else { return }
  691. self.focusedCollection?.fetchResult = changes.fetchResultAfterChanges
  692. if let removed = changes.removedIndexes, removed.count > 0 {
  693. self.collectionView.deleteItems(at: removed.map { IndexPath(item: $0+addIndex, section:0) })
  694. }
  695. if let inserted = changes.insertedIndexes, inserted.count > 0 {
  696. self.collectionView.insertItems(at: inserted.map { IndexPath(item: $0+addIndex, section:0) })
  697. }
  698. changes.enumerateMoves { fromIndex, toIndex in
  699. self.collectionView.moveItem(at: IndexPath(item: fromIndex, section: 0),
  700. to: IndexPath(item: toIndex, section: 0))
  701. }
  702. }, completion: { [weak self] (completed) in
  703. guard let `self` = self else { return }
  704. if completed {
  705. if let changed = changes.changedIndexes, changed.count > 0 {
  706. self.collectionView.reloadItems(at: changed.map { IndexPath(item: $0+addIndex, section:0) })
  707. }
  708. }
  709. })
  710. }
  711. }else {
  712. self.focusedCollection?.fetchResult = changes.fetchResultAfterChanges
  713. self.reloadCollectionView()
  714. }
  715. if let collection = self.focusedCollection {
  716. self.collections[self.getfocusedIndex()] = collection
  717. self.albumPopView.tableView.reloadRows(at: [IndexPath(row: self.getfocusedIndex(), section: 0)], with: .none)
  718. }
  719. }
  720. }
  721. }
  722. // MARK: - UICollectionView delegate & datasource
  723. extension TLPhotosPickerViewController: UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDataSourcePrefetching {
  724. private func getSelectedAssets(_ asset: TLPHAsset) -> TLPHAsset? {
  725. if let index = self.selectedAssets.firstIndex(where: { $0.phAsset == asset.phAsset }) {
  726. return self.selectedAssets[index]
  727. }
  728. return nil
  729. }
  730. private func orderUpdateCells() {
  731. let visibleIndexPaths = self.collectionView.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row })
  732. for indexPath in visibleIndexPaths {
  733. guard let cell = self.collectionView.cellForItem(at: indexPath) as? TLPhotoCollectionViewCell else { continue }
  734. guard let asset = self.focusedCollection?.getTLAsset(at: indexPath) else { continue }
  735. if let selectedAsset = getSelectedAssets(asset) {
  736. cell.selectedAsset = true
  737. cell.orderLabel?.text = "\(selectedAsset.selectedOrder)"
  738. }else {
  739. cell.selectedAsset = false
  740. }
  741. }
  742. }
  743. //Delegate
  744. open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  745. didSelect(collectionView: collectionView, didSelectItemAt: indexPath,isPreview:true)
  746. }
  747. func didSelect(collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath,isPreview: Bool) {
  748. guard let collection = self.focusedCollection, let cell = self.collectionView.cellForItem(at: indexPath) as? TLPhotoCollectionViewCell else { return }
  749. let isCameraRow = collection.useCameraButton && indexPath.section == 0 && indexPath.row == 0
  750. if isCameraRow {
  751. if Platform.isSimulator {
  752. print("not supported by the simulator.")
  753. return
  754. }else {
  755. if self.configure.cameraCellNibSet?.nibName != nil {
  756. cell.selectedCell()
  757. }else {
  758. showCameraIfAuthorized()
  759. }
  760. self.logDelegate?.selectedCameraCell(picker: self)
  761. return
  762. }
  763. }
  764. guard var asset = collection.getTLAsset(at: indexPath), let phAsset = asset.phAsset else { return }
  765. //preview
  766. if isPreview {
  767. if self.configure.isPreview {
  768. if self.configure.isAllPreview {
  769. if collection.sections != nil {
  770. guard let (index,TLPHAssets) = collection.getTLAssets(at: indexPath) else { return }
  771. Mediator.push(BrowsePictureRouterModuleType.pushBrowsePictureTLPHAssets(TLPHAssets: TLPHAssets!, index: index!))
  772. }else {
  773. guard let (index,fetchResult) = collection.getFetchResult(at: indexPath) else { return }
  774. Mediator.push(BrowsePictureRouterModuleType.pushBrowsePicturePHFetchResult(fetchResult: fetchResult!, index: index!))
  775. }
  776. } else {
  777. Mediator.push(BrowsePictureRouterModuleType.pushBrowsePicturePHAssets(phAssets: [phAsset], index: 0))
  778. }
  779. return
  780. }
  781. }
  782. cell.popScaleAnim()
  783. if let index = self.selectedAssets.firstIndex(where: { $0.phAsset == asset.phAsset }) {
  784. //deselect
  785. self.logDelegate?.deselectedPhoto(picker: self, at: indexPath.row)
  786. self.selectedAssets.remove(at: index)
  787. #if swift(>=4.1)
  788. self.selectedAssets = self.selectedAssets.enumerated().compactMap({ (offset,asset) -> TLPHAsset? in
  789. var asset = asset
  790. asset.selectedOrder = offset + 1
  791. return asset
  792. })
  793. #else
  794. self.selectedAssets = self.selectedAssets.enumerated().flatMap({ (offset,asset) -> TLPHAsset? in
  795. var asset = asset
  796. asset.selectedOrder = offset + 1
  797. return asset
  798. })
  799. #endif
  800. cell.selectedAsset = false
  801. cell.stopPlay()
  802. self.orderUpdateCells()
  803. if self.playRequestID?.indexPath == indexPath {
  804. stopPlay()
  805. }
  806. }else {
  807. //select
  808. self.logDelegate?.selectedPhoto(picker: self, at: indexPath.row)
  809. guard !maxCheck() else { return }
  810. guard canSelect(phAsset: phAsset) else { return }
  811. asset.selectedOrder = self.selectedAssets.count + 1
  812. self.selectedAssets.append(asset)
  813. cell.selectedAsset = true
  814. cell.orderLabel?.text = "\(asset.selectedOrder)"
  815. if asset.type != .photo, self.configure.autoPlay {
  816. playVideo(asset: asset, indexPath: indexPath)
  817. }
  818. }
  819. }
  820. open func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
  821. if let cell = cell as? TLPhotoCollectionViewCell {
  822. cell.endDisplayingCell()
  823. cell.stopPlay()
  824. if indexPath == self.playRequestID?.indexPath {
  825. self.playRequestID = nil
  826. }
  827. }
  828. guard let requestID = self.requestIDs[indexPath] else { return }
  829. self.requestIDs.removeValue(forKey: indexPath)
  830. self.photoLibrary.cancelPHImageRequest(requestID: requestID)
  831. }
  832. //Datasource
  833. open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  834. func makeCell(nibName: String) -> TLPhotoCollectionViewCell {
  835. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: nibName, for: indexPath) as! TLPhotoCollectionViewCell
  836. cell.configure = self.configure
  837. cell.imageView?.image = self.placeholderThumbnail
  838. cell.liveBadgeImageView?.image = nil
  839. cell.indexPath = indexPath
  840. cell.orderSelectedClosure = {
  841. [weak self,weak collectionView] indexPath in
  842. self?.didSelect(collectionView: collectionView!, didSelectItemAt: indexPath, isPreview: false)
  843. }
  844. return cell
  845. }
  846. let nibName = self.configure.nibSet?.nibName ?? "TLPhotoCollectionViewCell"
  847. var cell = makeCell(nibName: nibName)
  848. guard let collection = self.focusedCollection else { return cell }
  849. cell.isCameraCell = collection.useCameraButton && indexPath.section == 0 && indexPath.row == 0
  850. if cell.isCameraCell {
  851. if let nibName = self.configure.cameraCellNibSet?.nibName {
  852. cell = makeCell(nibName: nibName)
  853. }else{
  854. cell.imageView?.image = self.cameraImage
  855. }
  856. return cell
  857. }
  858. guard let asset = collection.getTLAsset(at: indexPath) else { return cell }
  859. if let selectedAsset = getSelectedAssets(asset) {
  860. cell.selectedAsset = true
  861. cell.orderLabel?.text = "\(selectedAsset.selectedOrder)"
  862. }else{
  863. cell.selectedAsset = false
  864. }
  865. if asset.state == .progress {
  866. cell.indicator?.startAnimating()
  867. }else {
  868. cell.indicator?.stopAnimating()
  869. }
  870. if let phAsset = asset.phAsset {
  871. if self.usedPrefetch {
  872. let options = PHImageRequestOptions()
  873. options.deliveryMode = .opportunistic
  874. options.resizeMode = .exact
  875. options.isNetworkAccessAllowed = true
  876. let requestID = self.photoLibrary.imageAsset(asset: phAsset, size: self.thumbnailSize, options: options) { [weak self, weak cell] (image,complete) in
  877. guard let `self` = self else { return }
  878. DispatchQueue.main.async {
  879. if self.requestIDs[indexPath] != nil {
  880. cell?.imageView?.image = image
  881. cell?.update(with: phAsset)
  882. if self.allowedVideo {
  883. cell?.durationView?.isHidden = asset.type != .video
  884. cell?.duration = asset.type == .video ? phAsset.duration : nil
  885. }
  886. if complete {
  887. self.requestIDs.removeValue(forKey: indexPath)
  888. }
  889. }
  890. }
  891. }
  892. if requestID > 0 {
  893. self.requestIDs[indexPath] = requestID
  894. }
  895. }else {
  896. queue.async { [weak self, weak cell] in
  897. guard let `self` = self else { return }
  898. let requestID = self.photoLibrary.imageAsset(asset: phAsset, size: self.thumbnailSize, completionBlock: { (image,complete) in
  899. DispatchQueue.main.async {
  900. if self.requestIDs[indexPath] != nil {
  901. cell?.imageView?.image = image
  902. cell?.update(with: phAsset)
  903. if self.allowedVideo {
  904. cell?.durationView?.isHidden = asset.type != .video
  905. cell?.duration = asset.type == .video ? phAsset.duration : nil
  906. }
  907. if complete {
  908. self.requestIDs.removeValue(forKey: indexPath)
  909. }
  910. }
  911. }
  912. })
  913. if requestID > 0 {
  914. self.requestIDs[indexPath] = requestID
  915. }
  916. }
  917. }
  918. if self.allowedLivePhotos {
  919. cell.liveBadgeImageView?.image = asset.type == .livePhoto ? PHLivePhotoView.livePhotoBadgeImage(options: .overContent) : nil
  920. cell.livePhotoView?.delegate = asset.type == .livePhoto ? self : nil
  921. }
  922. }
  923. cell.alpha = 0
  924. UIView.transition(with: cell, duration: 0.1, options: .curveEaseIn, animations: {
  925. cell.alpha = 1
  926. }, completion: nil)
  927. return cell
  928. }
  929. open func numberOfSections(in collectionView: UICollectionView) -> Int {
  930. return self.focusedCollection?.sections?.count ?? 1
  931. }
  932. open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  933. guard let collection = self.focusedCollection else {
  934. return 0
  935. }
  936. return self.focusedCollection?.sections?[safe: section]?.assets.count ?? collection.count
  937. }
  938. //Prefetch
  939. open func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
  940. if self.usedPrefetch {
  941. queue.async { [weak self] in
  942. guard let `self` = self, let collection = self.focusedCollection else { return }
  943. var assets = [PHAsset]()
  944. for indexPath in indexPaths {
  945. if let asset = collection.getAsset(at: indexPath.row) {
  946. assets.append(asset)
  947. }
  948. }
  949. let scale = max(UIScreen.main.scale,2)
  950. let targetSize = CGSize(width: self.thumbnailSize.width*scale, height: self.thumbnailSize.height*scale)
  951. self.photoLibrary.imageManager.startCachingImages(for: assets, targetSize: targetSize, contentMode: .aspectFill, options: nil)
  952. }
  953. }
  954. }
  955. open func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
  956. if self.usedPrefetch {
  957. for indexPath in indexPaths {
  958. guard let requestID = self.requestIDs[indexPath] else { continue }
  959. self.photoLibrary.cancelPHImageRequest(requestID: requestID)
  960. self.requestIDs.removeValue(forKey: indexPath)
  961. }
  962. queue.async { [weak self] in
  963. guard let `self` = self, let collection = self.focusedCollection else { return }
  964. var assets = [PHAsset]()
  965. for indexPath in indexPaths {
  966. if let asset = collection.getAsset(at: indexPath.row) {
  967. assets.append(asset)
  968. }
  969. }
  970. let scale = max(UIScreen.main.scale,2)
  971. let targetSize = CGSize(width: self.thumbnailSize.width*scale, height: self.thumbnailSize.height*scale)
  972. self.photoLibrary.imageManager.stopCachingImages(for: assets, targetSize: targetSize, contentMode: .aspectFill, options: nil)
  973. }
  974. }
  975. }
  976. public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
  977. guard let cell = cell as? TLPhotoCollectionViewCell else {
  978. return
  979. }
  980. cell.willDisplayCell()
  981. if self.usedPrefetch, let collection = self.focusedCollection, let asset = collection.getTLAsset(at: indexPath) {
  982. if let selectedAsset = getSelectedAssets(asset) {
  983. cell.selectedAsset = true
  984. cell.orderLabel?.text = "\(selectedAsset.selectedOrder)"
  985. }else{
  986. cell.selectedAsset = false
  987. }
  988. }
  989. }
  990. }
  991. // MARK: - CustomDataSources for supplementary view
  992. extension TLPhotosPickerViewController: UICollectionViewDelegateFlowLayout {
  993. public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
  994. guard let identifier = self.customDataSouces?.supplementIdentifier(kind: kind) else {
  995. return UICollectionReusableView()
  996. }
  997. let reuseView = collectionView.dequeueReusableSupplementaryView(ofKind: kind,
  998. withReuseIdentifier: identifier,
  999. for: indexPath)
  1000. if let section = self.focusedCollection?.sections?[safe: indexPath.section] {
  1001. self.customDataSouces?.configure(supplement: reuseView, section: section)
  1002. }
  1003. return reuseView
  1004. }
  1005. public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
  1006. if let sections = self.focusedCollection?.sections?[safe: section], sections.title != "camera" {
  1007. return self.customDataSouces?.headerReferenceSize() ?? CGSize.zero
  1008. }
  1009. return CGSize.zero
  1010. }
  1011. public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
  1012. if let sections = self.focusedCollection?.sections?[safe: section], sections.title != "camera" {
  1013. return self.customDataSouces?.footerReferenceSize() ?? CGSize.zero
  1014. }
  1015. return CGSize.zero
  1016. }
  1017. }
  1018. // MARK: - UITableView datasource & delegate
  1019. extension TLPhotosPickerViewController: UITableViewDelegate,UITableViewDataSource {
  1020. //delegate
  1021. open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  1022. self.logDelegate?.selectedAlbum(picker: self, title: self.collections[indexPath.row].title, at: indexPath.row)
  1023. self.focused(collection: self.collections[indexPath.row])
  1024. }
  1025. //datasource
  1026. open func numberOfSections(in tableView: UITableView) -> Int {
  1027. return 1
  1028. }
  1029. open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  1030. return self.collections.count
  1031. }
  1032. open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  1033. let cell = tableView.dequeueReusableCell(withIdentifier: "TLCollectionTableViewCell", for: indexPath) as! TLCollectionTableViewCell
  1034. let collection = self.collections[indexPath.row]
  1035. cell.titleLabel.text = collection.title
  1036. cell.subTitleLabel.text = "\(collection.fetchResult?.count ?? 0)"
  1037. if let phAsset = collection.getAsset(at: collection.useCameraButton ? 1 : 0) {
  1038. let scale = UIScreen.main.scale
  1039. let size = CGSize(width: 80*scale, height: 80*scale)
  1040. self.photoLibrary.imageAsset(asset: phAsset, size: size, completionBlock: { [weak cell] (image,complete) in
  1041. DispatchQueue.main.async {
  1042. cell?.thumbImageView.image = image
  1043. }
  1044. })
  1045. }
  1046. cell.accessoryType = getfocusedIndex() == indexPath.row ? .checkmark : .none
  1047. cell.selectionStyle = .none
  1048. return cell
  1049. }
  1050. }
  1051. extension Array where Element == PopupConfigure {
  1052. var duration: TimeInterval {
  1053. var result: TimeInterval = 0.1
  1054. self.compactMap{ $0 as? PopupConfigure }.forEach{
  1055. if case let .animation(duration) = $0 {
  1056. result = duration
  1057. }
  1058. }
  1059. return result
  1060. }
  1061. }
  1062. extension UIImage {
  1063. public func colorMask(color:UIColor) -> UIImage {
  1064. var result: UIImage?
  1065. let rect = CGRect(x:0, y:0, width:size.width, height:size.height)
  1066. UIGraphicsBeginImageContextWithOptions(rect.size, false, scale)
  1067. if let c = UIGraphicsGetCurrentContext() {
  1068. self.draw(in: rect)
  1069. c.setFillColor(color.cgColor)
  1070. c.setBlendMode(.sourceAtop)
  1071. c.fill(rect)
  1072. result = UIGraphicsGetImageFromCurrentImageContext()
  1073. }
  1074. UIGraphicsEndImageContext()
  1075. return result ?? self
  1076. }
  1077. }