📷 multiple phassets picker for iOS lib. like a facebook
Swift
1,895
315 commits
updated Jun 16, 2026
A modern, flexible photo and video picker for iOS applications. TLPhotoPicker enables selecting media from multiple smart albums with an interface similar to Facebook's photo picker.
| Facebook Picker | TLPhotoPicker |
|---|---|
![]() | ![]() |
| Smart Albums | Live Photo | Video | Photo | Custom Cell |
|---|---|---|---|---|
![]() | ![]() | ![]() | ![]() | ![]() |
| Live Camera Cell |
|---|
![]() |
platform :ios, '13.0'
pod "TLPhotoPicker"
Add TLPhotoPicker as a dependency in your Package.swift:
dependencies: [
.package(url: "https://github.com/tilltue/TLPhotoPicker.git", .upToNextMajor(from: "2.1.0"))
]
Add the following keys to your Info.plist:
<key>NSPhotoLibraryUsageDescription</key>
<string>Access to photos is required to select images</string>
<key>NSCameraUsageDescription</key>
<string>Camera access is required to take photos</string>
iOS 14+ Limited Photo Access
To suppress automatic prompting, add this to
Info.plist:<key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key> <true/>
import TLPhotoPicker
class ViewController: UIViewController {
@IBAction func openPhotoPicker() {
let picker = TLPhotosPickerViewController()
picker.delegate = self
present(picker, animated: true)
}
}
extension ViewController: TLPhotosPickerViewControllerDelegate {
func dismissPhotoPicker(withTLPHAssets: [TLPHAsset]) {
// Handle selected assets
for asset in withTLPHAssets {
print("Selected: \(asset.originalFileName ?? "Unknown")")
}
}
}
// Load images asynchronously
Task {
if let image = await selectedAssets.first?.fullResolutionImage() {
await MainActor.run {
self.imageView.image = image
}
}
}
// Load multiple images concurrently
Task {
let images = await withTaskGroup(of: UIImage?.self) { group in
for asset in selectedAssets {
group.addTask { await asset.fullResolutionImage() }
}
var results: [UIImage] = []
for await image in group {
if let image = image { results.append(image) }
}
return results
}
await MainActor.run {
self.displayImages(images)
}
}
let picker = TLPhotosPickerViewController()
// Use presets
picker.configure = .singlePhoto
picker.configure = .videoOnly
picker.configure = .compactGrid
// Or build custom configuration
picker.configure = TLPhotosPickerConfigure()
.numberOfColumns(3)
.maxSelection(20)
.allowVideo(true)
.allowLivePhotos(true)
.selectedColor(.systemPink)
.useCameraButton(true)
// Extend presets
picker.configure = .videoOnly
.numberOfColumns(4)
.selectedColor(.systemBlue)
present(picker, animated: true)
For detailed information, see:
picker.configure = .singlePhoto
.selectedColor(.systemPurple)
picker.configure = TLPhotosPickerConfigure()
.mediaType(.video)
.allowPhotograph(false)
.allowVideoRecording(true)
picker.configure = .compactGrid
.maxSelection(10)
.selectedColor(UIColor(red: 0/255, green: 122/255, blue: 255/255, alpha: 1.0))
picker.canSelectAsset = { asset in
// Only allow images larger than 300x300
return asset.pixelWidth >= 300 && asset.pixelHeight >= 300
}
picker.didExceedMaximumNumberOfSelection = { picker in
// Show alert when limit reached
}
let picker = TLPhotosPickerViewController()
picker.didCaptureMediaURL = { url in
// Both the camera picker and TLPhotosPickerViewController are already
// dismissed at this point. Just present your next screen.
let uploadVC = MyUploadViewController(fileURL: url)
presentingViewController.present(uploadVC, animated: true)
}
present(picker, animated: true)
When set, camera captures are returned as temporary file URLs instead of being saved to the Photo Library. By the time the closure fires, the camera picker and TLPhotosPickerViewController have both been fully dismissed — the caller only needs to present their next view controller, never dismiss.
protocol TLPhotosPickerViewControllerDelegate {
func shouldDismissPhotoPicker(withTLPHAssets: [TLPHAsset]) -> Bool
func dismissPhotoPicker(withTLPHAssets: [TLPHAsset])
func dismissPhotoPicker(withPHAssets: [PHAsset])
func photoPickerDidCancel()
func dismissComplete()
func canSelectAsset(phAsset: PHAsset) -> Bool
func didExceedMaximumNumberOfSelection(picker: TLPhotosPickerViewController)
func handleNoAlbumPermissions(picker: TLPhotosPickerViewController)
func handleNoCameraPermissions(picker: TLPhotosPickerViewController)
}
The library provides TLPHAsset, a wrapper around PHAsset with convenient helper methods:
public struct TLPHAsset {
public var phAsset: PHAsset?
public var selectedOrder: Int
public var type: AssetType // .photo, .video, .livePhoto
public var originalFileName: String?
public var isSelectedFromCamera: Bool
// Async image loading
public func fullResolutionImage() async -> UIImage?
// iCloud download
public func cloudImageDownload(
progressBlock: @escaping (Double) -> Void,
completionBlock: @escaping (UIImage?) -> Void
) -> PHImageRequestID?
// Export to file
public func tempCopyMediaFile(
convertLivePhotosToJPG: Bool = false,
progressBlock: ((Double) -> Void)? = nil,
completionBlock: @escaping ((URL, String) -> Void)
) -> PHImageRequestID?
// File size
public func photoSize(completion: @escaping (Int) -> Void)
public func videoSize(completion: @escaping (Int) -> Void)
// Static method
public static func asset(with localIdentifier: String) -> TLPHAsset?
}
See API Reference for complete documentation.
Issues and pull requests are welcome! Please check existing issues before creating new ones.
TLPhotoPicker is an open-source project maintained in my free time. If you find it useful, please consider supporting its development:
Your support helps me:
Every contribution is appreciated! 🙏
wade.hawk - junhyi.park@gmail.com
Does your organization use TLPhotoPicker? Let me know!
TLPhotoPicker is available under the MIT license. See the LICENSE file for details.
35 followers · starred May 2017
399 followers · starred Jan 2019
218 followers · starred May 2017
34 followers · starred Apr 2019
📷 multiple phassets picker for iOS lib. like a facebook
Swift
1,895
315 commits
updated Jun 16, 2026
A modern, flexible photo and video picker for iOS applications. TLPhotoPicker enables selecting media from multiple smart albums with an interface similar to Facebook's photo picker.
| Facebook Picker | TLPhotoPicker |
|---|---|
![]() | ![]() |
| Smart Albums | Live Photo | Video | Photo | Custom Cell |
|---|---|---|---|---|
![]() | ![]() | ![]() | ![]() | ![]() |
| Live Camera Cell |
|---|
![]() |
platform :ios, '13.0'
pod "TLPhotoPicker"
Add TLPhotoPicker as a dependency in your Package.swift:
dependencies: [
.package(url: "https://github.com/tilltue/TLPhotoPicker.git", .upToNextMajor(from: "2.1.0"))
]
Add the following keys to your Info.plist:
<key>NSPhotoLibraryUsageDescription</key>
<string>Access to photos is required to select images</string>
<key>NSCameraUsageDescription</key>
<string>Camera access is required to take photos</string>
iOS 14+ Limited Photo Access
To suppress automatic prompting, add this to
Info.plist:<key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key> <true/>
import TLPhotoPicker
class ViewController: UIViewController {
@IBAction func openPhotoPicker() {
let picker = TLPhotosPickerViewController()
picker.delegate = self
present(picker, animated: true)
}
}
extension ViewController: TLPhotosPickerViewControllerDelegate {
func dismissPhotoPicker(withTLPHAssets: [TLPHAsset]) {
// Handle selected assets
for asset in withTLPHAssets {
print("Selected: \(asset.originalFileName ?? "Unknown")")
}
}
}
// Load images asynchronously
Task {
if let image = await selectedAssets.first?.fullResolutionImage() {
await MainActor.run {
self.imageView.image = image
}
}
}
// Load multiple images concurrently
Task {
let images = await withTaskGroup(of: UIImage?.self) { group in
for asset in selectedAssets {
group.addTask { await asset.fullResolutionImage() }
}
var results: [UIImage] = []
for await image in group {
if let image = image { results.append(image) }
}
return results
}
await MainActor.run {
self.displayImages(images)
}
}
let picker = TLPhotosPickerViewController()
// Use presets
picker.configure = .singlePhoto
picker.configure = .videoOnly
picker.configure = .compactGrid
// Or build custom configuration
picker.configure = TLPhotosPickerConfigure()
.numberOfColumns(3)
.maxSelection(20)
.allowVideo(true)
.allowLivePhotos(true)
.selectedColor(.systemPink)
.useCameraButton(true)
// Extend presets
picker.configure = .videoOnly
.numberOfColumns(4)
.selectedColor(.systemBlue)
present(picker, animated: true)
For detailed information, see:
picker.configure = .singlePhoto
.selectedColor(.systemPurple)
picker.configure = TLPhotosPickerConfigure()
.mediaType(.video)
.allowPhotograph(false)
.allowVideoRecording(true)
picker.configure = .compactGrid
.maxSelection(10)
.selectedColor(UIColor(red: 0/255, green: 122/255, blue: 255/255, alpha: 1.0))
picker.canSelectAsset = { asset in
// Only allow images larger than 300x300
return asset.pixelWidth >= 300 && asset.pixelHeight >= 300
}
picker.didExceedMaximumNumberOfSelection = { picker in
// Show alert when limit reached
}
let picker = TLPhotosPickerViewController()
picker.didCaptureMediaURL = { url in
// Both the camera picker and TLPhotosPickerViewController are already
// dismissed at this point. Just present your next screen.
let uploadVC = MyUploadViewController(fileURL: url)
presentingViewController.present(uploadVC, animated: true)
}
present(picker, animated: true)
When set, camera captures are returned as temporary file URLs instead of being saved to the Photo Library. By the time the closure fires, the camera picker and TLPhotosPickerViewController have both been fully dismissed — the caller only needs to present their next view controller, never dismiss.
protocol TLPhotosPickerViewControllerDelegate {
func shouldDismissPhotoPicker(withTLPHAssets: [TLPHAsset]) -> Bool
func dismissPhotoPicker(withTLPHAssets: [TLPHAsset])
func dismissPhotoPicker(withPHAssets: [PHAsset])
func photoPickerDidCancel()
func dismissComplete()
func canSelectAsset(phAsset: PHAsset) -> Bool
func didExceedMaximumNumberOfSelection(picker: TLPhotosPickerViewController)
func handleNoAlbumPermissions(picker: TLPhotosPickerViewController)
func handleNoCameraPermissions(picker: TLPhotosPickerViewController)
}
The library provides TLPHAsset, a wrapper around PHAsset with convenient helper methods:
public struct TLPHAsset {
public var phAsset: PHAsset?
public var selectedOrder: Int
public var type: AssetType // .photo, .video, .livePhoto
public var originalFileName: String?
public var isSelectedFromCamera: Bool
// Async image loading
public func fullResolutionImage() async -> UIImage?
// iCloud download
public func cloudImageDownload(
progressBlock: @escaping (Double) -> Void,
completionBlock: @escaping (UIImage?) -> Void
) -> PHImageRequestID?
// Export to file
public func tempCopyMediaFile(
convertLivePhotosToJPG: Bool = false,
progressBlock: ((Double) -> Void)? = nil,
completionBlock: @escaping ((URL, String) -> Void)
) -> PHImageRequestID?
// File size
public func photoSize(completion: @escaping (Int) -> Void)
public func videoSize(completion: @escaping (Int) -> Void)
// Static method
public static func asset(with localIdentifier: String) -> TLPHAsset?
}
See API Reference for complete documentation.
Issues and pull requests are welcome! Please check existing issues before creating new ones.
TLPhotoPicker is an open-source project maintained in my free time. If you find it useful, please consider supporting its development:
Your support helps me:
Every contribution is appreciated! 🙏
wade.hawk - junhyi.park@gmail.com
Does your organization use TLPhotoPicker? Let me know!
TLPhotoPicker is available under the MIT license. See the LICENSE file for details.
35 followers · starred May 2017
399 followers · starred Jan 2019
218 followers · starred May 2017
34 followers · starred Apr 2019