瀏覽代碼

Add TLPhotoPicker

Marino Faggiana 6 年之前
父節點
當前提交
39ddd62d03
共有 20 個文件被更改,包括 2426 次插入6 次删除
  1. 0 0
      Libraries external/TLPhotoPicker/Classes/.gitkeep
  2. 78 0
      Libraries external/TLPhotoPicker/Classes/TLAlbumPopView.swift
  3. 341 0
      Libraries external/TLPhotoPicker/Classes/TLAssetsCollection.swift
  4. 20 0
      Libraries external/TLPhotoPicker/Classes/TLBundle.swift
  5. 15 0
      Libraries external/TLPhotoPicker/Classes/TLCollectionTableViewCell.swift
  6. 58 0
      Libraries external/TLPhotoPicker/Classes/TLCollectionTableViewCell.xib
  7. 173 0
      Libraries external/TLPhotoPicker/Classes/TLPhotoCollectionViewCell.swift
  8. 144 0
      Libraries external/TLPhotoPicker/Classes/TLPhotoCollectionViewCell.xib
  9. 252 0
      Libraries external/TLPhotoPicker/Classes/TLPhotoLibrary.swift
  10. 999 0
      Libraries external/TLPhotoPicker/Classes/TLPhotosPickerViewController.swift
  11. 220 0
      Libraries external/TLPhotoPicker/Classes/TLPhotosPickerViewController.xib
  12. 19 0
      Libraries external/TLPhotoPicker/TLPhotoPicker/TLPhotoPicker.h
  13. 二進制
      Libraries external/TLPhotoPicker/TLPhotoPickerController.bundle/arrow.png
  14. 二進制
      Libraries external/TLPhotoPicker/TLPhotoPickerController.bundle/camera@3x.png
  15. 二進制
      Libraries external/TLPhotoPicker/TLPhotoPickerController.bundle/insertPhotoMaterial@3x.png
  16. 二進制
      Libraries external/TLPhotoPicker/TLPhotoPickerController.bundle/pop_arrow.png
  17. 二進制
      Libraries external/TLPhotoPicker/TLPhotoPickerController.bundle/video.png
  18. 80 6
      Nextcloud.xcodeproj/project.pbxproj
  19. 6 0
      iOSClient/Main/CCMain.m
  20. 21 0
      iOSClient/Main/NCMainCommon.swift

+ 0 - 0
Libraries external/TLPhotoPicker/Classes/.gitkeep


+ 78 - 0
Libraries external/TLPhotoPicker/Classes/TLAlbumPopView.swift

@@ -0,0 +1,78 @@
+//
+//  TLAlbumPopView.swift
+//  TLPhotosPicker
+//
+//  Created by wade.hawk on 2017. 4. 19..
+//  Copyright © 2017년 wade.hawk. All rights reserved.
+//
+
+import UIKit
+
+protocol PopupViewProtocol: class {
+    var bgView: UIView! { get set }
+    var popupView: UIView! { get set }
+    var originalFrame: CGRect { get set }
+    var show: Bool { get set }
+    func setupPopupFrame()
+}
+
+extension PopupViewProtocol where Self: UIView {
+    fileprivate func getFrame(scale: CGFloat) -> CGRect {
+        var frame = self.originalFrame
+        frame.size.width = frame.size.width * scale
+        frame.size.height = frame.size.height * scale
+        frame.origin.x = self.frame.width/2 - frame.width/2
+        return frame
+    }
+    func setupPopupFrame() {
+        if self.originalFrame != self.popupView.frame {
+            self.originalFrame = self.popupView.frame
+        }
+    }
+    func show(_ show: Bool, duration: TimeInterval = 0.1) {
+        guard self.show != show else { return }
+        self.layer.removeAllAnimations()
+        self.isHidden = false
+        self.popupView.frame = show ? getFrame(scale: 0.1) : self.popupView.frame
+        self.bgView.alpha = show ? 0 : 1
+        UIView.animate(withDuration: duration, animations: {
+            self.bgView.alpha = show ? 1 : 0
+            self.popupView.transform = show ? CGAffineTransform(scaleX: 1.05, y: 1.05) : CGAffineTransform(scaleX: 0.1, y: 0.1)
+            self.popupView.frame = show ? self.getFrame(scale: 1.05) : self.getFrame(scale: 0.1)
+        }) { _ in
+            self.isHidden = show ? false : true
+            UIView.animate(withDuration: duration) {
+                if show {
+                    self.popupView.transform = CGAffineTransform(scaleX: 1, y: 1)
+                    self.popupView.frame = self.originalFrame
+                }
+                self.show = show
+            }
+        }
+    }
+}
+
+open class TLAlbumPopView: UIView,PopupViewProtocol {
+    @IBOutlet var bgView: UIView!
+    @IBOutlet var popupView: UIView!
+    @IBOutlet var popupViewHeight: NSLayoutConstraint!
+    @IBOutlet var tableView: UITableView!
+    @objc var originalFrame = CGRect.zero
+    @objc var show = false
+    
+    deinit {
+//        print("deinit TLAlbumPopView")
+    }
+    
+    override open func awakeFromNib() {
+        super.awakeFromNib()
+        self.popupView.layer.cornerRadius = 5.0
+        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapBgView))
+        self.bgView.addGestureRecognizer(tapGesture)
+        self.tableView.register(UINib(nibName: "TLCollectionTableViewCell", bundle: Bundle(for: TLCollectionTableViewCell.self)), forCellReuseIdentifier: "TLCollectionTableViewCell")
+    }
+    
+    @objc func tapBgView() {
+        self.show(false)
+    }
+}

+ 341 - 0
Libraries external/TLPhotoPicker/Classes/TLAssetsCollection.swift

