TLPhotosPickerViewController.swift 55 KB

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