{"id":1507,"url":"https://github.com/tilltue/TLPhotoPicker","last_synced_at":"2025-08-02T04:31:50.410Z","repository":{"id":39535316,"uuid":"90641944","full_name":"tilltue/TLPhotoPicker","owner":"tilltue","description":"📷 multiple phassets picker for iOS lib. like a facebook","archived":false,"fork":false,"pushed_at":"2024-07-18T16:32:04.000Z","size":85231,"stargazers_count":1886,"open_issues_count":61,"forks_count":333,"subscribers_count":34,"default_branch":"master","last_synced_at":"2024-12-03T13:04:23.044Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tilltue.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-05-08T15:10:34.000Z","updated_at":"2024-12-02T03:03:42.000Z","dependencies_parsed_at":"2022-07-09T18:46:34.223Z","dependency_job_id":"5366959a-747e-414b-bafc-63f5c0bd6998","html_url":"https://github.com/tilltue/TLPhotoPicker","commit_stats":{"total_commits":270,"total_committers":45,"mean_commits":6.0,"dds":0.6703703703703704,"last_synced_commit":"0d0cbbd2d20ed5fd36e5f4052209f5e2d9aaa8b7"},"previous_names":[],"tags_count":107,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tilltue%2FTLPhotoPicker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tilltue%2FTLPhotoPicker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tilltue%2FTLPhotoPicker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tilltue%2FTLPhotoPicker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tilltue","download_url":"https://codeload.github.com/tilltue/TLPhotoPicker/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228438976,"owners_count":17920017,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-01-05T20:15:48.075Z","updated_at":"2024-12-06T08:31:13.611Z","avatar_url":"https://github.com/tilltue.png","language":"Swift","funding_links":[],"categories":["Media"],"sub_categories":["Image","Other free courses"],"readme":"\u003cimg src=\"./Images/tlphotologo.png\"\u003e\n\n[![Version](https://img.shields.io/cocoapods/v/TLPhotoPicker.svg?style=flat)](http://cocoapods.org/pods/TLPhotoPicker)\n[![License](https://img.shields.io/cocoapods/l/TLPhotoPicker.svg?style=flat)](http://cocoapods.org/pods/TLPhotoPicker)\n[![Platform](https://img.shields.io/cocoapods/p/TLPhotoPicker.svg?style=flat)](http://cocoapods.org/pods/TLPhotoPicker)\n![Swift](https://img.shields.io/badge/%20in-swift%205.0-orange.svg)\n\n## Written in Swift 5.0\n\nTLPhotoPicker enables application to pick images and videos from multiple smart album in iOS, similar to the current facebook app.\n\n## Demo 🙉\n\n| Facebook Picker | TLPhotoPicker  |\n| ------------- | ------------- |\n| ![Facebook Picker](Images/facebook_ex.gif)  | ![TLPhotoPicker](Images/tlphotopicker_ex.gif)  |\n\n## Features\n\n- support smart album collection. \n  - camera roll, selfies, panoramas, favorites, videos, custom users album\n- selected order index.\n- playback video and live photos.\n  - just one. playback first video or live Photo in bounds of visible cell.\n- display video duration.\n- async phasset request and displayed cell.\n  - scrolling performance is better than facebook in displaying video assets collection.\n- custom cell\n- custom display and selection rules\n- reload of changes that occur in the Photos library.\n- support iCloud Photo Library\n- adds long press preview to images. ( to @smeshko ) [Preview](https://github.com/tilltue/TLPhotoPicker/pull/252#issue-362005178)\n\n| Smart album collection | LivePhotoCell | VideoPhotoCell  | PhotoCell | CustomCell(instagram) |\n| ------------- | ------------- | ------------- | ------------- | ------------- |\n| ![Facebook Picker](Images/smartalbum.png)  | ![LivePhotoCell](Images/livephotocell.png)  | ![VideoPhotoCell](Images/videophotocell.png)  | ![PhotoCell](Images/photocell.png)  | ![PhotoCell](Images/customcell.png)  |\n\nCustom Camera Cell\n\n| Live CameraCell |\n| ------------- |\n| ![Like Line](Images/custom_cameracell.gif)\n\n## Installation \n\n### Requirements \n\n- Swift 5.0 ( Swift 4.2 -\u003e use 'version 1.8.3' )\n- iOS 9.1 (for use live photos)\n\n### Cocoapods\n\nTLPhotoPicker is available through [CocoaPods](http://cocoapods.org). To install\nit, simply add the following line to your Podfile:\n\n```ruby\nplatform :ios, '9.1'\npod \"TLPhotoPicker\"\n```\n\n### Carthage\n\nCarthage is a simple, decentralized dependency manager for Cocoa.\n\nSpecify TLPhotoPicker into your project's Cartfile:\n\n```\ngithub \"tilltue/TLPhotoPicker\"\n```\n\n### Swift Package Manager\n\nThe Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler. It is in early development, but TLPhotoPicker does support its use on supported platforms.\n\nOnce you have your Swift package set up, adding Alamofire as a dependency is as easy as adding it to the dependencies value of your Package.swift.\n\n```\ndependencies: [\n    .package(url: \"https://github.com/tilltue/TLPhotoPicker.git\", .upToNextMajor(from: \"2.1.0\"))\n]\n```\n\n\u003e Don't forget the Privacy Description in `info.plist`.\n\u003cimg src=\"./Images/Privacy.png\"\u003e\n\n\u003e iOS 14\n\u003e You can suppress the automatic prompting from the system by setting this key to yes in your apps info plist.\n\u003e PHPhotoLibraryPreventAutomaticLimitedAccessAlert = YES\nhttps://developer.apple.com/videos/play/wwdc2020/10641/\n\n## Usage \n\n**use delegate**\n\nYou can choose delegate method or closure for handle picker event.\n\n```swift \nclass ViewController: UIViewController,TLPhotosPickerViewControllerDelegate {\n    var selectedAssets = [TLPHAsset]()\n    @IBAction func pickerButtonTap() {\n        let viewController = TLPhotosPickerViewController()\n        viewController.delegate = self\n        var configure = TLPhotosPickerConfigure()\n        //configure.nibSet = (nibName: \"CustomCell_Instagram\", bundle: Bundle.main) // If you want use your custom cell..\n        self.present(viewController, animated: true, completion: nil)\n    }\n    //TLPhotosPickerViewControllerDelegate\n    func shouldDismissPhotoPicker(withTLPHAssets: [TLPHAsset]) -\u003e Bool {\n        // use selected order, fullresolution image\n        self.selectedAssets = withTLPHAssets\n\treturn true\n    }\n    func dismissPhotoPicker(withPHAssets: [PHAsset]) {\n        // if you want to used phasset. \n    }\n    func photoPickerDidCancel() {\n        // cancel\n    }\n    func dismissComplete() {\n        // picker viewcontroller dismiss completion\n    }\n    func canSelectAsset(phAsset: PHAsset) -\u003e Bool {\n        //Custom Rules \u0026 Display\n        //You can decide in which case the selection of the cell could be forbidden. \n    }\n    func didExceedMaximumNumberOfSelection(picker: TLPhotosPickerViewController) {\n        // exceed max selection\n    }\n    func handleNoAlbumPermissions(picker: TLPhotosPickerViewController) {\n        // handle denied albums permissions case\n    }\n    func handleNoCameraPermissions(picker: TLPhotosPickerViewController) {\n        // handle denied camera permissions case\n    }\n}\n\n```\n**use closure**\n\n```swift\n    init(withPHAssets: (([PHAsset]) -\u003e Void)? = nil, didCancel: ((Void) -\u003e Void)? = nil)\n    init(withTLPHAssets: (([TLPHAsset]) -\u003e Void)? = nil, didCancel: ((Void) -\u003e Void)? = nil)\n    var canSelectAsset: ((PHAsset) -\u003e Bool)? = nil\n    var didExceedMaximumNumberOfSelection: ((TLPhotosPickerViewController) -\u003e Void)? = nil\n    var handleNoAlbumPermissions: ((TLPhotosPickerViewController) -\u003e Void)? = nil\n    var handleNoCameraPermissions: ((TLPhotosPickerViewController) -\u003e Void)? = nil\n    var dismissCompletion: (() -\u003e Void)? = nil\n```\n```swift\nclass ViewController: UIViewController,TLPhotosPickerViewControllerDelegate {\n    var selectedAssets = [TLPHAsset]()\n    @IBAction func pickerButtonTap() {\n        let viewController = TLPhotosPickerViewController(withTLPHAssets: { [weak self] (assets) in // TLAssets\n            self?.selectedAssets = assets\n        }, didCancel: nil)\n        viewController.didExceedMaximumNumberOfSelection = { [weak self] (picker) in\n            //exceed max selection\n        }\n        viewController.handleNoAlbumPermissions = { [weak self] (picker) in\n            // handle denied albums permissions case\n        }\n        viewController.handleNoCameraPermissions = { [weak self] (picker) in\n            // handle denied camera permissions case\n        }\n        viewController.selectedAssets = self.selectedAssets\n        self.present(viewController, animated: true, completion: nil)\n    }\n}\n\n```\n\n**Custom Cell**\nCustom Cell must subclass TLPhotoCollectionViewCell\n```Swift\nclass CustomCell_Instagram: TLPhotoCollectionViewCell {\n\n}\n\n//If you want custom camera cell?\n//only used camera cell\n[Sample](https://github.com/tilltue/TLPhotoPicker/blob/master/Example/TLPhotoPicker/CustomCameraCell.swift)\n\n//Adding the possibility to handle cell display according to a specific conditions\nfunc update(with phAsset: PHAsset)\nfunc selectedCell()\nfunc willDisplayCell()\nfunc endDisplayingCell()\n```\n\n**Custom Rules \u0026 Display**\n\nYou can implement your own rules to handle the cell display. You can decide in which case the selection of the cell could be forbidden. \n\nFor example, if you want to disable the selection of a cell if its width is under 300, you can follow these steps:\n\n- Override the update method of your custom cell and add your own display rule \n\n```swift\noverride func update(with phAsset: PHAsset) {\n    super.update(with: phAsset)\n    self.sizeRequiredOverlayView?.isHidden = !(phAsset.pixelHeight \u003c= 300 \u0026\u0026 phAsset.pixelWidth \u003c= 300)\n}\n``` \nIn this code, we show an overlay when the height and width required values are not satisified.\n\n- When you instanciate a `TLPhotosPickerViewController` subclass, you can pass a closure called `canSelectAsset` to handle the selection according to some rules.  ( or delegate)\n\n```Swift\n//use delegate \npublic protocol TLPhotosPickerViewControllerDelegate: class {\n    ...\n    func canSelectAsset(phAsset: PHAsset) -\u003e Bool\n    ...\n}\n\nextension UserViewController: TLPhotosPickerViewControllerDelegate {\n    func canSelectAsset(phAsset: PHAsset) -\u003e Bool {\n        if asset.pixelHeight \u003c 100 || asset.pixelWidth \u003c 100 {\n            self?.showUnsatisifiedSizeAlert(vc: viewController)\n            return false\n        }\n        return true\n    }\n}\n\n//or use closure\nviewController.canSelectAsset = { [weak self] asset -\u003e Bool in\n    if asset.pixelHeight \u003c 100 || asset.pixelWidth \u003c 100 {\n        self?.showUnsatisifiedSizeAlert(vc: viewController)\n        return false\n    }\n    return true\n}\n```\nIn this code, we show an alert when the condition in the closure are not satisfiied.\n\n**TLPHAsset**\n\n```swift\npublic struct TLPHAsset {\n    public enum AssetType {\n        case photo,video,livePhoto\n    }\n    // phasset \n    public var phAsset: PHAsset? = nil\n    // selected order index\n    public var selectedOrder: Int = 0\n    // asset type\n    public var type: AssetType\n    // get full resolution image \n    public var fullResolutionImage: UIImage?\n    // get photo file size (async)\n    public func photoSize(options: PHImageRequestOptions? = nil ,completion: @escaping ((Int)-\u003eVoid), livePhotoVideoSize: Bool = false)\n    // get video file size (async)\n    public func videoSize(options: PHVideoRequestOptions? = nil, completion: @escaping ((Int)-\u003eVoid))\n    // get async icloud image (download)\n    @discardableResult\n    public func cloudImageDownload(progressBlock: @escaping (Double) -\u003e Void, completionBlock:@escaping (UIImage?)-\u003e Void ) -\u003e PHImageRequestID?\n    // get original media file async copy temporary media file ( photo(png,gif...etc.) and video ) -\u003e Don't forget, You should delete temporary file.\n    // parmeter : convertLivePhotosToJPG\n    // false : If you want mov file at live photos\n    // true  : If you want png file at live photos ( HEIC )\n    public func tempCopyMediaFile(videoRequestOptions: PHVideoRequestOptions? = nil, \n                                  imageRequestOptions: PHImageRequestOptions? = nil,\n                                  livePhotoRequestOptions: PHLivePhotoRequestOptions? = nil,\n                                  exportPreset: String = AVAssetExportPresetHighestQuality, \n                                  convertLivePhotosToJPG: Bool = false, \n                                  progressBlock:((Double) -\u003e Void)? = nil, \n                                  completionBlock:@escaping ((URL,String) -\u003e Void)) -\u003e PHImageRequestID?\n    //Apparently, This is not the only way to export video.\n    //There is many way that export a video.\n    //This method was one of them.\n    public func exportVideoFile(options: PHVideoRequestOptions? = nil,\n                                outputURL: URL? = nil,\n                                outputFileType: AVFileType = .mov,\n                                progressBlock:((Double) -\u003e Void)? = nil,\n                                completionBlock:@escaping ((URL,String) -\u003e Void))\n    // get original asset file name\n    public var originalFileName: String?\n}\n```\n\u003e  Note:  convenience export method\n\u003e  fullResolutionImage, cloudImageDownload, tempCopyMediaFile, exportVideoFile\n\u003e  It's not enough if you wanted to use more complicated export asset options. ( progress, export type, etc..)\n\n## Customize \n\n```swift\nlet viewController = TLPhotosPickerViewController()\nvar configure = TLPhotosPickerConfigure()\nviewController.configure = configure\n\npublic struct TLPhotosPickerConfigure {\n    public var customLocalizedTitle: [String: String] = [\"Camera Roll\": \"Camera Roll\"] // Set [:] if you want use default localized title of album\n    public var tapHereToChange = \"Tap here to change\"\n    public var cancelTitle = \"Cancel\"\n    public var doneTitle = \"Done\"\n    public var emptyMessage = \"No albums\"\n    public var emptyImage: UIImage? = nil\n    public var usedCameraButton = true\n    public var usedPrefetch = false\n    public var previewAtForceTouch = false\n    public var allowedLivePhotos = true\n    public var allowedVideo = true\n    public var allowedAlbumCloudShared = false\n    public var allowedPhotograph = true // for camera : allow this option when you want to take a photos\n    public var allowedVideoRecording = true //for camera : allow this option when you want to recording video.\n    public var recordingVideoQuality: UIImagePickerControllerQualityType = .typeMedium //for camera : recording video quality\n    public var maxVideoDuration:TimeInterval? = nil //for camera : max video recording duration\n    public var autoPlay = true\n    public var muteAudio = true\n    public var preventAutomaticLimitedAccessAlert = true // newest iOS 14\n    public var mediaType: PHAssetMediaType? = nil\n    public var numberOfColumn = 3\n    public var minimumLineSpacing: CGFloat = 5\n    public var minimumInteritemSpacing: CGFloat = 5\n    public var singleSelectedMode = false\n    public var maxSelectedAssets: Int? = nil //default: inf\n    public var fetchOption: PHFetchOptions? = nil //default: creationDate\n    public var fetchCollectionOption: [FetchCollectionType: PHFetchOptions] = [:] \n    public var singleSelectedMode = false\n    public var selectedColor = UIColor(red: 88/255, green: 144/255, blue: 255/255, alpha: 1.0)\n    public var cameraBgColor = UIColor(red: 221/255, green: 223/255, blue: 226/255, alpha: 1)\n    public var cameraIcon = TLBundle.podBundleImage(named: \"camera\")\n    public var videoIcon = TLBundle.podBundleImage(named: \"video\")\n    public var placeholderIcon = TLBundle.podBundleImage(named: \"insertPhotoMaterial\")\n    public var nibSet: (nibName: String, bundle:Bundle)? = nil // custom cell\n    public var cameraCellNibSet: (nibName: String, bundle:Bundle)? = nil // custom camera cell\n    public var fetchCollectionTypes: [(PHAssetCollectionType,PHAssetCollectionSubtype)]? = nil\n    public var groupByFetch: PHFetchedResultGroupedBy? = nil // cannot be used prefetch options\n    public var supportedInterfaceOrientations: UIInterfaceOrientationMask = .portrait\n    public var popup: [PopupConfigure] = []\n    public init() {\n    }\n}\n\n//Related issue: https://github.com/tilltue/TLPhotoPicker/issues/201\n//e.g.\n//let option = PHFetchOptions()\n//configure.fetchCollectionOption[.assetCollections(.smartAlbum)] = option\n//configure.fetchCollectionOption[.assetCollections(.album)] = option\n//configure.fetchCollectionOption[.topLevelUserCollections] = option\n\npublic enum FetchCollectionType {\n    case assetCollections(PHAssetCollectionType)\n    case topLevelUserCollections\n}\n\npublic enum PopupConfigure {\n    //Popup album view animation duration\n    case animation(TimeInterval)\n}\n\n// PHFetchedResultGroupedBy\n//\n// CGrouped by date, cannot be used prefetch options\n// take about few seconds ( 5000 image iPhoneX: 1 ~ 1.5 sec ) \npublic enum PHFetchedResultGroupedBy {\n    case year\n    case month\n    case week\n    case day\n    case hour\n    case custom(dateFormat: String)\n}\n\n//customizable photos picker viewcontroller\nclass CustomPhotoPickerViewController: TLPhotosPickerViewController {\n    override func makeUI() {\n        super.makeUI()\n        self.customNavItem.leftBarButtonItem = UIBarButtonItem.init(barButtonSystemItem: .stop, target: nil, action: #selector(customAction))\n    }\n    func customAction() {\n        self.dismiss(animated: true, completion: nil)\n    }\n}\n\n//for log\npublic protocol TLPhotosPickerLogDelegate: class {\n    func selectedCameraCell(picker: TLPhotosPickerViewController)\n    func deselectedPhoto(picker: TLPhotosPickerViewController, at: Int)\n    func selectedPhoto(picker: TLPhotosPickerViewController, at: Int)\n    func selectedAlbum(picker: TLPhotosPickerViewController, title: String, at: Int)\n}\n\n//for collection supplement view \nlet viewController = TLPhotosPickerViewController()\nviewController.customDataSouces = CustomDataSources() // inherit TLPhotopickerDataSourcesProtocol\n\npublic protocol TLPhotopickerDataSourcesProtocol {\n    func headerReferenceSize() -\u003e CGSize\n    func footerReferenceSize() -\u003e CGSize\n    func registerSupplementView(collectionView: UICollectionView)\n    func supplementIdentifier(kind: String) -\u003e String\n    func configure(supplement view: UICollectionReusableView, section: (title: String, assets: [TLPHAsset]))\n}\n\n```\n\n## Author\n\nDoes your organization or project use TLPhotoPicker? Please let me know by email.\n\nwade.hawk, junhyi.park@gmail.com\n\n## License \n\nTLPhotoPicker is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftilltue%2FTLPhotoPicker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftilltue%2FTLPhotoPicker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftilltue%2FTLPhotoPicker/lists"}