@@ -0,0 +1,341 @@
+//
+//  TLAssetsCollection.swift
+//  TLPhotosPicker
+//
+//  Created by wade.hawk on 2017. 4. 18..
+//  Copyright © 2017년 wade.hawk. All rights reserved.
+//
+
+import Foundation
+import Photos
+import PhotosUI
+import MobileCoreServices
+
+public struct TLPHAsset {
+    enum CloudDownloadState {
+        case ready, progress, complete, failed
+    }
+    
+    public enum AssetType {
+        case photo, video, livePhoto
+    }
+    
+    public enum ImageExtType: String {
+        case png, jpg, gif, heic
+    }
+    
+    var state = CloudDownloadState.ready
+    public var phAsset: PHAsset? = nil
+    public var selectedOrder: Int = 0
+    public var type: AssetType {
+        get {
+            guard let phAsset = self.phAsset else { return .photo }
+            if phAsset.mediaSubtypes.contains(.photoLive) {
+                return .livePhoto
+            }else if phAsset.mediaType == .video {
+                return .video
+            }else {
+                return .photo
+            }
+        }
+    }
+    
+    public var fullResolutionImage: UIImage? {
+        get {
+            guard let phAsset = self.phAsset else { return nil }
+            return TLPhotoLibrary.fullResolutionImageData(asset: phAsset)
+        }
+    }
+    
+    public func extType() -> ImageExtType {
+        var ext = ImageExtType.png
+        if let fileName = self.originalFileName, let extention = URL(string: fileName)?.pathExtension.lowercased() {
+            ext = ImageExtType(rawValue: extention) ?? .png
+        }
+        return ext
+    }
+    
+    @discardableResult
+    public func cloudImageDownload(progressBlock: @escaping (Double) -> Void, completionBlock:@escaping (UIImage?)-> Void ) -> PHImageRequestID? {
+        guard let phAsset = self.phAsset else { return nil }
+        return TLPhotoLibrary.cloudImageDownload(asset: phAsset, progressBlock: progressBlock, completionBlock: completionBlock)
+    }
+    
+    public var originalFileName: String? {
+        get {
+            guard let phAsset = self.phAsset,let resource = PHAssetResource.assetResources(for: phAsset).first else { return nil }
+            return resource.originalFilename
+        }
+    }
+    
+    public func photoSize(options: PHImageRequestOptions? = nil ,completion: @escaping ((Int)->Void), livePhotoVideoSize: Bool = false) {
+        guard let phAsset = self.phAsset, self.type == .photo else { completion(-1); return }
+        var resource: PHAssetResource? = nil
+        if phAsset.mediaSubtypes.contains(.photoLive) == true, livePhotoVideoSize {
+            resource = PHAssetResource.assetResources(for: phAsset).filter { $0.type == .pairedVideo }.first
+        }else {
+            resource = PHAssetResource.assetResources(for: phAsset).filter { $0.type == .photo }.first
+        }
+        if let fileSize = resource?.value(forKey: "fileSize") as? Int {
+            completion(fileSize)
+        }else {
+            PHImageManager.default().requestImageData(for: phAsset, options: nil) { (data, uti, orientation, info) in
+                var fileSize = -1
+                if let data = data {
+                    let bcf = ByteCountFormatter()
+                    bcf.countStyle = .file
+                    fileSize = data.count
+                }
+                DispatchQueue.main.async {
+                    completion(fileSize)
+                }
+            }
+        }
+    }
+    
+    public func videoSize(options: PHVideoRequestOptions? = nil, completion: @escaping ((Int)->Void)) {
+        guard let phAsset = self.phAsset, self.type == .video else {  completion(-1); return }
+        let resource = PHAssetResource.assetResources(for: phAsset).filter { $0.type == .video }.first
+        if let fileSize = resource?.value(forKey: "fileSize") as? Int {
+            completion(fileSize)
+        }else {
+            PHImageManager.default().requestAVAsset(forVideo: phAsset, options: options) { (avasset, audioMix, info) in
+                func fileSize(_ url: URL?) -> Int? {
+                    do {
+                        guard let fileSize = try url?.resourceValues(forKeys: [.fileSizeKey]).fileSize else { return nil }
+                        return fileSize
+                    }catch { return nil }
+                }
+                var url: URL? = nil
+                if let urlAsset = avasset as? AVURLAsset {
+                    url = urlAsset.url
+                }else if let sandboxKeys = info?["PHImageFileSandboxExtensionTokenKey"] as? String, let path = sandboxKeys.components(separatedBy: ";").last {
+                    url = URL(fileURLWithPath: path)
+                }
+                let size = fileSize(url) ?? -1
+                DispatchQueue.main.async {
+                    completion(size)
+                }
+            }
+        }
+    }
+    
+    func MIMEType(_ url: URL?) -> String? {
+        guard let ext = url?.pathExtension else { return nil }
+        if !ext.isEmpty {
+            let UTIRef = UTTypeCreatePreferredIdentifierForTag("public.filename-extension" as CFString, ext as CFString, nil)
+            let UTI = UTIRef?.takeUnretainedValue()
+            UTIRef?.release()
+            if let UTI = UTI {
+                guard let MIMETypeRef = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType) else { return nil }
+                let MIMEType = MIMETypeRef.takeUnretainedValue()
+                MIMETypeRef.release()
+                return MIMEType as String
+            }
+        }
+        return nil
+    }
+    
+    @discardableResult
+    //convertLivePhotosToPNG
+    // false : If you want mov file at live photos
+    // true  : If you want png file at live photos ( HEIC )
+    public func tempCopyMediaFile(videoRequestOptions: PHVideoRequestOptions? = nil, imageRequestOptions: PHImageRequestOptions? = nil, exportPreset: String = AVAssetExportPresetHighestQuality, convertLivePhotosToJPG: Bool = false, progressBlock:((Double) -> Void)? = nil, completionBlock:@escaping ((URL,String) -> Void)) -> PHImageRequestID? {
+        guard let phAsset = self.phAsset else { return nil }
+        var type: PHAssetResourceType? = nil
+        if phAsset.mediaSubtypes.contains(.photoLive) == true, convertLivePhotosToJPG == false {
+            type = .pairedVideo
+        }else {
+            type = phAsset.mediaType == .video ? .video : .photo
+        }
+        guard let resource = (PHAssetResource.assetResources(for: phAsset).filter{ $0.type == type }).first else { return nil }
+        let fileName = resource.originalFilename
+        var writeURL: URL? = nil
+        if #available(iOS 10.0, *) {
+            writeURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(fileName)")
+        } else {
+            writeURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("\(fileName)")
+        }
+        if (writeURL?.pathExtension.uppercased() == "HEIC" || writeURL?.pathExtension.uppercased() == "HEIF") && convertLivePhotosToJPG {
+            if let fileName2 = writeURL?.deletingPathExtension().lastPathComponent {
+                writeURL?.deleteLastPathComponent()
+                writeURL?.appendPathComponent("\(fileName2).jpg")
+            }
+        }
+        guard let localURL = writeURL,let mimetype = MIMEType(writeURL) else { return nil }
+        switch phAsset.mediaType {
+        case .video:
+            var requestOptions = PHVideoRequestOptions()
+            if let options = videoRequestOptions {
+                requestOptions = options
+            }else {
+                requestOptions.isNetworkAccessAllowed = true
+            }
+            //iCloud download progress
+            requestOptions.progressHandler = { (progress, error, stop, info) in
+                DispatchQueue.main.async {
+                    progressBlock?(progress)
+                }
+            }
+            return PHImageManager.default().requestExportSession(forVideo: phAsset, options: requestOptions, exportPreset: exportPreset) { (session, infoDict) in
+                session?.outputURL = localURL
+                session?.outputFileType = AVFileType.mov
+                session?.exportAsynchronously(completionHandler: {
+                    DispatchQueue.main.async {
+                        completionBlock(localURL, mimetype)
+                    }
+                })
+            }
+        case .image:
+            var requestOptions = PHImageRequestOptions()
+            if let options = imageRequestOptions {
+                requestOptions = options
+            }else {
+                requestOptions.isNetworkAccessAllowed = true
+            }
+            //iCloud download progress
+            requestOptions.progressHandler = { (progress, error, stop, info) in
+                DispatchQueue.main.async {
+                    progressBlock?(progress)
+                }
+            }
+            return PHImageManager.default().requestImageData(for: phAsset, options: requestOptions, resultHandler: { (data, uti, orientation, info) in
+                do {
+                    var data = data
+                    if convertLivePhotosToJPG == true, let imgData = data, let rawImage = UIImage(data: imgData)?.upOrientationImage() {
+                        data = rawImage.jpegData(compressionQuality: 1)
+                    }
+                    try data?.write(to: localURL)
+                    DispatchQueue.main.async {
+                        completionBlock(localURL, mimetype)
+                    }
+                }catch { }
+            })
+        default:
+            return nil
+        }
+    }
+    
+    //Apparently, this method is not be safety to export a video.
+    //There is many way that export a video.
+    //This method was one of them.
+    public func exportVideoFile(options: PHVideoRequestOptions? = nil, progressBlock:((Float) -> Void)? = nil, completionBlock:@escaping ((URL,String) -> Void)) {
+        guard let phAsset = self.phAsset, phAsset.mediaType == .video else { return }
+        var type = PHAssetResourceType.video
+        guard let resource = (PHAssetResource.assetResources(for: phAsset).filter{ $0.type == type }).first else { return }
+        let fileName = resource.originalFilename
+        var writeURL: URL? = nil
+        if #available(iOS 10.0, *) {
+            writeURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(fileName)")
+        } else {
+            writeURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("\(fileName)")
+        }
+        guard let localURL = writeURL,let mimetype = MIMEType(writeURL) else { return }
+        var requestOptions = PHVideoRequestOptions()
+        if let options = options {
+            requestOptions = options
+        }else {
+            requestOptions.isNetworkAccessAllowed = true
+        }
+        //iCloud download progress
+        //options.progressHandler = { (progress, error, stop, info) in
+            
+        //}
+        PHImageManager.default().requestAVAsset(forVideo: phAsset, options: options) { (avasset, avaudioMix, infoDict) in
+            guard let avasset = avasset else { return }
+            let exportSession = AVAssetExportSession.init(asset: avasset, presetName: AVAssetExportPresetHighestQuality)
+            exportSession?.outputURL = localURL
+            exportSession?.outputFileType = AVFileType.mov
+            exportSession?.exportAsynchronously(completionHandler: {
+                completionBlock(localURL,mimetype)
+            })
+            func checkExportSession() {
+                DispatchQueue.global().async { [weak exportSession] in
+                    guard let exportSession = exportSession else { return }
+                    switch exportSession.status {
+                    case .waiting,.exporting:
+                        DispatchQueue.main.async {
+                            progressBlock?(exportSession.progress)
+                        }
+                        Thread.sleep(forTimeInterval: 1)
+                        checkExportSession()
+                    default:
+                        break
+                    }
+                }
+            }
+            checkExportSession()
+        }
+    }
+    
+    init(asset: PHAsset?) {
+        self.phAsset = asset
+    }
+}
+
+extension TLPHAsset: Equatable {
+    public static func ==(lhs: TLPHAsset, rhs: TLPHAsset) -> Bool {
+        guard let lphAsset = lhs.phAsset, let rphAsset = rhs.phAsset else { return false }
+        return lphAsset.localIdentifier == rphAsset.localIdentifier
+    }
+}
+
+struct TLAssetsCollection {
+    var phAssetCollection: PHAssetCollection? = nil
+    var fetchResult: PHFetchResult<PHAsset>? = nil
+    var useCameraButton: Bool = false
+    var recentPosition: CGPoint = CGPoint.zero
+    var title: String
+    var localIdentifier: String
+    var count: Int {
+        get {
+            guard let count = self.fetchResult?.count, count > 0 else { return self.useCameraButton ? 1 : 0 }
+            return count + (self.useCameraButton ? 1 : 0)
+        }
+    }
+    
+    init(collection: PHAssetCollection) {
+        self.phAssetCollection = collection
+        self.title = collection.localizedTitle ?? ""
+        self.localIdentifier = collection.localIdentifier
+    }
+    
+    func getAsset(at index: Int) -> PHAsset? {
+        if self.useCameraButton && index == 0 { return nil }
+        let index = index - (self.useCameraButton ? 1 : 0)
+        guard let result = self.fetchResult, index < result.count else { return nil }
+        return result.object(at: max(index,0))
+    }
+    
+    func getTLAsset(at index: Int) -> TLPHAsset? {
+        if self.useCameraButton && index == 0 { return nil }
+        let index = index - (self.useCameraButton ? 1 : 0)
+        guard let result = self.fetchResult, index < result.count else { return nil }
+        return TLPHAsset(asset: result.object(at: max(index,0)))
+    }
+    
+    func getAssets(at range: CountableClosedRange<Int>) -> [PHAsset]? {
+        let lowerBound = range.lowerBound - (self.useCameraButton ? 1 : 0)
+        let upperBound = range.upperBound - (self.useCameraButton ? 1 : 0)
+        return self.fetchResult?.objects(at: IndexSet(integersIn: max(lowerBound,0)...min(upperBound,count)))
+    }
+    
+    static func ==(lhs: TLAssetsCollection, rhs: TLAssetsCollection) -> Bool {
+        return lhs.localIdentifier == rhs.localIdentifier
+    }
+}
+
+extension UIImage {
+    func upOrientationImage() -> UIImage? {
+        switch imageOrientation {
+        case .up:
+            return self
+        default:
+            UIGraphicsBeginImageContextWithOptions(size, false, scale)
+            draw(in: CGRect(origin: .zero, size: size))
+            let result = UIGraphicsGetImageFromCurrentImageContext()
+            UIGraphicsEndImageContext()
+            return result
+        }
+    }
+}

+ 20 - 0
Libraries external/TLPhotoPicker/Classes/TLBundle.swift

@@ -0,0 +1,20 @@
+//
+//  TLBundle.swift
+//  Pods
+//
+//  Created by wade.hawk on 2017. 5. 9..
+//
+//
+
+import Foundation
+
+class TLBundle {
+    class func podBundleImage(named: String) -> UIImage? {
+        let podBundle = Bundle(for: TLBundle.self)
+        if let url = podBundle.url(forResource: "TLPhotoPickerController", withExtension: "bundle") {
+            let bundle = Bundle(url: url)
+            return UIImage(named: named, in: bundle, compatibleWith: nil)!
+        }
+        return nil
+    }
+}

+ 15 - 0
Libraries external/TLPhotoPicker/Classes/TLCollectionTableViewCell.swift

@@ -0,0 +1,15 @@
+//
+//  TLCollectionTableViewCell.swift
+//  TLPhotosPicker
+//
+//  Created by wade.hawk on 2017. 5. 3..
+//  Copyright © 2017년 wade.hawk. All rights reserved.
+//
+
+import UIKit
+
+class TLCollectionTableViewCell: UITableViewCell {
+    @IBOutlet var thumbImageView: UIImageView!
+    @IBOutlet var titleLabel: UILabel!
+    @IBOutlet var subTitleLabel: UILabel!
+}

