TLPhotosPickerViewController.swift 51 KB

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