123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999 |
- //
- // TLPhotosPickerViewController.swift
- // TLPhotosPicker
- //
- // Created by wade.hawk on 2017. 4. 14..
- // Copyright © 2017년 wade.hawk. All rights reserved.
- //
- import UIKit
- import Photos
- import PhotosUI
- import MobileCoreServices
- public protocol TLPhotosPickerViewControllerDelegate: class {
- func dismissPhotoPicker(withPHAssets: [PHAsset])
- func dismissPhotoPicker(withTLPHAssets: [TLPHAsset])
- func dismissComplete()
- func photoPickerDidCancel()
- func canSelectAsset(phAsset: PHAsset) -> Bool
- func didExceedMaximumNumberOfSelection(picker: TLPhotosPickerViewController)
- func handleNoAlbumPermissions(picker: TLPhotosPickerViewController)
- func handleNoCameraPermissions(picker: TLPhotosPickerViewController)
- }
- extension TLPhotosPickerViewControllerDelegate {
- public func deninedAuthoization() { }
- public func dismissPhotoPicker(withPHAssets: [PHAsset]) { }
- public func dismissPhotoPicker(withTLPHAssets: [TLPHAsset]) { }
- public func dismissComplete() { }
- public func photoPickerDidCancel() { }
- public func canSelectAsset(phAsset: PHAsset) -> Bool { return true }
- public func didExceedMaximumNumberOfSelection(picker: TLPhotosPickerViewController) { }
- public func handleNoAlbumPermissions(picker: TLPhotosPickerViewController) { }
- public func handleNoCameraPermissions(picker: TLPhotosPickerViewController) { }
- }
- //for log
- public protocol TLPhotosPickerLogDelegate: class {
- func selectedCameraCell(picker: TLPhotosPickerViewController)
- func deselectedPhoto(picker: TLPhotosPickerViewController, at: Int)
- func selectedPhoto(picker: TLPhotosPickerViewController, at: Int)
- func selectedAlbum(picker: TLPhotosPickerViewController, title: String, at: Int)
- }
- extension TLPhotosPickerLogDelegate {
- func selectedCameraCell(picker: TLPhotosPickerViewController) { }
- func deselectedPhoto(picker: TLPhotosPickerViewController, at: Int) { }
- func selectedPhoto(picker: TLPhotosPickerViewController, at: Int) { }
- func selectedAlbum(picker: TLPhotosPickerViewController, collections: [TLAssetsCollection], at: Int) { }
- }
- public struct TLPhotosPickerConfigure {
- public var defaultCameraRollTitle = "Camera Roll"
- public var tapHereToChange = "Tap here to change"
- public var cancelTitle = "Cancel"
- public var doneTitle = "Done"
- public var emptyMessage = "No albums"
- public var emptyImage: UIImage? = nil
- public var usedCameraButton = true
- public var usedPrefetch = false
- public var allowedLivePhotos = true
- public var allowedVideo = true
- public var allowedAlbumCloudShared = false
- public var allowedVideoRecording = true
- public var recordingVideoQuality: UIImagePickerController.QualityType = .typeMedium
- public var maxVideoDuration:TimeInterval? = nil
- public var autoPlay = true
- public var muteAudio = true
- public var mediaType: PHAssetMediaType? = nil
- public var numberOfColumn = 3
- public var singleSelectedMode = false
- public var maxSelectedAssets: Int? = nil
- public var fetchOption: PHFetchOptions? = nil
- public var selectedColor = UIColor(red: 88/255, green: 144/255, blue: 255/255, alpha: 1.0)
- public var cameraBgColor = UIColor(red: 221/255, green: 223/255, blue: 226/255, alpha: 1)
- public var cameraIcon = TLBundle.podBundleImage(named: "camera")
- public var videoIcon = TLBundle.podBundleImage(named: "video")
- public var placeholderIcon = TLBundle.podBundleImage(named: "insertPhotoMaterial")
- public var nibSet: (nibName: String, bundle:Bundle)? = nil
- public var cameraCellNibSet: (nibName: String, bundle:Bundle)? = nil
- public var fetchCollectionTypes: [(PHAssetCollectionType,PHAssetCollectionSubtype)]? = nil
- public init() {
-
- }
- }
- public struct Platform {
-
- public static var isSimulator: Bool {
- return TARGET_OS_SIMULATOR != 0 // Use this line in Xcode 7 or newer
- }
-
- }
- open class TLPhotosPickerViewController: UIViewController {
- @IBOutlet open var titleView: UIView!
- @IBOutlet open var titleLabel: UILabel!
- @IBOutlet open var subTitleStackView: UIStackView!
- @IBOutlet open var subTitleLabel: UILabel!
- @IBOutlet open var subTitleArrowImageView: UIImageView!
- @IBOutlet open var albumPopView: TLAlbumPopView!
- @IBOutlet open var collectionView: UICollectionView!
- @IBOutlet open var indicator: UIActivityIndicatorView!
- @IBOutlet open var popArrowImageView: UIImageView!
- @IBOutlet open var customNavItem: UINavigationItem!
- @IBOutlet open var doneButton: UIBarButtonItem!
- @IBOutlet open var cancelButton: UIBarButtonItem!
- @IBOutlet open var navigationBarTopConstraint: NSLayoutConstraint!
- @IBOutlet open var emptyView: UIView!
- @IBOutlet open var emptyImageView: UIImageView!
- @IBOutlet open var emptyMessageLabel: UILabel!
-
- public weak var delegate: TLPhotosPickerViewControllerDelegate? = nil
- public weak var logDelegate: TLPhotosPickerLogDelegate? = nil
- public var selectedAssets = [TLPHAsset]()
- public var configure = TLPhotosPickerConfigure()
-
- fileprivate var usedCameraButton: Bool {
- get {
- return self.configure.usedCameraButton
- }
- }
- fileprivate var allowedVideo: Bool {
- get {
- return self.configure.allowedVideo
- }
- }
- fileprivate var usedPrefetch: Bool {
- get {
- return self.configure.usedPrefetch
- }
- set {
- self.configure.usedPrefetch = newValue
- }
- }
- fileprivate var allowedLivePhotos: Bool {
- get {
- return self.configure.allowedLivePhotos
- }
- set {
- self.configure.allowedLivePhotos = newValue
- }
- }
- @objc open var canSelectAsset: ((PHAsset) -> Bool)? = nil
- @objc open var didExceedMaximumNumberOfSelection: ((TLPhotosPickerViewController) -> Void)? = nil
- @objc open var handleNoAlbumPermissions: ((TLPhotosPickerViewController) -> Void)? = nil
- @objc open var handleNoCameraPermissions: ((TLPhotosPickerViewController) -> Void)? = nil
- @objc open var dismissCompletion: (() -> Void)? = nil
- fileprivate var completionWithPHAssets: (([PHAsset]) -> Void)? = nil
- fileprivate var completionWithTLPHAssets: (([TLPHAsset]) -> Void)? = nil
- fileprivate var didCancel: (() -> Void)? = nil
-
- fileprivate var collections = [TLAssetsCollection]()
- fileprivate var focusedCollection: TLAssetsCollection? = nil
- fileprivate var requestIds = [IndexPath:PHImageRequestID]()
- fileprivate var playRequestId: (indexPath: IndexPath, requestId: PHImageRequestID)? = nil
- fileprivate var photoLibrary = TLPhotoLibrary()
- fileprivate var queue = DispatchQueue(label: "tilltue.photos.pikcker.queue")
- fileprivate var thumbnailSize = CGSize.zero
- fileprivate var placeholderThumbnail: UIImage? = nil
- fileprivate var cameraImage: UIImage? = nil
-
- deinit {
- //print("deinit TLPhotosPickerViewController")
- PHPhotoLibrary.shared().unregisterChangeObserver(self)
- }
-
- required public init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- public init() {
- super.init(nibName: "TLPhotosPickerViewController", bundle: Bundle(for: TLPhotosPickerViewController.self))
- }
-
- @objc convenience public init(withPHAssets: (([PHAsset]) -> Void)? = nil, didCancel: (() -> Void)? = nil) {
- self.init()
- self.completionWithPHAssets = withPHAssets
- self.didCancel = didCancel
- }
-
- convenience public init(withTLPHAssets: (([TLPHAsset]) -> Void)? = nil, didCancel: (() -> Void)? = nil) {
- self.init()
- self.completionWithTLPHAssets = withTLPHAssets
- self.didCancel = didCancel
- }
-
- override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
- return UIInterfaceOrientationMask.portrait
- }
-
- override open func didReceiveMemoryWarning() {
- super.didReceiveMemoryWarning()
- self.stopPlay()
- }
-
- func checkAuthorization() {
- switch PHPhotoLibrary.authorizationStatus() {
- case .notDetermined:
- PHPhotoLibrary.requestAuthorization { [weak self] status in
- switch status {
- case .authorized:
- self?.initPhotoLibrary()
- default:
- self?.handleDeniedAlbumsAuthorization()
- }
- }
- case .authorized:
- self.initPhotoLibrary()
- case .restricted: fallthrough
- case .denied:
- handleDeniedAlbumsAuthorization()
- }
- }
-
- override open func viewDidLoad() {
- super.viewDidLoad()
- makeUI()
- checkAuthorization()
- }
-
- override open func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
- if self.thumbnailSize == CGSize.zero {
- initItemSize()
- }
- if #available(iOS 11.0, *) {
- } else if self.navigationBarTopConstraint.constant == 0 {
- self.navigationBarTopConstraint.constant = 20
- }
- }
-
- override open func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- if self.photoLibrary.delegate == nil {
- initPhotoLibrary()
- }
- }
- }
- // MARK: - UI & UI Action
- extension TLPhotosPickerViewController {
-
- @objc public func registerNib(nibName: String, bundle: Bundle) {
- self.collectionView.register(UINib(nibName: nibName, bundle: bundle), forCellWithReuseIdentifier: nibName)
- }
-
- fileprivate func centerAtRect(image: UIImage?, rect: CGRect, bgColor: UIColor = UIColor.white) -> UIImage? {
- guard let image = image else { return nil }
- UIGraphicsBeginImageContextWithOptions(rect.size, false, image.scale)
- bgColor.setFill()
- UIRectFill(CGRect(x: 0, y: 0, width: rect.size.width, height: rect.size.height))
- 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))
- let result = UIGraphicsGetImageFromCurrentImageContext()
- UIGraphicsEndImageContext()
- return result
- }
-
- fileprivate func initItemSize() {
- guard let layout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
- let count = CGFloat(self.configure.numberOfColumn)
- let width = (self.view.frame.size.width-(5*(count-1)))/count
- self.thumbnailSize = CGSize(width: width, height: width)
- layout.itemSize = self.thumbnailSize
- self.collectionView.collectionViewLayout = layout
- self.placeholderThumbnail = centerAtRect(image: self.configure.placeholderIcon, rect: CGRect(x: 0, y: 0, width: width, height: width))
- self.cameraImage = centerAtRect(image: self.configure.cameraIcon, rect: CGRect(x: 0, y: 0, width: width, height: width), bgColor: self.configure.cameraBgColor)
- }
-
- @objc open func makeUI() {
- registerNib(nibName: "TLPhotoCollectionViewCell", bundle: Bundle(for: TLPhotoCollectionViewCell.self))
- if let nibSet = self.configure.nibSet {
- registerNib(nibName: nibSet.nibName, bundle: nibSet.bundle)
- }
- if let nibSet = self.configure.cameraCellNibSet {
- registerNib(nibName: nibSet.nibName, bundle: nibSet.bundle)
- }
- self.indicator.startAnimating()
- let tapGesture = UITapGestureRecognizer(target: self, action: #selector(titleTap))
- self.titleView.addGestureRecognizer(tapGesture)
- self.titleLabel.text = self.configure.defaultCameraRollTitle
- self.subTitleLabel.text = self.configure.tapHereToChange
- self.cancelButton.title = self.configure.cancelTitle
- self.doneButton.title = self.configure.doneTitle
- self.doneButton.setTitleTextAttributes([NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: UIFont.labelFontSize)], for: .normal)
- self.emptyView.isHidden = true
- self.emptyImageView.image = self.configure.emptyImage
- self.emptyMessageLabel.text = self.configure.emptyMessage
- self.albumPopView.tableView.delegate = self
- self.albumPopView.tableView.dataSource = self
- self.popArrowImageView.image = TLBundle.podBundleImage(named: "pop_arrow")
- self.subTitleArrowImageView.image = TLBundle.podBundleImage(named: "arrow")
- if #available(iOS 10.0, *), self.usedPrefetch {
- self.collectionView.isPrefetchingEnabled = true
- self.collectionView.prefetchDataSource = self
- } else {
- self.usedPrefetch = false
- }
- if #available(iOS 9.0, *), self.allowedLivePhotos {
- }else {
- self.allowedLivePhotos = false
- }
- }
-
- fileprivate func updateTitle() {
- guard self.focusedCollection != nil else { return }
- self.titleLabel.text = self.focusedCollection?.title
- }
-
- fileprivate func reloadCollectionView() {
- guard self.focusedCollection != nil else { return }
- self.collectionView.reloadData()
- }
-
- fileprivate func reloadTableView() {
- let count = min(5, self.collections.count)
- var frame = self.albumPopView.popupView.frame
- frame.size.height = CGFloat(count * 75)
- self.albumPopView.popupViewHeight.constant = CGFloat(count * 75)
- UIView.animate(withDuration: self.albumPopView.show ? 0.1:0) {
- self.albumPopView.popupView.frame = frame
- self.albumPopView.setNeedsLayout()
- }
- self.albumPopView.tableView.reloadData()
- self.albumPopView.setupPopupFrame()
- }
-
- fileprivate func initPhotoLibrary() {
- if PHPhotoLibrary.authorizationStatus() == .authorized {
- self.photoLibrary.delegate = self
- self.photoLibrary.fetchCollection(configure: self.configure)
- }else{
- //self.dismiss(animated: true, completion: nil)
- }
- }
-
- fileprivate func registerChangeObserver() {
- PHPhotoLibrary.shared().register(self)
- }
-
- fileprivate func getfocusedIndex() -> Int {
- guard let focused = self.focusedCollection, let result = self.collections.index(where: { $0 == focused }) else { return 0 }
- return result
- }
-
- fileprivate func focused(collection: TLAssetsCollection) {
- func resetRequest() {
- cancelAllImageAssets()
- }
- resetRequest()
- self.collections[getfocusedIndex()].recentPosition = self.collectionView.contentOffset
- var reloadIndexPaths = [IndexPath(row: getfocusedIndex(), section: 0)]
- self.focusedCollection = collection
- self.focusedCollection?.fetchResult = self.photoLibrary.fetchResult(collection: collection, configure: self.configure)
- reloadIndexPaths.append(IndexPath(row: getfocusedIndex(), section: 0))
- self.albumPopView.tableView.reloadRows(at: reloadIndexPaths, with: .none)
- self.albumPopView.show(false, duration: 0.2)
- self.updateTitle()
- self.reloadCollectionView()
- self.collectionView.contentOffset = collection.recentPosition
- }
-
- fileprivate func cancelAllImageAssets() {
- for (_,requestId) in self.requestIds {
- self.photoLibrary.cancelPHImageRequest(requestId: requestId)
- }
- self.requestIds.removeAll()
- }
-
- // User Action
- @objc func titleTap() {
- guard collections.count > 0 else { return }
- self.albumPopView.show(self.albumPopView.isHidden)
- }
-
- @IBAction open func cancelButtonTap() {
- self.stopPlay()
- self.dismiss(done: false)
- }
-
- @IBAction open func doneButtonTap() {
- self.stopPlay()
- self.dismiss(done: true)
- }
-
- fileprivate func dismiss(done: Bool) {
- if done {
- #if swift(>=4.1)
- self.delegate?.dismissPhotoPicker(withPHAssets: self.selectedAssets.compactMap{ $0.phAsset })
- #else
- self.delegate?.dismissPhotoPicker(withPHAssets: self.selectedAssets.flatMap{ $0.phAsset })
- #endif
- self.delegate?.dismissPhotoPicker(withTLPHAssets: self.selectedAssets)
- self.completionWithTLPHAssets?(self.selectedAssets)
- #if swift(>=4.1)
- self.completionWithPHAssets?(self.selectedAssets.compactMap{ $0.phAsset })
- #else
- self.completionWithPHAssets?(self.selectedAssets.flatMap{ $0.phAsset })
- #endif
- }else {
- self.delegate?.photoPickerDidCancel()
- self.didCancel?()
- }
- self.dismiss(animated: true) { [weak self] in
- self?.delegate?.dismissComplete()
- self?.dismissCompletion?()
- }
- }
-
- fileprivate func canSelect(phAsset: PHAsset) -> Bool {
- if let closure = self.canSelectAsset {
- return closure(phAsset)
- }else if let delegate = self.delegate {
- return delegate.canSelectAsset(phAsset: phAsset)
- }
- return true
- }
-
- fileprivate func maxCheck() -> Bool {
- if self.configure.singleSelectedMode {
- self.selectedAssets.removeAll()
- self.orderUpdateCells()
- }
- if let max = self.configure.maxSelectedAssets, max <= self.selectedAssets.count {
- self.delegate?.didExceedMaximumNumberOfSelection(picker: self)
- self.didExceedMaximumNumberOfSelection?(self)
- return true
- }
- return false
- }
- fileprivate func focusFirstCollection() {
- if self.focusedCollection == nil, let collection = self.collections.first {
- self.focusedCollection = collection
- self.updateTitle()
- self.reloadCollectionView()
- }
- }
- }
- // MARK: - TLPhotoLibraryDelegate
- extension TLPhotosPickerViewController: TLPhotoLibraryDelegate {
- func loadCameraRollCollection(collection: TLAssetsCollection) {
- self.collections = [collection]
- self.focusFirstCollection()
- self.indicator.stopAnimating()
- self.reloadCollectionView()
- self.reloadTableView()
- }
-
- func loadCompleteAllCollection(collections: [TLAssetsCollection]) {
- self.collections = collections
- self.focusFirstCollection()
- let isEmpty = self.collections.count == 0
- self.subTitleStackView.isHidden = isEmpty
- self.emptyView.isHidden = !isEmpty
- self.emptyImageView.isHidden = self.emptyImageView.image == nil
- self.indicator.stopAnimating()
- self.reloadTableView()
- self.registerChangeObserver()
- }
- }
- // MARK: - Camera Picker
- extension TLPhotosPickerViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
- fileprivate func showCameraIfAuthorized() {
- let cameraAuthorization = AVCaptureDevice.authorizationStatus(for: .video)
- switch cameraAuthorization {
- case .authorized:
- self.showCamera()
- case .notDetermined:
- AVCaptureDevice.requestAccess(for: .video, completionHandler: { [weak self] (authorized) in
- DispatchQueue.main.async { [weak self] in
- if authorized {
- self?.showCamera()
- } else {
- self?.handleDeniedCameraAuthorization()
- }
- }
- })
- case .restricted, .denied:
- self.handleDeniedCameraAuthorization()
- }
- }
- fileprivate func showCamera() {
- guard !maxCheck() else { return }
- let picker = UIImagePickerController()
- picker.sourceType = .camera
- picker.mediaTypes = [kUTTypeImage as String]
- if self.configure.allowedVideoRecording {
- picker.mediaTypes.append(kUTTypeMovie as String)
- picker.videoQuality = self.configure.recordingVideoQuality
- if let duration = self.configure.maxVideoDuration {
- picker.videoMaximumDuration = duration
- }
- }
- picker.allowsEditing = false
- picker.delegate = self
- self.present(picker, animated: true, completion: nil)
- }
- fileprivate func handleDeniedAlbumsAuthorization() {
- self.delegate?.handleNoAlbumPermissions(picker: self)
- self.handleNoAlbumPermissions?(self)
- }
-
- fileprivate func handleDeniedCameraAuthorization() {
- self.delegate?.handleNoCameraPermissions(picker: self)
- self.handleNoCameraPermissions?(self)
- }
-
- open func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
- picker.dismiss(animated: true, completion: nil)
- }
-
- open func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
- if let image = (info[.originalImage] as? UIImage) {
- var placeholderAsset: PHObjectPlaceholder? = nil
- PHPhotoLibrary.shared().performChanges({
- let newAssetRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
- placeholderAsset = newAssetRequest.placeholderForCreatedAsset
- }, completionHandler: { [weak self] (sucess, error) in
- if sucess, let `self` = self, let identifier = placeholderAsset?.localIdentifier {
- guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil).firstObject else { return }
- var result = TLPHAsset(asset: asset)
- result.selectedOrder = self.selectedAssets.count + 1
- self.selectedAssets.append(result)
- self.logDelegate?.selectedPhoto(picker: self, at: 1)
- }
- })
- }
- else if (info[.mediaType] as? String) == kUTTypeMovie as String {
- var placeholderAsset: PHObjectPlaceholder? = nil
- PHPhotoLibrary.shared().performChanges({
- let newAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: info[.mediaURL] as! URL)
- placeholderAsset = newAssetRequest?.placeholderForCreatedAsset
- }) { [weak self] (sucess, error) in
- if sucess, let `self` = self, let identifier = placeholderAsset?.localIdentifier {
- guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil).firstObject else { return }
- var result = TLPHAsset(asset: asset)
- result.selectedOrder = self.selectedAssets.count + 1
- self.selectedAssets.append(result)
- self.logDelegate?.selectedPhoto(picker: self, at: 1)
- }
- }
- }
-
- picker.dismiss(animated: true, completion: nil)
- }
- }
- // MARK: - UICollectionView Scroll Delegate
- extension TLPhotosPickerViewController {
- open func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
- if !decelerate {
- videoCheck()
- }
- }
-
- open func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
- videoCheck()
- }
-
- fileprivate func videoCheck() {
- func play(asset: (IndexPath,TLPHAsset)) {
- if self.playRequestId?.indexPath != asset.0 {
- playVideo(asset: asset.1, indexPath: asset.0)
- }
- }
- guard self.configure.autoPlay else { return }
- guard self.playRequestId == nil else { return }
- let visibleIndexPaths = self.collectionView.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row })
- #if swift(>=4.1)
- let boundAssets = visibleIndexPaths.compactMap{ indexPath -> (IndexPath,TLPHAsset)? in
- guard let asset = self.focusedCollection?.getTLAsset(at: indexPath.row),asset.phAsset?.mediaType == .video else { return nil }
- return (indexPath,asset)
- }
- #else
- let boundAssets = visibleIndexPaths.flatMap{ indexPath -> (IndexPath,TLPHAsset)? in
- guard let asset = self.focusedCollection?.getTLAsset(at: indexPath.row),asset.phAsset?.mediaType == .video else { return nil }
- return (indexPath,asset)
- }
- #endif
- if let firstSelectedVideoAsset = (boundAssets.filter{ getSelectedAssets($0.1) != nil }.first) {
- play(asset: firstSelectedVideoAsset)
- }else if let firstVideoAsset = boundAssets.first {
- play(asset: firstVideoAsset)
- }
-
- }
- }
- // MARK: - Video & LivePhotos Control PHLivePhotoViewDelegate
- extension TLPhotosPickerViewController: PHLivePhotoViewDelegate {
- fileprivate func stopPlay() {
- guard let playRequest = self.playRequestId else { return }
- self.playRequestId = nil
- guard let cell = self.collectionView.cellForItem(at: playRequest.indexPath) as? TLPhotoCollectionViewCell else { return }
- cell.stopPlay()
- }
-
- fileprivate func playVideo(asset: TLPHAsset, indexPath: IndexPath) {
- stopPlay()
- guard let phAsset = asset.phAsset else { return }
- if asset.type == .video {
- guard let cell = self.collectionView.cellForItem(at: indexPath) as? TLPhotoCollectionViewCell else { return }
- let requestId = self.photoLibrary.videoAsset(asset: phAsset, completionBlock: { (playerItem, info) in
- DispatchQueue.main.sync { [weak self, weak cell] in
- guard let `self` = self, let cell = cell, cell.player == nil else { return }
- let player = AVPlayer(playerItem: playerItem)
- cell.player = player
- player.play()
- player.isMuted = self.configure.muteAudio
- }
- })
- if requestId > 0 {
- self.playRequestId = (indexPath,requestId)
- }
- }else if asset.type == .livePhoto {
-
- guard let cell = self.collectionView.cellForItem(at: indexPath) as? TLPhotoCollectionViewCell else { return }
- let requestId = self.photoLibrary.livePhotoAsset(asset: phAsset, size: self.thumbnailSize, completionBlock: { [weak cell] (livePhoto,complete) in
- cell?.livePhotoView?.isHidden = false
- cell?.livePhotoView?.livePhoto = livePhoto
- cell?.livePhotoView?.isMuted = true
- cell?.livePhotoView?.startPlayback(with: .hint)
- })
- if requestId > 0 {
- self.playRequestId = (indexPath,requestId)
- }
- }
- }
-
- public func livePhotoView(_ livePhotoView: PHLivePhotoView, didEndPlaybackWith playbackStyle: PHLivePhotoViewPlaybackStyle) {
- livePhotoView.isMuted = true
- livePhotoView.startPlayback(with: .hint)
- }
-
- public func livePhotoView(_ livePhotoView: PHLivePhotoView, willBeginPlaybackWith playbackStyle: PHLivePhotoViewPlaybackStyle) {
- }
- }
- // MARK: - PHPhotoLibraryChangeObserver
- extension TLPhotosPickerViewController: PHPhotoLibraryChangeObserver {
- public func photoLibraryDidChange(_ changeInstance: PHChange) {
- guard getfocusedIndex() == 0 else { return }
- let addIndex = self.usedCameraButton ? 1 : 0
- DispatchQueue.main.sync {
- guard let changeFetchResult = self.focusedCollection?.fetchResult else { return }
- guard let changes = changeInstance.changeDetails(for: changeFetchResult) else { return }
- if changes.hasIncrementalChanges {
- var deletedSelectedAssets = false
- var order = 0
- #if swift(>=4.1)
- self.selectedAssets = self.selectedAssets.enumerated().compactMap({ (offset,asset) -> TLPHAsset? in
- var asset = asset
- if let phAsset = asset.phAsset, changes.fetchResultAfterChanges.contains(phAsset) {
- order += 1
- asset.selectedOrder = order
- return asset
- }
- deletedSelectedAssets = true
- return nil
- })
- #else
- self.selectedAssets = self.selectedAssets.enumerated().flatMap({ (offset,asset) -> TLPHAsset? in
- var asset = asset
- if let phAsset = asset.phAsset, changes.fetchResultAfterChanges.contains(phAsset) {
- order += 1
- asset.selectedOrder = order
- return asset
- }
- deletedSelectedAssets = true
- return nil
- })
- #endif
- if deletedSelectedAssets {
- self.focusedCollection?.fetchResult = changes.fetchResultAfterChanges
- self.collectionView.reloadData()
- }else {
- self.collectionView.performBatchUpdates({ [weak self] in
- guard let `self` = self else { return }
- self.focusedCollection?.fetchResult = changes.fetchResultAfterChanges
- if let removed = changes.removedIndexes, removed.count > 0 {
- self.collectionView.deleteItems(at: removed.map { IndexPath(item: $0+addIndex, section:0) })
- }
- if let inserted = changes.insertedIndexes, inserted.count > 0 {
- self.collectionView.insertItems(at: inserted.map { IndexPath(item: $0+addIndex, section:0) })
- }
- changes.enumerateMoves { fromIndex, toIndex in
- self.collectionView.moveItem(at: IndexPath(item: fromIndex, section: 0),
- to: IndexPath(item: toIndex, section: 0))
- }
- }, completion: { [weak self] (completed) in
- guard let `self` = self else { return }
- if completed {
- if let changed = changes.changedIndexes, changed.count > 0 {
- self.collectionView.reloadItems(at: changed.map { IndexPath(item: $0+addIndex, section:0) })
- }
- }
- })
- }
- }else {
- self.focusedCollection?.fetchResult = changes.fetchResultAfterChanges
- self.collectionView.reloadData()
- }
- if let collection = self.focusedCollection {
- self.collections[getfocusedIndex()] = collection
- self.albumPopView.tableView.reloadRows(at: [IndexPath(row: getfocusedIndex(), section: 0)], with: .none)
- }
- }
- }
- }
- // MARK: - UICollectionView delegate & datasource
- extension TLPhotosPickerViewController: UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDataSourcePrefetching {
- fileprivate func getSelectedAssets(_ asset: TLPHAsset) -> TLPHAsset? {
- if let index = self.selectedAssets.index(where: { $0.phAsset == asset.phAsset }) {
- return self.selectedAssets[index]
- }
- return nil
- }
-
- fileprivate func orderUpdateCells() {
- let visibleIndexPaths = self.collectionView.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row })
- for indexPath in visibleIndexPaths {
- guard let cell = self.collectionView.cellForItem(at: indexPath) as? TLPhotoCollectionViewCell else { continue }
- guard let asset = self.focusedCollection?.getTLAsset(at: indexPath.row) else { continue }
- if let selectedAsset = getSelectedAssets(asset) {
- cell.selectedAsset = true
- cell.orderLabel?.text = "\(selectedAsset.selectedOrder)"
- }else {
- cell.selectedAsset = false
- }
- }
- }
-
- //Delegate
- open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
- guard let collection = self.focusedCollection, let cell = self.collectionView.cellForItem(at: indexPath) as? TLPhotoCollectionViewCell else { return }
- if collection.useCameraButton && indexPath.row == 0 {
- if Platform.isSimulator {
- print("not supported by the simulator.")
- return
- }else {
- if self.configure.cameraCellNibSet?.nibName != nil {
- cell.selectedCell()
- }else {
- showCameraIfAuthorized()
- }
- self.logDelegate?.selectedCameraCell(picker: self)
- return
- }
- }
- guard var asset = collection.getTLAsset(at: indexPath.row), let phAsset = asset.phAsset else { return }
- cell.popScaleAnim()
- if let index = self.selectedAssets.index(where: { $0.phAsset == asset.phAsset }) {
- //deselect
- self.logDelegate?.deselectedPhoto(picker: self, at: indexPath.row)
- self.selectedAssets.remove(at: index)
- #if swift(>=4.1)
- self.selectedAssets = self.selectedAssets.enumerated().compactMap({ (offset,asset) -> TLPHAsset? in
- var asset = asset
- asset.selectedOrder = offset + 1
- return asset
- })
- #else
- self.selectedAssets = self.selectedAssets.enumerated().flatMap({ (offset,asset) -> TLPHAsset? in
- var asset = asset
- asset.selectedOrder = offset + 1
- return asset
- })
- #endif
- cell.selectedAsset = false
- cell.stopPlay()
- self.orderUpdateCells()
- if self.playRequestId?.indexPath == indexPath {
- stopPlay()
- }
- }else {
- //select
- self.logDelegate?.selectedPhoto(picker: self, at: indexPath.row)
- guard !maxCheck() else { return }
- guard canSelect(phAsset: phAsset) else { return }
- asset.selectedOrder = self.selectedAssets.count + 1
- self.selectedAssets.append(asset)
- cell.selectedAsset = true
- cell.orderLabel?.text = "\(asset.selectedOrder)"
- if asset.type != .photo, self.configure.autoPlay {
- playVideo(asset: asset, indexPath: indexPath)
- }
- }
- }
-
- open func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
- if let cell = cell as? TLPhotoCollectionViewCell {
- cell.endDisplayingCell()
- cell.stopPlay()
- if indexPath == self.playRequestId?.indexPath {
- self.playRequestId = nil
- }
- }
- guard let requestId = self.requestIds[indexPath] else { return }
- self.requestIds.removeValue(forKey: indexPath)
- self.photoLibrary.cancelPHImageRequest(requestId: requestId)
- }
-
- //Datasource
- open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
- func makeCell(nibName: String) -> TLPhotoCollectionViewCell {
- let cell = collectionView.dequeueReusableCell(withReuseIdentifier: nibName, for: indexPath) as! TLPhotoCollectionViewCell
- cell.configure = self.configure
- cell.imageView?.image = self.placeholderThumbnail
- cell.liveBadgeImageView = nil
- return cell
- }
- let nibName = self.configure.nibSet?.nibName ?? "TLPhotoCollectionViewCell"
- var cell = makeCell(nibName: nibName)
- guard let collection = self.focusedCollection else { return cell }
- cell.isCameraCell = collection.useCameraButton && indexPath.row == 0
- if cell.isCameraCell {
- if let nibName = self.configure.cameraCellNibSet?.nibName {
- cell = makeCell(nibName: nibName)
- }else{
- cell.imageView?.image = self.cameraImage
- }
- cell.willDisplayCell()
- return cell
- }
- guard let asset = collection.getTLAsset(at: indexPath.row) else { return cell }
- if let selectedAsset = getSelectedAssets(asset) {
- cell.selectedAsset = true
- cell.orderLabel?.text = "\(selectedAsset.selectedOrder)"
- }else{
- cell.selectedAsset = false
- }
- if asset.state == .progress {
- cell.indicator?.startAnimating()
- }else {
- cell.indicator?.stopAnimating()
- }
- if let phAsset = asset.phAsset {
- if self.usedPrefetch {
- let options = PHImageRequestOptions()
- options.deliveryMode = .opportunistic
- options.resizeMode = .exact
- options.isNetworkAccessAllowed = true
- let requestId = self.photoLibrary.imageAsset(asset: phAsset, size: self.thumbnailSize, options: options) { [weak self, weak cell] (image,complete) in
- guard let `self` = self else { return }
- DispatchQueue.main.async {
- if self.requestIds[indexPath] != nil {
- cell?.imageView?.image = image
- cell?.update(with: phAsset)
- if self.allowedVideo {
- cell?.durationView?.isHidden = asset.type != .video
- cell?.duration = asset.type == .video ? phAsset.duration : nil
- }
- if complete {
- self.requestIds.removeValue(forKey: indexPath)
- }
- }
- }
- }
- if requestId > 0 {
- self.requestIds[indexPath] = requestId
- }
- }else {
- queue.async { [weak self, weak cell] in
- guard let `self` = self else { return }
- let requestId = self.photoLibrary.imageAsset(asset: phAsset, size: self.thumbnailSize, completionBlock: { (image,complete) in
- DispatchQueue.main.async {
- if self.requestIds[indexPath] != nil {
- cell?.imageView?.image = image
- cell?.update(with: phAsset)
- if self.allowedVideo {
- cell?.durationView?.isHidden = asset.type != .video
- cell?.duration = asset.type == .video ? phAsset.duration : nil
- }
- if complete {
- self.requestIds.removeValue(forKey: indexPath)
- }
- }
- }
- })
- if requestId > 0 {
- self.requestIds[indexPath] = requestId
- }
- }
- }
- if self.allowedLivePhotos {
- cell.liveBadgeImageView?.image = asset.type == .livePhoto ? PHLivePhotoView.livePhotoBadgeImage(options: .overContent) : nil
- cell.livePhotoView?.delegate = asset.type == .livePhoto ? self : nil
- }
- }
- cell.alpha = 0
- UIView.transition(with: cell, duration: 0.1, options: .curveEaseIn, animations: {
- cell.alpha = 1
- }, completion: nil)
- return cell
- }
-
- open func numberOfSections(in collectionView: UICollectionView) -> Int {
- return 1
- }
-
- open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
- guard let collection = self.focusedCollection else { return 0 }
- return collection.count
- }
-
- //Prefetch
- open func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
- if self.usedPrefetch {
- queue.async { [weak self] in
- guard let `self` = self, let collection = self.focusedCollection else { return }
- var assets = [PHAsset]()
- for indexPath in indexPaths {
- if let asset = collection.getAsset(at: indexPath.row) {
- assets.append(asset)
- }
- }
- let scale = max(UIScreen.main.scale,2)
- let targetSize = CGSize(width: self.thumbnailSize.width*scale, height: self.thumbnailSize.height*scale)
- self.photoLibrary.imageManager.startCachingImages(for: assets, targetSize: targetSize, contentMode: .aspectFill, options: nil)
- }
- }
- }
-
- open func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
- if self.usedPrefetch {
- for indexPath in indexPaths {
- guard let requestId = self.requestIds[indexPath] else { continue }
- self.photoLibrary.cancelPHImageRequest(requestId: requestId)
- self.requestIds.removeValue(forKey: indexPath)
- }
- queue.async { [weak self] in
- guard let `self` = self, let collection = self.focusedCollection else { return }
- var assets = [PHAsset]()
- for indexPath in indexPaths {
- if let asset = collection.getAsset(at: indexPath.row) {
- assets.append(asset)
- }
- }
- let scale = max(UIScreen.main.scale,2)
- let targetSize = CGSize(width: self.thumbnailSize.width*scale, height: self.thumbnailSize.height*scale)
- self.photoLibrary.imageManager.stopCachingImages(for: assets, targetSize: targetSize, contentMode: .aspectFill, options: nil)
- }
- }
- }
- public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
- if self.usedPrefetch, let cell = cell as? TLPhotoCollectionViewCell, let collection = self.focusedCollection, let asset = collection.getTLAsset(at: indexPath.row) {
- if let selectedAsset = getSelectedAssets(asset) {
- cell.selectedAsset = true
- cell.orderLabel?.text = "\(selectedAsset.selectedOrder)"
- }else{
- cell.selectedAsset = false
- }
- }
- }
- }
- // MARK: - UITableView datasource & delegate
- extension TLPhotosPickerViewController: UITableViewDelegate,UITableViewDataSource {
- //delegate
- open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- self.logDelegate?.selectedAlbum(picker: self, title: self.collections[indexPath.row].title, at: indexPath.row)
- self.focused(collection: self.collections[indexPath.row])
- }
-
- //datasource
- open func numberOfSections(in tableView: UITableView) -> Int {
- return 1
- }
-
- open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return self.collections.count
- }
-
- open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- let cell = tableView.dequeueReusableCell(withIdentifier: "TLCollectionTableViewCell", for: indexPath) as! TLCollectionTableViewCell
- let collection = self.collections[indexPath.row]
- cell.titleLabel.text = collection.title
- cell.subTitleLabel.text = "\(collection.fetchResult?.count ?? 0)"
- if let phAsset = collection.getAsset(at: collection.useCameraButton ? 1 : 0) {
- let scale = UIScreen.main.scale
- let size = CGSize(width: 80*scale, height: 80*scale)
- self.photoLibrary.imageAsset(asset: phAsset, size: size, completionBlock: { [weak cell] (image,complete) in
- DispatchQueue.main.async {
- cell?.thumbImageView.image = image
- }
- })
- }
- cell.accessoryType = getfocusedIndex() == indexPath.row ? .checkmark : .none
- cell.selectionStyle = .none
- return cell
- }
- }
|