+ 58 - 0
Libraries external/TLPhotoPicker/Classes/TLCollectionTableViewCell.xib

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12120" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina4_7" orientation="portrait">
+        <adaptation id="fullscreen"/>
+    </device>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/>
+        <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="TLCollectionTableViewCell" id="9FY-S1-KKQ" customClass="TLCollectionTableViewCell" customModule="TLPhotoPicker" customModuleProvider="target">
+            <rect key="frame" x="0.0" y="0.0" width="375" height="75"/>
+            <autoresizingMask key="autoresizingMask"/>
+            <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="9FY-S1-KKQ" id="UUZ-nI-xH1">
+                <rect key="frame" x="0.0" y="0.0" width="375" height="74.5"/>
+                <autoresizingMask key="autoresizingMask"/>
+                <subviews>
+                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4Dk-kD-sMy">
+                        <rect key="frame" x="5" y="12.5" width="50" height="50"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                    </imageView>
+                    <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="7nv-2U-xMp">
+                        <rect key="frame" x="69" y="19" width="248" height="36.5"/>
+                        <subviews>
+                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Camera Roll" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ES8-Tq-6Wz">
+                                <rect key="frame" x="0.0" y="0.0" width="248" height="17"/>
+                                <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
+                                <nil key="textColor"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="9999" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NuJ-3w-ZAP">
+                                <rect key="frame" x="0.0" y="22" width="248" height="14.5"/>
+                                <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                <color key="textColor" red="0.43137254901960786" green="0.43137254901960786" blue="0.43137254901960786" alpha="1" colorSpace="calibratedRGB"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                        </subviews>
+                    </stackView>
+                </subviews>
+                <constraints>
+                    <constraint firstItem="7nv-2U-xMp" firstAttribute="centerY" secondItem="UUZ-nI-xH1" secondAttribute="centerY" id="E6U-ch-GDV"/>
+                    <constraint firstItem="7nv-2U-xMp" firstAttribute="leading" secondItem="UUZ-nI-xH1" secondAttribute="leadingMargin" constant="61" id="Jsu-Hd-Sg3"/>
+                    <constraint firstAttribute="trailingMargin" secondItem="7nv-2U-xMp" secondAttribute="trailing" constant="50" id="T3U-hj-718"/>
+                </constraints>
+            </tableViewCellContentView>
+            <connections>
+                <outlet property="subTitleLabel" destination="NuJ-3w-ZAP" id="vlD-VJ-e5z"/>
+                <outlet property="thumbImageView" destination="4Dk-kD-sMy" id="f8b-VP-j8s"/>
+                <outlet property="titleLabel" destination="ES8-Tq-6Wz" id="sYW-0b-o03"/>
+            </connections>
+            <point key="canvasLocation" x="9.5" y="-83"/>
+        </tableViewCell>
+    </objects>
+</document>

+ 173 - 0
Libraries external/TLPhotoPicker/Classes/TLPhotoCollectionViewCell.swift

@@ -0,0 +1,173 @@
+//
+//  TLPhotoCollectionViewCell.swift
+//  TLPhotosPicker
+//
+//  Created by wade.hawk on 2017. 5. 3..
+//  Copyright © 2017년 wade.hawk. All rights reserved.
+//
+
+import UIKit
+import PhotosUI
+
+open class TLPlayerView: UIView {
+    @objc open var player: AVPlayer? {
+        get {
+            return playerLayer.player
+        }
+        set {
+            playerLayer.player = newValue
+        }
+    }
+    
+    @objc open var playerLayer: AVPlayerLayer {
+        return layer as! AVPlayerLayer
+    }
+    
+    // Override UIView property
+    override open class var layerClass: AnyClass {
+        return AVPlayerLayer.self
+    }
+}
+
+open class TLPhotoCollectionViewCell: UICollectionViewCell {
+    private var observer: NSObjectProtocol?
+    @IBOutlet open var imageView: UIImageView?
+    @IBOutlet open var playerView: TLPlayerView?
+    @IBOutlet open var livePhotoView: PHLivePhotoView?
+    @IBOutlet open var liveBadgeImageView: UIImageView?
+    @IBOutlet open var durationView: UIView?
+    @IBOutlet open var videoIconImageView: UIImageView?
+    @IBOutlet open var durationLabel: UILabel?
+    @IBOutlet open var indicator: UIActivityIndicatorView?
+    @IBOutlet open var selectedView: UIView?
+    @IBOutlet open var selectedHeight: NSLayoutConstraint?
+    @IBOutlet open var orderLabel: UILabel?
+    @IBOutlet open var orderBgView: UIView?
+    
+    var configure = TLPhotosPickerConfigure() {
+        didSet {
+            self.selectedView?.layer.borderColor = self.configure.selectedColor.cgColor
+            self.orderBgView?.backgroundColor = self.configure.selectedColor
+            self.videoIconImageView?.image = self.configure.videoIcon
+        }
+    }
+    
+    @objc open var isCameraCell = false
+    
+    open var duration: TimeInterval? {
+        didSet {
+            guard let duration = self.duration else { return }
+            self.selectedHeight?.constant = -10
+            self.durationLabel?.text = timeFormatted(timeInterval: duration)
+        }
+    }
+    
+    @objc open var player: AVPlayer? = nil {
+        didSet {
+            if self.configure.autoPlay == false { return }
+            if self.player == nil {
+                self.playerView?.playerLayer.player = nil
+                if let observer = self.observer {
+                    NotificationCenter.default.removeObserver(observer)
+                }
+            }else {
+                self.playerView?.playerLayer.player = self.player
+                self.observer = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem, queue: nil, using: { [weak self] (_) in
+                    DispatchQueue.main.async {
+                        guard let `self` = self else { return }
+                        self.player?.seek(to: CMTime.zero)
+                        self.player?.play()
+                        self.player?.isMuted = self.configure.muteAudio
+                    }
+                })
+            }
+        }
+    }
+    
+    @objc open var selectedAsset: Bool = false {
+        willSet(newValue) {
+            self.selectedView?.isHidden = !newValue
+            self.durationView?.backgroundColor = newValue ? self.configure.selectedColor : UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
+            if !newValue {
+                self.orderLabel?.text = ""
+            }
+        }
+    }
+    
+    @objc open func timeFormatted(timeInterval: TimeInterval) -> String {
+        let seconds: Int = lround(timeInterval)
+        var hour: Int = 0
+        var minute: Int = Int(seconds/60)
+        let second: Int = seconds % 60
+        if minute > 59 {
+            hour = minute / 60
+            minute = minute % 60
+            return String(format: "%d:%d:%02d", hour, minute, second)
+        } else {
+            return String(format: "%d:%02d", minute, second)
+        }
+    }
+    
+    @objc open func popScaleAnim() {
+        UIView.animate(withDuration: 0.1, animations: {
+            self.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
+        }) { _ in
+            UIView.animate(withDuration: 0.1, animations: {
+                self.transform = CGAffineTransform(scaleX: 1, y: 1)
+            })
+        }
+    }
+    
+    @objc open func update(with phAsset: PHAsset) {
+        
+    }
+    
+    @objc open func selectedCell() {
+        
+    }
+    
+    @objc open func willDisplayCell() {
+        
+    }
+    
+    @objc open func endDisplayingCell() {
+        
+    }
+    
+    @objc func stopPlay() {
+        if let player = self.player {
+            player.pause()
+            self.player = nil
+        }
+        self.livePhotoView?.isHidden = true
+        self.livePhotoView?.stopPlayback()
+        self.livePhotoView?.delegate = nil
+    }
+    
+    deinit {
+//        print("deinit TLPhotoCollectionViewCell")
+    }
+    
+    override open func awakeFromNib() {
+        super.awakeFromNib()
+        self.playerView?.playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
+        self.livePhotoView?.isHidden = true
+        self.durationView?.isHidden = true
+        self.selectedView?.isHidden = true
+        self.selectedView?.layer.borderWidth = 10
+        self.selectedView?.layer.cornerRadius = 15
+        self.orderBgView?.layer.cornerRadius = 2
+        self.videoIconImageView?.image = self.configure.videoIcon
+    }
+    
+    override open func prepareForReuse() {
+        super.prepareForReuse()
+        stopPlay()
+        self.livePhotoView?.isHidden = true
+        self.livePhotoView?.delegate = nil
+        self.durationView?.isHidden = true
+        self.durationView?.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
+        self.selectedHeight?.constant = 10
+        self.selectedAsset = false
+    }
+}

+ 144 - 0
Libraries external/TLPhotoPicker/Classes/TLPhotoCollectionViewCell.xib

