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 Album Support - Camera roll, selfies, panoramas, favorites, videos, and custom albums
- π± Selection Order - Visual order indicators for selected media
βΆοΈ Media Playback - Preview videos and Live Photos directly in the picker- β±οΈ Video Duration - Display video length on thumbnails
- β‘ High Performance - Async asset loading with excellent scrolling performance
- π¨ Customizable - Custom cells, selection rules, and UI elements
- π Live Updates - Automatic reload when Photos library changes
- βοΈ iCloud Support - Seamless iCloud Photo Library integration
| Smart Albums | Live Photo | Video | Photo | Custom Cell |
|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
| Live Camera Cell |
|---|
![]() |
- iOS 13.0+
- Swift 5.0+
- Xcode 14.0+
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:
- Configuration Guide - Complete configuration options
- Advanced Usage - Custom cells, delegates, and rules
- API Reference - TLPHAsset and helper methods
- Migration Guide - Upgrading from older versions
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:
- π Fix bugs and maintain compatibility with latest iOS versions
- β¨ Develop new features and improvements
- π Improve documentation and examples
- β‘ Performance optimizations and code quality
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.