@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12120" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina4_7" orientation="portrait">
+        <adaptation id="fullscreen"/>
+    </device>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="TLPhotoCollectionViewCell" id="IF3-4e-v0j" customClass="TLPhotoCollectionViewCell" customModule="TLPhotoPicker" customModuleProvider="target">
+            <rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
+            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+            <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
+                <rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
+                <autoresizingMask key="autoresizingMask"/>
+                <subviews>
+                    <imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="0cR-fZ-1bW">
+                        <rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
+                    </imageView>
+                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vfk-Ao-TKR" customClass="PHLivePhotoView">
+                        <rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
+                        <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+                    </view>
+                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="TGh-wt-hcR">
+                        <rect key="frame" x="70" y="70" width="25" height="25"/>
+                        <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+                        <constraints>
+                            <constraint firstAttribute="width" constant="25" id="NMA-SI-Idt"/>
+                            <constraint firstAttribute="height" constant="25" id="XvY-Ee-EA2"/>
+                        </constraints>
+                    </imageView>
+                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="apl-2Q-Hz7" customClass="TLPlayerView" customModule="TLPhotoPicker" customModuleProvider="target">
+                        <rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
+                        <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+                    </view>
+                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d1a-KW-Ta4">
+                        <rect key="frame" x="-5" y="-5" width="110" height="110"/>
+                        <subviews>
+                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hbA-dR-I09">
+                                <rect key="frame" x="75" y="5" width="30" height="30"/>
+                                <subviews>
+                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Weu-ef-IZ5">
+                                        <rect key="frame" x="1.5" y="8" width="25" height="16"/>
+                                        <constraints>
+                                            <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="25" id="YMX-0Z-bfm"/>
+                                        </constraints>
+                                        <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
+                                        <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+                                        <nil key="highlightedColor"/>
+                                    </label>
+                                </subviews>
+                                <color key="backgroundColor" red="0.27843137254901962" green="0.47058823529411764" blue="0.85098039215686272" alpha="1" colorSpace="calibratedRGB"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="30" id="8ln-g5-eHL"/>
+                                    <constraint firstItem="Weu-ef-IZ5" firstAttribute="leading" secondItem="hbA-dR-I09" secondAttribute="leading" constant="1.5" id="Yw3-zp-UZQ"/>
+                                    <constraint firstAttribute="trailing" secondItem="Weu-ef-IZ5" secondAttribute="trailing" constant="3.5" id="jsL-aw-IED"/>
+                                    <constraint firstItem="Weu-ef-IZ5" firstAttribute="centerY" secondItem="hbA-dR-I09" secondAttribute="centerY" constant="1" id="nUh-b3-s9s"/>
+                                </constraints>
+                            </view>
+                        </subviews>
+                        <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+                        <constraints>
+                            <constraint firstAttribute="trailing" secondItem="hbA-dR-I09" secondAttribute="trailing" constant="5" id="hjj-HC-A4B"/>
+                            <constraint firstItem="hbA-dR-I09" firstAttribute="top" secondItem="d1a-KW-Ta4" secondAttribute="top" constant="5" id="xJ1-j7-RX7"/>
+                        </constraints>
+                    </view>
+                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aju-ob-KlZ">
+                        <rect key="frame" x="0.0" y="75" width="100" height="25"/>
+                        <subviews>
+                            <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Uzf-HJ-aUY">
+                                <rect key="frame" x="5" y="2.5" width="20" height="20"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+                            </imageView>
+                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0:02" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zNx-FC-5V2">
+                                <rect key="frame" x="30" y="0.0" width="65" height="25"/>
+                                <constraints>
+                                    <constraint firstAttribute="width" constant="65" id="3hi-82-ovQ"/>
+                                </constraints>
+                                <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                        </subviews>
+                        <color key="backgroundColor" red="0.11109734326601028" green="0.57953345775604248" blue="0.96568840742111206" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstAttribute="height" constant="25" id="DzP-0j-1SD"/>
+                            <constraint firstItem="zNx-FC-5V2" firstAttribute="top" secondItem="aju-ob-KlZ" secondAttribute="top" id="Lim-xf-cbx"/>
+                            <constraint firstAttribute="bottom" secondItem="zNx-FC-5V2" secondAttribute="bottom" id="T0P-EH-uPu"/>
+                            <constraint firstAttribute="trailing" secondItem="zNx-FC-5V2" secondAttribute="trailing" constant="5" id="uhl-Y8-3g5"/>
+                        </constraints>
+                    </view>
+                    <activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="DZc-ag-e55">
+                        <rect key="frame" x="40" y="40" width="20" height="20"/>
+                    </activityIndicatorView>
+                </subviews>
+            </view>
+            <constraints>
+                <constraint firstAttribute="bottom" secondItem="vfk-Ao-TKR" secondAttribute="bottom" id="2gw-cO-5bN"/>
+                <constraint firstAttribute="bottom" secondItem="aju-ob-KlZ" secondAttribute="bottom" id="2wo-Ki-15d"/>
+                <constraint firstAttribute="trailing" secondItem="aju-ob-KlZ" secondAttribute="trailing" id="2zm-kD-GPn"/>
+                <constraint firstAttribute="bottom" secondItem="apl-2Q-Hz7" secondAttribute="bottom" id="3zu-V3-EAd"/>
+                <constraint firstAttribute="trailing" secondItem="0cR-fZ-1bW" secondAttribute="trailing" id="9sd-Ea-EBO"/>
+                <constraint firstAttribute="trailing" secondItem="vfk-Ao-TKR" secondAttribute="trailing" id="BZ0-ZG-uW5"/>
+                <constraint firstAttribute="bottom" secondItem="TGh-wt-hcR" secondAttribute="bottom" constant="5" id="Ba1-Z6-ypz"/>
+                <constraint firstItem="0cR-fZ-1bW" firstAttribute="leading" secondItem="IF3-4e-v0j" secondAttribute="leading" id="DXe-M9-mxX"/>
+                <constraint firstItem="aju-ob-KlZ" firstAttribute="leading" secondItem="IF3-4e-v0j" secondAttribute="leading" id="FDl-UE-XVM"/>
+                <constraint firstItem="apl-2Q-Hz7" firstAttribute="top" secondItem="IF3-4e-v0j" secondAttribute="top" id="JlA-ya-cf3"/>
+                <constraint firstAttribute="trailing" secondItem="apl-2Q-Hz7" secondAttribute="trailing" id="KZg-oh-B11"/>
+                <constraint firstAttribute="trailing" secondItem="TGh-wt-hcR" secondAttribute="trailing" constant="5" id="Meo-q5-HyZ"/>
+                <constraint firstItem="vfk-Ao-TKR" firstAttribute="leading" secondItem="IF3-4e-v0j" secondAttribute="leading" id="NOQ-KU-fHD"/>
+                <constraint firstItem="vfk-Ao-TKR" firstAttribute="top" secondItem="IF3-4e-v0j" secondAttribute="top" id="NrV-wc-c3q"/>
+                <constraint firstItem="d1a-KW-Ta4" firstAttribute="height" secondItem="IF3-4e-v0j" secondAttribute="height" constant="10" id="VMJ-Qp-D4N"/>
+                <constraint firstItem="apl-2Q-Hz7" firstAttribute="leading" secondItem="IF3-4e-v0j" secondAttribute="leading" id="bsL-0J-0Y5"/>
+                <constraint firstItem="d1a-KW-Ta4" firstAttribute="width" secondItem="IF3-4e-v0j" secondAttribute="width" constant="10" id="clS-DI-jvL"/>
+                <constraint firstItem="d1a-KW-Ta4" firstAttribute="top" secondItem="IF3-4e-v0j" secondAttribute="top" constant="-5" id="d3r-HZ-6We"/>
+                <constraint firstItem="DZc-ag-e55" firstAttribute="centerX" secondItem="IF3-4e-v0j" secondAttribute="centerX" id="fnn-1c-fSn"/>
+                <constraint firstItem="DZc-ag-e55" firstAttribute="centerY" secondItem="IF3-4e-v0j" secondAttribute="centerY" id="qZc-2h-nOx"/>
+                <constraint firstItem="d1a-KW-Ta4" firstAttribute="leading" secondItem="IF3-4e-v0j" secondAttribute="leading" constant="-5" id="sOj-ms-Oo3"/>
+                <constraint firstItem="0cR-fZ-1bW" firstAttribute="top" secondItem="IF3-4e-v0j" secondAttribute="top" id="sSC-fd-MoT"/>
+                <constraint firstAttribute="bottom" secondItem="0cR-fZ-1bW" secondAttribute="bottom" id="xzA-tS-EVK"/>
+            </constraints>
+            <connections>
+                <outlet property="durationLabel" destination="zNx-FC-5V2" id="5SU-lD-AGm"/>
+                <outlet property="durationView" destination="aju-ob-KlZ" id="hiP-Ze-cbc"/>
+                <outlet property="imageView" destination="0cR-fZ-1bW" id="N3f-v5-K9b"/>
+                <outlet property="indicator" destination="DZc-ag-e55" id="R3y-XL-0SY"/>
+                <outlet property="liveBadgeImageView" destination="TGh-wt-hcR" id="cry-3P-rLI"/>
+                <outlet property="livePhotoView" destination="vfk-Ao-TKR" id="uvj-l1-wcp"/>
+                <outlet property="orderBgView" destination="hbA-dR-I09" id="cQo-BP-xh1"/>
+                <outlet property="orderLabel" destination="Weu-ef-IZ5" id="B3t-X5-o3a"/>
+                <outlet property="playerView" destination="apl-2Q-Hz7" id="vTI-ie-mcV"/>
+                <outlet property="selectedHeight" destination="VMJ-Qp-D4N" id="uVe-tQ-4q5"/>
+                <outlet property="selectedView" destination="d1a-KW-Ta4" id="c31-4y-72g"/>
+                <outlet property="videoIconImageView" destination="Uzf-HJ-aUY" id="d7e-Bl-181"/>
+            </connections>
+            <point key="canvasLocation" x="32" y="-10"/>
+        </collectionViewCell>
+    </objects>
+</document>

+ 252 - 0
Libraries external/TLPhotoPicker/Classes/TLPhotoLibrary.swift

@@ -0,0 +1,252 @@
+//
+//  TLPhotoLibrary.swift
+//  TLPhotosPicker
+//
+//  Created by wade.hawk on 2017. 5. 3..
+//  Copyright © 2017년 wade.hawk. All rights reserved.
+//
+
+import Foundation
+import Photos
+
+protocol TLPhotoLibraryDelegate: class {
+    func loadCameraRollCollection(collection: TLAssetsCollection)
+    func loadCompleteAllCollection(collections: [TLAssetsCollection])
+}
+
+class TLPhotoLibrary {
+    
+    weak var delegate: TLPhotoLibraryDelegate? = nil
+    
+    lazy var imageManager: PHCachingImageManager = {
+        return PHCachingImageManager()
+    }()
+    
+    deinit {
+        //        print("deinit TLPhotoLibrary")
+    }
+    
+    @discardableResult
+    func livePhotoAsset(asset: PHAsset, size: CGSize = CGSize(width: 720, height: 1280), progressBlock: Photos.PHAssetImageProgressHandler? = nil, completionBlock:@escaping (PHLivePhoto,Bool)-> Void ) -> PHImageRequestID {
+        let options = PHLivePhotoRequestOptions()
+        options.deliveryMode = .opportunistic
+        options.isNetworkAccessAllowed = true
+        options.progressHandler = progressBlock
+        let scale = min(UIScreen.main.scale,2)
+        let targetSize = CGSize(width: size.width*scale, height: size.height*scale)
+        let requestId = self.imageManager.requestLivePhoto(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { (livePhoto, info) in
+            let complete = (info?["PHImageResultIsDegradedKey"] as? Bool) == false
+            if let livePhoto = livePhoto {
+                completionBlock(livePhoto,complete)
+            }
+        }
+        return requestId
+    }
+    
+    @discardableResult
+    func videoAsset(asset: PHAsset, size: CGSize = CGSize(width: 720, height: 1280), progressBlock: Photos.PHAssetImageProgressHandler? = nil, completionBlock:@escaping (AVPlayerItem?, [AnyHashable : Any]?) -> Void ) -> PHImageRequestID {
+        let options = PHVideoRequestOptions()
+        options.isNetworkAccessAllowed = true
+        options.deliveryMode = .automatic
+        options.progressHandler = progressBlock
+        let requestId = self.imageManager.requestPlayerItem(forVideo: asset, options: options, resultHandler: { playerItem, info in
+            completionBlock(playerItem,info)
+        })
+        return requestId
+    }
+    
+    @discardableResult
+    func imageAsset(asset: PHAsset, size: CGSize = CGSize(width: 160, height: 160), options: PHImageRequestOptions? = nil, completionBlock:@escaping (UIImage,Bool)-> Void ) -> PHImageRequestID {
+        var options = options
+        if options == nil {
+            options = PHImageRequestOptions()
+            options?.isSynchronous = false
+            options?.resizeMode = .exact
+            options?.deliveryMode = .opportunistic
+            options?.isNetworkAccessAllowed = true
+        }
+        let scale = min(UIScreen.main.scale,2)
+        let targetSize = CGSize(width: size.width*scale, height: size.height*scale)
+        let requestId = self.imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { image, info in
+            let complete = (info?["PHImageResultIsDegradedKey"] as? Bool) == false
+            if let image = image {
+                completionBlock(image,complete)
+            }
+        }
+        return requestId
+    }
+    
+    func cancelPHImageRequest(requestId: PHImageRequestID) {
+        self.imageManager.cancelImageRequest(requestId)
+    }
+    
+    @discardableResult
+    class func cloudImageDownload(asset: PHAsset, size: CGSize = PHImageManagerMaximumSize, progressBlock: @escaping (Double) -> Void, completionBlock:@escaping (UIImage?)-> Void ) -> PHImageRequestID {
+        let options = PHImageRequestOptions()
+        options.isSynchronous = false
+        options.isNetworkAccessAllowed = true
+        options.deliveryMode = .opportunistic
+        options.version = .current
+        options.resizeMode = .exact
+        options.progressHandler = { (progress,error,stop,info) in
+            progressBlock(progress)
+        }
+        let requestId = PHCachingImageManager().requestImageData(for: asset, options: options) { (imageData, dataUTI, orientation, info) in
+            if let data = imageData,let _ = info {
+                completionBlock(UIImage(data: data))
+            }else{
+                completionBlock(nil)//error
+            }
+        }
+        return requestId
+    }
+    
+    @discardableResult
+    class func fullResolutionImageData(asset: PHAsset) -> UIImage? {
+        let options = PHImageRequestOptions()
+        options.isSynchronous = true
+        options.resizeMode = .none
+        options.isNetworkAccessAllowed = false
+        options.version = .current
+        var image: UIImage? = nil
+        _ = PHCachingImageManager().requestImageData(for: asset, options: options) { (imageData, dataUTI, orientation, info) in
+            if let data = imageData {
+                image = UIImage(data: data)
+            }
+        }
+        return image
+    }
+}
+
+extension PHFetchOptions {
+    func merge(predicate: NSPredicate) {
+        if let storePredicate = self.predicate {
+            self.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [storePredicate, predicate])
+        }else {
+            self.predicate = predicate
+        }
+    }
+}
+
+//MARK: - Load Collection
+extension TLPhotoLibrary {
+    func getOption(configure: TLPhotosPickerConfigure) -> PHFetchOptions {
+        
+        let options = configure.fetchOption ?? PHFetchOptions()
+        options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
+        if let mediaType = configure.mediaType {
+            let mediaTypePredicate = configure.maxVideoDuration != nil && mediaType == PHAssetMediaType.video ? NSPredicate(format: "mediaType = %i AND duration < %f", mediaType.rawValue, configure.maxVideoDuration! + 1) : NSPredicate(format: "mediaType = %i", mediaType.rawValue)
+            options.merge(predicate: mediaTypePredicate)
+        } else if !configure.allowedVideo {
+            let mediaTypePredicate = NSPredicate(format: "mediaType = %i", PHAssetMediaType.image.rawValue)
+            options.merge(predicate: mediaTypePredicate)
+        } else if let duration = configure.maxVideoDuration {
+            let mediaTypePredicate = NSPredicate(format: "mediaType = %i OR (mediaType = %i AND duration < %f)", PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue, duration + 1)
+            options.merge(predicate: mediaTypePredicate)
+        }
+        
+        return options
+    }
+    
+    func fetchResult(collection: TLAssetsCollection?, configure: TLPhotosPickerConfigure) -> PHFetchResult<PHAsset>? {
+        guard let phAssetCollection = collection?.phAssetCollection else { return nil }
+        let options = getOption(configure: configure)
+        return PHAsset.fetchAssets(in: phAssetCollection, options: options)
+    }
+    
+    func fetchCollection(configure: TLPhotosPickerConfigure) {
+        let useCameraButton = configure.usedCameraButton
+        let options = getOption(configure: configure)
+        
+        func getAlbum(subType: PHAssetCollectionSubtype, result: inout [TLAssetsCollection]) {
+            let fetchCollection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: subType, options: nil)
+            var collections = [PHAssetCollection]()
+            fetchCollection.enumerateObjects { (collection, index, _) in 
+                if configure.allowedAlbumCloudShared == false && collection.assetCollectionSubtype == .albumCloudShared {
+                }else {
+                    collections.append(collection)
+                }
+            }
+            for collection in collections {
+                if !result.contains(where: { $0.localIdentifier == collection.localIdentifier }) {
+                    var assetsCollection = TLAssetsCollection(collection: collection)
+                    assetsCollection.fetchResult = PHAsset.fetchAssets(in: collection, options: options)
+                    if assetsCollection.count > 0 {
+                        result.append(assetsCollection)
+                    }
+                }
+            }
+        }
+        
+        @discardableResult
+        func getSmartAlbum(subType: PHAssetCollectionSubtype, useCameraButton: Bool = false, result: inout [TLAssetsCollection]) -> TLAssetsCollection? {
+            let fetchCollection = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: subType, options: nil)
+            if let collection = fetchCollection.firstObject, !result.contains(where: { $0.localIdentifier == collection.localIdentifier }) {
+                var assetsCollection = TLAssetsCollection(collection: collection)
+                assetsCollection.fetchResult = PHAsset.fetchAssets(in: collection, options: options)
+                if assetsCollection.count > 0 || useCameraButton {
+                    result.append(assetsCollection)
+                    return assetsCollection
+                }
+            }
+            return nil
+        }
+        if let fetchCollectionTypes: [(PHAssetCollectionType,PHAssetCollectionSubtype)] = configure.fetchCollectionTypes {
+            DispatchQueue.global(qos: .userInteractive).async { [weak self] in
+                var assetCollections = [TLAssetsCollection]()
+                for (type,subType) in fetchCollectionTypes {
+                    if type == .smartAlbum {
+                        getSmartAlbum(subType: subType, result: &assetCollections)
+                    }else {
+                        getAlbum(subType: subType, result: &assetCollections)
+                    }
+                }
+                DispatchQueue.main.async {
+                    self?.delegate?.loadCompleteAllCollection(collections: assetCollections)
+                }
+            }
+        }else {
+            DispatchQueue.global(qos: .userInteractive).async { [weak self] in
+                var assetCollections = [TLAssetsCollection]()
+                //Camera Roll
+                let camerarollCollection = getSmartAlbum(subType: .smartAlbumUserLibrary, useCameraButton: useCameraButton, result: &assetCollections)
+                if var cameraRoll = camerarollCollection {
+                    cameraRoll.useCameraButton = useCameraButton
+                    assetCollections[0] = cameraRoll
+                    DispatchQueue.main.async {
+                        self?.delegate?.loadCameraRollCollection(collection: cameraRoll)
+                    }
+                }
+                //Selfies
+                getSmartAlbum(subType: .smartAlbumSelfPortraits, result: &assetCollections)
+                //Panoramas
+                getSmartAlbum(subType: .smartAlbumPanoramas, result: &assetCollections)
+                //Favorites
+                getSmartAlbum(subType: .smartAlbumFavorites, result: &assetCollections)
+                //CloudShared
+                getSmartAlbum(subType: .albumCloudShared, result: &assetCollections)
+                //get all another albums
+                getAlbum(subType: .any, result: &assetCollections)
+                if configure.allowedVideo {
+                    //Videos
+                    getSmartAlbum(subType: .smartAlbumVideos, result: &assetCollections)
+                }
+                //Album
+                let albumsResult = PHCollectionList.fetchTopLevelUserCollections(with: nil)
+                albumsResult.enumerateObjects({ (collection, index, stop) -> Void in
+                    guard let collection = collection as? PHAssetCollection else { return }
+                    var assetsCollection = TLAssetsCollection(collection: collection)
+                    assetsCollection.fetchResult = PHAsset.fetchAssets(in: collection, options: options)
+                    if assetsCollection.count > 0, !assetCollections.contains(where: { $0.localIdentifier == collection.localIdentifier }) {
+                        assetCollections.append(assetsCollection)
+                    }
+                })
+                
+                DispatchQueue.main.async {
+                    self?.delegate?.loadCompleteAllCollection(collections: assetCollections)
+                }
+            }
+        }
+    }
+}
+

+ 999 - 0
Libraries external/TLPhotoPicker/Classes/TLPhotosPickerViewController.swift

@@ -0,0 +1,999 @@
+//
+//  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
+    }
+}

+ 220 - 0
Libraries external/TLPhotoPicker/Classes/TLPhotosPickerViewController.xib

@@ -0,0 +1,220 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina4_7" orientation="portrait">
+        <adaptation id="fullscreen"/>
+    </device>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="TLPhotosPickerViewController" customModule="TLPhotoPicker" customModuleProvider="target">
+            <connections>
+                <outlet property="albumPopView" destination="Jcn-hC-Umh" id="c3n-m9-wfd"/>
+                <outlet property="cancelButton" destination="sqJ-Z7-zxj" id="J6u-hz-ePK"/>
+                <outlet property="collectionView" destination="4gR-Bn-quP" id="ZOF-qU-cpd"/>
+                <outlet property="customNavItem" destination="5CU-MZ-p1K" id="ih7-d3-nco"/>
+                <outlet property="doneButton" destination="daA-Ag-vVv" id="P53-fy-Sbh"/>
+                <outlet property="emptyImageView" destination="YDZ-o1-AXT" id="TVN-0v-aQc"/>
+                <outlet property="emptyMessageLabel" destination="7qj-q4-rHC" id="Bcp-Hu-lEY"/>
+                <outlet property="emptyView" destination="HPm-Vc-F86" id="4FT-XL-9ql"/>
+                <outlet property="indicator" destination="AEv-G6-dRI" id="coA-3n-07e"/>
+                <outlet property="navigationBarTopConstraint" destination="IKp-hS-tTy" id="MFH-eP-0tb"/>
+                <outlet property="popArrowImageView" destination="5zn-je-qLx" id="6k9-cH-vcU"/>
+                <outlet property="subTitleArrowImageView" destination="b7w-7R-rco" id="IjY-7S-Zz1"/>
+                <outlet property="subTitleLabel" destination="DON-iU-Cox" id="ZyB-O9-EcR"/>
+                <outlet property="subTitleStackView" destination="kgt-Cn-AXg" id="Vcc-hP-iqe"/>
+                <outlet property="titleLabel" destination="xuG-bc-Oq9" id="2o0-aN-au6"/>
+                <outlet property="titleView" destination="VAz-Py-dsa" id="deY-US-9Jh"/>
+                <outlet property="view" destination="Zyk-dI-msE" id="dxK-gh-unF"/>
+            </connections>
+        </placeholder>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="Zyk-dI-msE">
+            <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <navigationBar contentMode="scaleToFill" translucent="NO" translatesAutoresizingMaskIntoConstraints="NO" id="X8O-Gg-slz">
+                    <rect key="frame" x="0.0" y="20" width="375" height="44"/>
+                    <items>
+                        <navigationItem id="5CU-MZ-p1K">
+                            <nil key="title"/>
+                            <barButtonItem key="leftBarButtonItem" title="Cancel" id="sqJ-Z7-zxj">
+                                <connections>
+                                    <action selector="cancelButtonTap" destination="-1" id="dm0-Ur-I5r"/>
+                                </connections>
+                            </barButtonItem>
+                            <view key="titleView" contentMode="scaleToFill" id="VAz-Py-dsa">
+                                <rect key="frame" x="87.5" y="0.0" width="200" height="44"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <subviews>
+                                    <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="Ql8-7f-9Uk">
+                                        <rect key="frame" x="0.0" y="0.0" width="200" height="44"/>
+                                        <subviews>
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xuG-bc-Oq9">
+                                                <rect key="frame" x="79.5" y="0.0" width="41.5" height="19.5"/>
+                                                <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="16"/>
+                                                <nil key="textColor"/>
+                                                <nil key="highlightedColor"/>
+                                            </label>
+                                            <stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="kgt-Cn-AXg">
+                                                <rect key="frame" x="78" y="19.5" width="44.5" height="24.5"/>
+                                                <subviews>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="DON-iU-Cox">
+                                                        <rect key="frame" x="0.0" y="5.5" width="29.5" height="13.5"/>
+                                                        <fontDescription key="fontDescription" type="system" weight="medium" pointSize="11"/>
+                                                        <nil key="textColor"/>
+                                                        <nil key="highlightedColor"/>
+                                                    </label>
+                                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="b7w-7R-rco">
+                                                        <rect key="frame" x="34.5" y="7.5" width="10" height="10"/>
+                                                        <constraints>
+                                                            <constraint firstAttribute="width" priority="999" constant="10" id="7bM-Da-Q8A"/>
+                                                            <constraint firstAttribute="height" priority="999" constant="10" id="OHz-gp-CQp"/>
+                                                        </constraints>
+                                                    </imageView>
+                                                </subviews>
+                                            </stackView>
+                                        </subviews>
+                                    </stackView>
+                                </subviews>
+                                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+                                <constraints>
+                                    <constraint firstAttribute="trailing" secondItem="Ql8-7f-9Uk" secondAttribute="trailing" id="Vv5-au-j7y"/>
+                                    <constraint firstAttribute="bottom" secondItem="Ql8-7f-9Uk" secondAttribute="bottom" id="XNk-yU-iZJ"/>
+                                    <constraint firstItem="Ql8-7f-9Uk" firstAttribute="top" secondItem="VAz-Py-dsa" secondAttribute="top" id="h9o-f1-oRa"/>
+                                    <constraint firstItem="Ql8-7f-9Uk" firstAttribute="leading" secondItem="VAz-Py-dsa" secondAttribute="leading" id="j8K-5o-bAh"/>
+                                </constraints>
+                            </view>
+                            <barButtonItem key="rightBarButtonItem" title="Done" id="daA-Ag-vVv">
+                                <connections>
+                                    <action selector="doneButtonTap" destination="-1" id="BzF-rn-rn4"/>
+                                </connections>
+                            </barButtonItem>
+                        </navigationItem>
+                    </items>
+                </navigationBar>
+                <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="4gR-Bn-quP">
+                    <rect key="frame" x="0.0" y="64" width="375" height="603"/>
+                    <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+                    <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="5" minimumInteritemSpacing="5" id="VDa-Pp-VBq">
+                        <size key="itemSize" width="50" height="50"/>
+                        <size key="headerReferenceSize" width="0.0" height="0.0"/>
+                        <size key="footerReferenceSize" width="0.0" height="0.0"/>
+                        <inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
+                    </collectionViewFlowLayout>
+                    <connections>
+                        <outlet property="dataSource" destination="-1" id="6fE-SF-z6F"/>
+                        <outlet property="delegate" destination="-1" id="pjZ-sM-fyY"/>
+                    </connections>
+                </collectionView>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HPm-Vc-F86">
+                    <rect key="frame" x="0.0" y="64" width="375" height="603"/>
+                    <subviews>
+                        <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="gi6-Sw-3Zf">
+                            <rect key="frame" x="137.5" y="239" width="100" height="125.5"/>
+                            <subviews>
+                                <imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="YDZ-o1-AXT">
+                                    <rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
+                                    <constraints>
+                                        <constraint firstAttribute="width" priority="999" constant="100" id="VUj-VB-7Kr"/>
+                                        <constraint firstAttribute="height" priority="999" constant="100" id="jBo-WN-gZr"/>
+                                    </constraints>
+                                </imageView>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7qj-q4-rHC">
+                                    <rect key="frame" x="29" y="105" width="42" height="20.5"/>
+                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                                    <nil key="textColor"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                            </subviews>
+                        </stackView>
+                    </subviews>
+                    <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <constraints>
+                        <constraint firstItem="gi6-Sw-3Zf" firstAttribute="centerX" secondItem="HPm-Vc-F86" secondAttribute="centerX" id="FpA-96-esN"/>
+                        <constraint firstItem="gi6-Sw-3Zf" firstAttribute="centerY" secondItem="HPm-Vc-F86" secondAttribute="centerY" id="L0c-BG-65l"/>
+                    </constraints>
+                </view>
+                <activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="AEv-G6-dRI">
+                    <rect key="frame" x="177" y="323" width="20" height="20"/>
+                </activityIndicatorView>
+                <view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Jcn-hC-Umh" customClass="TLAlbumPopView" customModule="TLPhotoPicker" customModuleProvider="target">
+                    <rect key="frame" x="0.0" y="64" width="375" height="603"/>
+                    <subviews>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="GKD-We-AdT">
+                            <rect key="frame" x="0.0" y="0.0" width="375" height="603"/>
+                            <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.29999999999999999" colorSpace="calibratedRGB"/>
+                        </view>
+                        <view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eL2-gJ-b87">
+                            <rect key="frame" x="1" y="17" width="373" height="130"/>
+                            <subviews>
+                                <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" style="plain" separatorStyle="default" rowHeight="75" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="grw-Nk-Sxr">
+                                    <rect key="frame" x="10" y="0.0" width="363" height="130"/>
+                                    <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+                                </tableView>
+                            </subviews>
+                            <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+                            <constraints>
+                                <constraint firstItem="grw-Nk-Sxr" firstAttribute="top" secondItem="eL2-gJ-b87" secondAttribute="top" id="DAx-an-6Qt"/>
+                                <constraint firstAttribute="height" constant="130" id="cet-yf-3jU"/>
+                                <constraint firstAttribute="trailing" secondItem="grw-Nk-Sxr" secondAttribute="trailing" id="nM5-IG-MPJ"/>
+                                <constraint firstItem="grw-Nk-Sxr" firstAttribute="leading" secondItem="eL2-gJ-b87" secondAttribute="leading" constant="10" id="o4b-cS-L3T"/>
+                                <constraint firstAttribute="bottom" secondItem="grw-Nk-Sxr" secondAttribute="bottom" id="sPK-1G-qLo"/>
+                            </constraints>
+                        </view>
+                        <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="5zn-je-qLx">
+                            <rect key="frame" x="180" y="10" width="14" height="7"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="7" id="eZN-nB-EHu"/>
+                                <constraint firstAttribute="width" constant="14" id="hWo-ji-iBX"/>
+                            </constraints>
+                        </imageView>
+                    </subviews>
+                    <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+                    <constraints>
+                        <constraint firstItem="GKD-We-AdT" firstAttribute="top" secondItem="Jcn-hC-Umh" secondAttribute="top" id="83h-bg-tAJ"/>
+                        <constraint firstAttribute="trailing" secondItem="eL2-gJ-b87" secondAttribute="trailing" constant="1" id="IMh-Wh-897"/>
+                        <constraint firstItem="5zn-je-qLx" firstAttribute="centerX" secondItem="Jcn-hC-Umh" secondAttribute="centerX" id="LpE-mT-MxA"/>
+                        <constraint firstItem="eL2-gJ-b87" firstAttribute="top" secondItem="Jcn-hC-Umh" secondAttribute="top" constant="17" id="XtI-9R-CFm"/>
+                        <constraint firstItem="GKD-We-AdT" firstAttribute="leading" secondItem="Jcn-hC-Umh" secondAttribute="leading" id="krq-3L-sxU"/>
+                        <constraint firstItem="eL2-gJ-b87" firstAttribute="top" secondItem="5zn-je-qLx" secondAttribute="bottom" id="tM4-Tb-JsI"/>
+                        <constraint firstItem="eL2-gJ-b87" firstAttribute="leading" secondItem="Jcn-hC-Umh" secondAttribute="leading" constant="1" id="v5O-lt-eKe"/>
+                        <constraint firstAttribute="bottom" secondItem="GKD-We-AdT" secondAttribute="bottom" id="x4Q-bm-MrY"/>
+                        <constraint firstAttribute="trailing" secondItem="GKD-We-AdT" secondAttribute="trailing" id="xdF-2b-pOS"/>
+                    </constraints>
+                    <connections>
+                        <outlet property="bgView" destination="GKD-We-AdT" id="ymr-tp-YBW"/>
+                        <outlet property="popupView" destination="eL2-gJ-b87" id="xgf-xj-Rd4"/>
+                        <outlet property="popupViewHeight" destination="cet-yf-3jU" id="OS3-MZ-s9c"/>
+                        <outlet property="tableView" destination="grw-Nk-Sxr" id="Y3k-8m-iJp"/>
+                    </connections>
+                </view>
+            </subviews>
+            <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <constraints>
+                <constraint firstItem="HPm-Vc-F86" firstAttribute="bottom" secondItem="HLR-WT-D3I" secondAttribute="bottom" id="0AS-35-SWm"/>
+                <constraint firstItem="AEv-G6-dRI" firstAttribute="centerX" secondItem="Zyk-dI-msE" secondAttribute="centerX" id="44C-fd-MVc"/>
+                <constraint firstItem="X8O-Gg-slz" firstAttribute="leading" secondItem="Zyk-dI-msE" secondAttribute="leading" id="4Ud-fp-qD2"/>
+                <constraint firstItem="4gR-Bn-quP" firstAttribute="top" secondItem="X8O-Gg-slz" secondAttribute="bottom" id="CZx-xl-GLa"/>
+                <constraint firstItem="X8O-Gg-slz" firstAttribute="top" secondItem="HLR-WT-D3I" secondAttribute="top" id="IKp-hS-tTy"/>
+                <constraint firstItem="Jcn-hC-Umh" firstAttribute="top" secondItem="X8O-Gg-slz" secondAttribute="bottom" id="KdD-nT-6tf"/>
+                <constraint firstItem="HPm-Vc-F86" firstAttribute="leading" secondItem="HLR-WT-D3I" secondAttribute="leading" id="M3L-CU-HdQ"/>
+                <constraint firstAttribute="trailing" secondItem="4gR-Bn-quP" secondAttribute="trailing" id="M6b-2G-2m1"/>
+                <constraint firstAttribute="trailing" secondItem="Jcn-hC-Umh" secondAttribute="trailing" id="Njh-ZO-lnq"/>
+                <constraint firstAttribute="bottom" secondItem="Jcn-hC-Umh" secondAttribute="bottom" id="NxH-d8-b65"/>
+                <constraint firstItem="HPm-Vc-F86" firstAttribute="top" secondItem="X8O-Gg-slz" secondAttribute="bottom" id="YyG-QW-0ZP"/>
+                <constraint firstItem="AEv-G6-dRI" firstAttribute="centerY" secondItem="Zyk-dI-msE" secondAttribute="centerY" id="aLU-u9-ALA"/>
+                <constraint firstItem="Jcn-hC-Umh" firstAttribute="leading" secondItem="Zyk-dI-msE" secondAttribute="leading" id="aY7-Ml-cd3"/>
+                <constraint firstItem="HPm-Vc-F86" firstAttribute="trailing" secondItem="HLR-WT-D3I" secondAttribute="trailing" id="aoA-8G-xDA"/>
+                <constraint firstItem="4gR-Bn-quP" firstAttribute="leading" secondItem="Zyk-dI-msE" secondAttribute="leading" id="kb7-vy-yTu"/>
+                <constraint firstItem="4gR-Bn-quP" firstAttribute="bottom" secondItem="HLR-WT-D3I" secondAttribute="bottom" id="qqE-w2-Tsc"/>
+                <constraint firstAttribute="trailing" secondItem="X8O-Gg-slz" secondAttribute="trailing" id="yWV-L2-0f4"/>
+            </constraints>
+            <viewLayoutGuide key="safeArea" id="HLR-WT-D3I"/>
+            <point key="canvasLocation" x="33.5" y="53.5"/>
+        </view>
+    </objects>
+</document>

+ 19 - 0
Libraries external/TLPhotoPicker/TLPhotoPicker/TLPhotoPicker.h

@@ -0,0 +1,19 @@
+//
+//  TLPhotoPicker.h
+//  TLPhotoPicker
+//
+//  Created by wade.hawk on 2018. 3. 31..
+//  Copyright © 2018년 wade.hawk. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+//! Project version number for TLPhotoPicker.
+FOUNDATION_EXPORT double TLPhotoPickerVersionNumber;
+
+//! Project version string for TLPhotoPicker.
+FOUNDATION_EXPORT const unsigned char TLPhotoPickerVersionString[];
+
+// In this header, you should import all the public headers of your framework using statements like #import <TLPhotoPicker/PublicHeader.h>
+
+

二進制
Libraries external/TLPhotoPicker/TLPhotoPickerController.bundle/arrow.png


二進制
Libraries external/TLPhotoPicker/TLPhotoPickerController.bundle/camera@3x.png


二進制
Libraries external/TLPhotoPicker/TLPhotoPickerController.bundle/insertPhotoMaterial@3x.png


二進制
Libraries external/TLPhotoPicker/TLPhotoPickerController.bundle/pop_arrow.png


二進制
Libraries external/TLPhotoPicker/TLPhotoPickerController.bundle/video.png


+ 80 - 6
Nextcloud.xcodeproj/project.pbxproj

@@ -516,6 +516,18 @@
 		F78F74342163757000C2ADAD /* NCTrash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F78F74332163757000C2ADAD /* NCTrash.storyboard */; };
 		F78F74362163781100C2ADAD /* NCTrash.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78F74352163781100C2ADAD /* NCTrash.swift */; };
 		F790110E21415BF600D7B136 /* NCViewerRichdocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = F790110D21415BF600D7B136 /* NCViewerRichdocument.swift */; };
+		F7960D0A219823C400459AE2 /* .gitkeep in Resources */ = {isa = PBXBuildFile; fileRef = F7960CFC219823C300459AE2 /* .gitkeep */; };
+		F7960D0B219823C400459AE2 /* TLPhotoLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7960CFD219823C300459AE2 /* TLPhotoLibrary.swift */; };
+		F7960D0C219823C400459AE2 /* TLCollectionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7960CFE219823C300459AE2 /* TLCollectionTableViewCell.swift */; };
+		F7960D0D219823C400459AE2 /* TLCollectionTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7960CFF219823C300459AE2 /* TLCollectionTableViewCell.xib */; };
+		F7960D0E219823C400459AE2 /* TLBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7960D00219823C300459AE2 /* TLBundle.swift */; };
+		F7960D0F219823C400459AE2 /* TLAlbumPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7960D01219823C300459AE2 /* TLAlbumPopView.swift */; };
+		F7960D10219823C400459AE2 /* TLPhotoCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7960D02219823C300459AE2 /* TLPhotoCollectionViewCell.swift */; };
+		F7960D11219823C400459AE2 /* TLPhotoCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7960D03219823C300459AE2 /* TLPhotoCollectionViewCell.xib */; };
+		F7960D12219823C400459AE2 /* TLPhotosPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7960D04219823C300459AE2 /* TLPhotosPickerViewController.swift */; };
+		F7960D13219823C400459AE2 /* TLAssetsCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7960D05219823C300459AE2 /* TLAssetsCollection.swift */; };
+		F7960D14219823C400459AE2 /* TLPhotosPickerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7960D06219823C300459AE2 /* TLPhotosPickerViewController.xib */; };
+		F7960D15219823C400459AE2 /* TLPhotoPickerController.bundle in Resources */ = {isa = PBXBuildFile; fileRef = F7960D09219823C300459AE2 /* TLPhotoPickerController.bundle */; };
 		F79630EE215527D40015EEA5 /* NCViewerMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79630ED215527D40015EEA5 /* NCViewerMedia.swift */; };
 		F79A65C32191D90F00FF6DCC /* NCSelect.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F79A65C22191D90F00FF6DCC /* NCSelect.storyboard */; };
 		F79A65C62191D95E00FF6DCC /* NCSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79A65C52191D95E00FF6DCC /* NCSelect.swift */; };
@@ -1425,6 +1437,19 @@
 		F7956FC91B4886E60085DEA3 /* CCUploadFromOtherUpp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCUploadFromOtherUpp.h; sourceTree = "<group>"; };
 		F7956FCA1B4886E60085DEA3 /* CCUploadFromOtherUpp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCUploadFromOtherUpp.m; sourceTree = "<group>"; };
 		F7956FCB1B4886E60085DEA3 /* CCUploadFromOtherUpp.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = CCUploadFromOtherUpp.storyboard; sourceTree = "<group>"; };
+		F7960CFC219823C300459AE2 /* .gitkeep */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .gitkeep; sourceTree = "<group>"; };
+		F7960CFD219823C300459AE2 /* TLPhotoLibrary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLPhotoLibrary.swift; sourceTree = "<group>"; };
+		F7960CFE219823C300459AE2 /* TLCollectionTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLCollectionTableViewCell.swift; sourceTree = "<group>"; };
+		F7960CFF219823C300459AE2 /* TLCollectionTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TLCollectionTableViewCell.xib; sourceTree = "<group>"; };
+		F7960D00219823C300459AE2 /* TLBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLBundle.swift; sourceTree = "<group>"; };
+		F7960D01219823C300459AE2 /* TLAlbumPopView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAlbumPopView.swift; sourceTree = "<group>"; };
+		F7960D02219823C300459AE2 /* TLPhotoCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLPhotoCollectionViewCell.swift; sourceTree = "<group>"; };
+		F7960D03219823C300459AE2 /* TLPhotoCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TLPhotoCollectionViewCell.xib; sourceTree = "<group>"; };
+		F7960D04219823C300459AE2 /* TLPhotosPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLPhotosPickerViewController.swift; sourceTree = "<group>"; };
+		F7960D05219823C300459AE2 /* TLAssetsCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLAssetsCollection.swift; sourceTree = "<group>"; };
+		F7960D06219823C300459AE2 /* TLPhotosPickerViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TLPhotosPickerViewController.xib; sourceTree = "<group>"; };
+		F7960D08219823C300459AE2 /* TLPhotoPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLPhotoPicker.h; sourceTree = "<group>"; };
+		F7960D09219823C300459AE2 /* TLPhotoPickerController.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = TLPhotoPickerController.bundle; sourceTree = "<group>"; };
 		F79630ED215527D40015EEA5 /* NCViewerMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCViewerMedia.swift; sourceTree = "<group>"; };
 		F79A65C22191D90F00FF6DCC /* NCSelect.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCSelect.storyboard; sourceTree = "<group>"; };
 		F79A65C52191D95E00FF6DCC /* NCSelect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCSelect.swift; sourceTree = "<group>"; };
@@ -1908,6 +1933,7 @@
 				F7A55417204EF8AF008468EC /* TOScrollBar */,
 				F7622F6F2175FCC0000383FF /* Sheeeeeeeeet */,
 				F75AE3C51E9D12900088BB09 /* SwiftyAvatar */,
+				F7960CFA219823C300459AE2 /* TLPhotoPicker */,
 				F73CCE271DC13798007E38D8 /* UICKeyChainStore */,
 				F70F05561C889184008DAB36 /* UIImage+animatedGIF */,
 			);
@@ -2999,6 +3025,42 @@
 			path = Trash;
 			sourceTree = "<group>";
 		};
+		F7960CFA219823C300459AE2 /* TLPhotoPicker */ = {
+			isa = PBXGroup;
+			children = (
+				F7960CFB219823C300459AE2 /* Classes */,
+				F7960D07219823C300459AE2 /* TLPhotoPicker */,
+				F7960D09219823C300459AE2 /* TLPhotoPickerController.bundle */,
+			);
+			path = TLPhotoPicker;
+			sourceTree = "<group>";
+		};
+		F7960CFB219823C300459AE2 /* Classes */ = {
+			isa = PBXGroup;
+			children = (
+				F7960CFC219823C300459AE2 /* .gitkeep */,
+				F7960CFD219823C300459AE2 /* TLPhotoLibrary.swift */,
+				F7960CFE219823C300459AE2 /* TLCollectionTableViewCell.swift */,
+				F7960CFF219823C300459AE2 /* TLCollectionTableViewCell.xib */,
+				F7960D00219823C300459AE2 /* TLBundle.swift */,
+				F7960D01219823C300459AE2 /* TLAlbumPopView.swift */,
+				F7960D02219823C300459AE2 /* TLPhotoCollectionViewCell.swift */,
+				F7960D03219823C300459AE2 /* TLPhotoCollectionViewCell.xib */,
+				F7960D04219823C300459AE2 /* TLPhotosPickerViewController.swift */,
+				F7960D05219823C300459AE2 /* TLAssetsCollection.swift */,
+				F7960D06219823C300459AE2 /* TLPhotosPickerViewController.xib */,
+			);
+			path = Classes;
+			sourceTree = "<group>";
+		};
+		F7960D07219823C300459AE2 /* TLPhotoPicker */ = {
+			isa = PBXGroup;
+			children = (
+				F7960D08219823C300459AE2 /* TLPhotoPicker.h */,
+			);
+			path = TLPhotoPicker;
+			sourceTree = "<group>";
+		};
 		F79630EC215526B60015EEA5 /* Viewer */ = {
 			isa = PBXGroup;
 			children = (
@@ -3905,6 +3967,7 @@
 				F7F54CFA1E5B14C700E19C62 /* UIBarButtonItemArrowLeft.png in Resources */,
 				F7FCFFD81D70798C000E6E29 /* CCPeekPop.storyboard in Resources */,
 				F7F54CF61E5B14C700E19C62 /* PlayButtonOverlayLarge@3x.png in Resources */,
+				F7960D11219823C400459AE2 /* TLPhotoCollectionViewCell.xib in Resources */,
 				F7D423371F0596AC009C9782 /* AppIcon-180.png in Resources */,
 				F7D423401F0596AC009C9782 /* Reader-Email@3x.png in Resources */,
 				F7D423391F0596AC009C9782 /* Reader-Button-H@2x.png in Resources */,
@@ -3921,6 +3984,7 @@
 				F7F54CE81E5B14C700E19C62 /* ImageSelectedOff.png in Resources */,
 				F7D4234B1F0596AC009C9782 /* Reader-Print@2x.png in Resources */,
 				F762CB971EACB84400B38484 /* icon-info.png in Resources */,
+				F7960D14219823C400459AE2 /* TLPhotosPickerViewController.xib in Resources */,
 				F7169A1E1EE590930086BD69 /* NCSharesCell.xib in Resources */,
 				F73D71641F2674A400E233EB /* NCText.storyboard in Resources */,
 				F7226EDC1EE4089300EBECB1 /* Main.storyboard in Resources */,
@@ -3968,16 +4032,19 @@
 				F7C525A21E3B6DA800FFE02C /* CCNotification.storyboard in Resources */,
 				F7F54CEB1E5B14C700E19C62 /* ImageSelectedOn.png in Resources */,
 				F72D1003210B67CE009C96B7 /* GoogleService-Info.plist in Resources */,
+				F7960D0D219823C400459AE2 /* TLCollectionTableViewCell.xib in Resources */,
 				F762CB951EACB84400B38484 /* icon-error.png in Resources */,
 				F7F54CF01E5B14C700E19C62 /* ImageSelectedSmallOff@3x.png in Resources */,
 				F73B4EF01F470D9100BBEE4B /* CMakeLists.txt in Resources */,
 				F7F54CEC1E5B14C700E19C62 /* ImageSelectedOn@2x.png in Resources */,
 				F73B4F151F470D9100BBEE4B /* symbols.cmake in Resources */,
+				F7960D0A219823C400459AE2 /* .gitkeep in Resources */,
 				F7F54CF21E5B14C700E19C62 /* ImageSelectedSmallOn@2x.png in Resources */,
 				F7D4233A1F0596AC009C9782 /* Reader-Button-H@3x.png in Resources */,
 				F7D423341F0596AC009C9782 /* AppIcon-120.png in Resources */,
 				F7D4234F1F0596AC009C9782 /* Reader-Thumbs@3x.png in Resources */,
 				F7F54CE51E5B14C700E19C62 /* ImageError.png in Resources */,
+				F7960D15219823C400459AE2 /* TLPhotoPickerController.bundle in Resources */,
 				F7D423381F0596AC009C9782 /* Reader-Button-H.png in Resources */,
 				F7F54CFC1E5B14C700E19C62 /* UIBarButtonItemArrowLeft@3x.png in Resources */,
 				F77B0F7D1D118A16002130FE /* Images.xcassets in Resources */,
@@ -4272,6 +4339,7 @@
 				F7F54D0C1E5B14C800E19C62 /* MWTapDetectingView.m in Sources */,
 				F712ACAB2175E56F0061158E /* NSBundle+CTAssetsPickerController.m in Sources */,
 				F758B444212C516300515F55 /* AVCaptureVideoOrientation+Utils.swift in Sources */,
+				F7960D0B219823C400459AE2 /* TLPhotoLibrary.swift in Sources */,
 				F758B3E4212C4A6C00515F55 /* PDFGenerator.swift in Sources */,
 				F712AC982175E56F0061158E /* CTAssetsPageViewController.m in Sources */,
 				F7B1FBC61E72E3D1001781FE /* SwiftModalWebVC.swift in Sources */,
@@ -4324,6 +4392,7 @@
 				F7622FAF2175FCC0000383FF /* ActionSheet.swift in Sources */,
 				F77B0E231D118A16002130FE /* CCSharePermissionOC.m in Sources */,
 				F7622FD12175FCC0000383FF /* ActionSheetSingleSelectItem.swift in Sources */,
+				F7960D12219823C400459AE2 /* TLPhotosPickerViewController.swift in Sources */,
 				F762CAF81EACB66200B38484 /* XLFormButtonCell.m in Sources */,
 				F7CA1ED120E7E3FE002CC65E /* PKCircleProgressView.m in Sources */,
 				F712ACA82175E56F0061158E /* PHAssetCollection+CTAssetsPickerController.m in Sources */,
@@ -4423,6 +4492,7 @@
 				F77B0E671D118A16002130FE /* Reachability.m in Sources */,
 				F762CB121EACB66200B38484 /* UIView+XLFormAdditions.m in Sources */,
 				F70BFC7420E0FA7D00C67599 /* NCUtility.swift in Sources */,
+				F7960D0F219823C400459AE2 /* TLAlbumPopView.swift in Sources */,
 				F73CC06F1E813DFF006E3047 /* BKPasscodeInputView.m in Sources */,
 				F754EECC21772B6100BB1CDF /* SectionHeader.swift in Sources */,
 				F73CC0751E813DFF006E3047 /* BKPasscodeViewController.m in Sources */,
@@ -4480,6 +4550,7 @@
 				F762CAF71EACB66200B38484 /* XLFormBaseCell.m in Sources */,
 				F70022E01EC4C9100080073F /* OCXMLListParser.m in Sources */,
 				F758B43E212C516300515F55 /* ImageScannerController.swift in Sources */,
+				F7960D0C219823C400459AE2 /* TLCollectionTableViewCell.swift in Sources */,
 				F70022B31EC4C9100080073F /* OCActivity.m in Sources */,
 				F70022D41EC4C9100080073F /* NSDate+ISO8601.m in Sources */,
 				F78964AD1EBB576C00403E13 /* JDStatusBarNotification.m in Sources */,
@@ -4491,6 +4562,7 @@
 				F758B447212C516300515F55 /* CGRect+Utils.swift in Sources */,
 				F762CB0A1EACB66200B38484 /* XLFormDescriptor.m in Sources */,
 				F7D4238C1F0596C6009C9782 /* UIXToolbarView.m in Sources */,
+				F7960D0E219823C400459AE2 /* TLBundle.swift in Sources */,
 				F726EEEC1FED1C820030B9C8 /* NCEndToEndInitialize.swift in Sources */,
 				F7622FBB2175FCC0000383FF /* ActionSheetSelectItemAppearance.swift in Sources */,
 				F7622FB12175FCC0000383FF /* ActionSheetPopoverApperance.swift in Sources */,
@@ -4501,6 +4573,7 @@
 				F762CAFB1EACB66200B38484 /* XLFormDatePickerCell.m in Sources */,
 				F762CB0F1EACB66200B38484 /* NSObject+XLFormAdditions.m in Sources */,
 				F7B2DEF01F976854007CF4D2 /* NYMnemonic.m in Sources */,
+				F7960D10219823C400459AE2 /* TLPhotoCollectionViewCell.swift in Sources */,
 				F7622FD82175FCC0000383FF /* ActionSheetCollectionItemCell.swift in Sources */,
 				F762CB891EACB81000B38484 /* REMenuItem.m in Sources */,
 				F7D423791F0596C6009C9782 /* CGPDFDocument.m in Sources */,
@@ -4524,6 +4597,7 @@
 				F7CA1ED220E7E3FE002CC65E /* PKCircleView.m in Sources */,
 				F7F54D0D1E5B14C800E19C62 /* MWZoomingScrollView.m in Sources */,
 				F762CB0B1EACB66200B38484 /* XLFormRowDescriptor.m in Sources */,
+				F7960D13219823C400459AE2 /* TLAssetsCollection.swift in Sources */,
 				F7169A1C1EE590930086BD69 /* NCShares.m in Sources */,
 				F77B0EC61D118A16002130FE /* CCCellMain.m in Sources */,
 				F758B3E6212C4A6C00515F55 /* PDFPage.swift in Sources */,
@@ -4701,7 +4775,7 @@
 				);
 				HEADER_SEARCH_PATHS = "\"Libraries external\"/**";
 				INFOPLIST_FILE = "$(SRCROOT)/iOSClient/Brand/Share.plist";
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.1;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
 				LIBRARY_SEARCH_PATHS = "\"Libraries external\"/**";
 				OTHER_LDFLAGS = "-ObjC";
@@ -4743,7 +4817,7 @@
 				);
 				HEADER_SEARCH_PATHS = "\"Libraries external\"/**";
 				INFOPLIST_FILE = "$(SRCROOT)/iOSClient/Brand/Share.plist";
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.1;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
 				LIBRARY_SEARCH_PATHS = "\"Libraries external\"/**";
 				OTHER_LDFLAGS = "-ObjC";
@@ -4978,7 +5052,7 @@
 				);
 				HEADER_SEARCH_PATHS = "\"Libraries external\"/**";
 				INFOPLIST_FILE = "$(SRCROOT)/iOSClient/Brand/iOSClient.plist";
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.1;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
 				LIBRARY_SEARCH_PATHS = "\"Libraries external/openssl\"";
 				OTHER_LDFLAGS = "-ObjC";
@@ -5026,7 +5100,7 @@
 				);
 				HEADER_SEARCH_PATHS = "\"Libraries external\"/**";
 				INFOPLIST_FILE = "$(SRCROOT)/iOSClient/Brand/iOSClient.plist";
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.1;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
 				LIBRARY_SEARCH_PATHS = "\"Libraries external/openssl\"";
 				OTHER_LDFLAGS = "-ObjC";
@@ -5089,7 +5163,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.1;
 				MTL_ENABLE_DEBUG_INFO = YES;
 				ONLY_ACTIVE_ARCH = YES;
 				OTHER_LDFLAGS = (
@@ -5141,7 +5215,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.1;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				OTHER_LDFLAGS = (
 					"-Obj-C",

+ 6 - 0
iOSClient/Main/CCMain.m

@@ -793,6 +793,12 @@
 
 - (void)openAssetsPickerController
 {
+    [[NCMainCommon sharedInstance] openPhotosPickerViewController:self phAssets:^{
+        NSLog(@"ciao");
+    }];
+    
+    return;
+    
     CTAssetCheckmark *checkmark = [CTAssetCheckmark appearance];
     checkmark.tintColor = [NCBrandColor sharedInstance].brandElement;
     [checkmark setMargin:0.0 forVerticalEdge:NSLayoutAttributeRight horizontalEdge:NSLayoutAttributeTop];

+ 21 - 0
iOSClient/Main/NCMainCommon.swift

@@ -853,6 +853,27 @@ class NCMainCommon: NSObject {
         self.reloadDatasource(ServerUrl: serverUrl, fileID: nil, action: k_action_NULL)
         self.appDelegate.activeMedia.reloadDatasource(nil, action: Int(k_action_NULL))
     }
+    
+    @objc func openPhotosPickerViewController(_ sourceViewController: UIViewController, phAssets: @escaping () -> ()) {
+     
+        var selectedAssets = [TLPHAsset]()
+        
+        let viewController = TLPhotosPickerViewController(withTLPHAssets: { [weak self] (assets) in // TLAssets
+            selectedAssets = assets
+            phAssets()
+            }, didCancel: nil)
+        viewController.didExceedMaximumNumberOfSelection = { [weak self] (picker) in
+            //exceed max selection
+        }
+        viewController.handleNoAlbumPermissions = { [weak self] (picker) in
+            // handle denied albums permissions case
+        }
+        viewController.handleNoCameraPermissions = { [weak self] (picker) in
+            // handle denied camera permissions case
+        }
+        viewController.selectedAssets = selectedAssets
+        sourceViewController.present(viewController, animated: true, completion: nil)
+    }
 }
     
 //MARK: -