https://github.com/SilenceLove/HXPhotoPicker
图片/视频选择器 - 支持LivePhoto、GIF图片选择、3DTouch预览、在线下载iCloud上的资源、编辑图片/视频、浏览网络图片 功能 Imitation wx photo/image picker - support for LivePhoto, GIF image selection, 3DTouch preview, Download the resources on iCloud online, browse the web image function
https://github.com/SilenceLove/HXPhotoPicker
3d-touch browser editor gif icloud image imagepicker ios livephoto photo photoeditor photokit photopicker phpicker picker previewimage swift uiimagepickercontroller videoeditor
Last synced: 9 days ago
JSON representation
图片/视频选择器 - 支持LivePhoto、GIF图片选择、3DTouch预览、在线下载iCloud上的资源、编辑图片/视频、浏览网络图片 功能 Imitation wx photo/image picker - support for LivePhoto, GIF image selection, 3DTouch preview, Download the resources on iCloud online, browse the web image function
- Host: GitHub
- URL: https://github.com/SilenceLove/HXPhotoPicker
- Owner: SilenceLove
- License: mit
- Created: 2017-03-03T07:53:12.000Z (about 8 years ago)
- Default Branch: master
- Last Pushed: 2024-10-16T09:12:29.000Z (7 months ago)
- Last Synced: 2024-10-29T15:09:24.936Z (7 months ago)
- Topics: 3d-touch, browser, editor, gif, icloud, image, imagepicker, ios, livephoto, photo, photoeditor, photokit, photopicker, phpicker, picker, previewimage, swift, uiimagepickercontroller, videoeditor
- Language: Swift
- Homepage: https://github.com/SilenceLove/HXPhotoPicker
- Size: 56.7 MB
- Stars: 3,085
- Watchers: 54
- Forks: 662
- Open Issues: 13
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
- Awesome-iOS - HXPhotoPicker - 照片/图片选择器 - 支持LivePhoto、GIF图片选择、3DTouch预览、在线下载iCloud上的资源、浏览网络图片功能 (UI Components)
- awesome - HXPhotoPicker - 图片/视频选择器 - 支持LivePhoto、GIF图片选择、3DTouch预览、在线下载iCloud上的资源、编辑图片/视频、浏览网络图片 功能 Imitation wx photo/image picker - support for LivePhoto, GIF image selection, 3DTouch preview, Download the resources on iCloud online, browse the web image function. (iOS / PhotoPicker)
- awesome - HXPhotoPicker - 图片/视频选择器 - 支持LivePhoto、GIF图片选择、3DTouch预览、在线下载iCloud上的资源、编辑图片/视频、浏览网络图片 功能 Imitation wx photo/image picker - support for LivePhoto, GIF image selection, 3DTouch preview, Download the resources on iCloud online, browse the web image function. (iOS / PhotoPicker)
README
中文 | English
一款图片/视频选择器-支持LivePhoto、GIF选择、iCloud/网络资源在线下载、图片/视频编辑## 目录
* [功能](#功能)
* [要求](#要求)
* [安装](#安装)
* [示例](#示例)
* [快速使用](#示例)
* [如何支持GIF/网络图片](#如何支持GIF/网络图片)
* [如何获取](#如何获取)
* [更新记录](#更新记录)
* [演示效果](#演示效果)
* [界面展示](#界面展示)
* [支持❤️](#支持❤️)- [x] UI 外观支持浅色/深色/自动/自定义
- [x] 支持多选/混合内容选择
- [x] 支持的媒体类型:
- [x] Photo
- [x] GIF
- [x] Live Photo
- [x] Video
- [x] 支持的本地资源类型:
- [x] Photo
- [x] Video
- [x] GIF
- [x] Live Photo
- [x] 支持的网络资源类型:
- [x] Photo
- [x] Video
- [x] 支持下载iCloud上的资源
- [x] 支持手势返回
- [x] 支持滑动选择
- [x] 编辑图片(支持动图、网络资源)
- [x] 涂鸦
- [x] 贴纸
- [x] 文字
- [x] 裁剪
- [x] 旋转任意角度
- [x] 自定义蒙版
- [x] 马赛克
- [x] 画面调整
- [x] 滤镜
- [x] 编辑视频(支持网络资源)
- [x] 涂鸦
- [x] 贴纸(支持GIF)
- [x] 文字
- [x] 配乐(支持歌词字幕)
- [x] 裁剪时长
- [x] 裁剪尺寸
- [x] 旋转任意角度
- [x] 自定义蒙版
- [x] 画面调整
- [x] 滤镜
- [x] 相册展现方式
- [x] 单独列表
- [x] 弹窗
- [x] 多平台支持
- [x] iOS
- [x] iPadOS
- [x] Mac Catalyst
- [x] 国际化支持
- [x] 🇨🇳 简体中文 (zh-Hans)
- [x] 🇨🇳 繁体中文 (zh-Hant)
- [x] 🇬🇧 英文 (en)
- [x] 🇯🇵 日语 (ja)
- [x] 🇰🇷 韩语 (ko)
- [x] 🇹🇭 泰语 (th)
- [x] 🇮🇳 印尼语 (id)
- [x] 🇻🇳 越南语 (vi)
- [x] 🇷🇺 俄罗斯 (ru)
- [x] 🇩🇪 德国 (de)
- [x] 🇫🇷 法国 (fr)
- [x] 🇸🇦 阿拉伯 (ar)
- [x] ✍️ 自定义语言 (custom)
- [ ] 🤝 更多支持... (欢迎PR)- iOS 10.0+
- Xcode 12.5+
- Swift 5.4+### [Swift Package Manager](https://swift.org/package-manager/)
⚠️ 需要 Xcode 13.0 及以上版本来支持资源文件/本地化文件的添加。
```swift
dependencies: [
.package(url: "https://github.com/SilenceLove/HXPhotoPicker.git", .upToNextMajor(from: "5.0.1"))
]
```### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)
将下面内容添加到 `Podfile`,并执行依赖更新。
```swift
/// iOS 10.0+ 默认不支持GIF和网络图片
pod 'HXPhotoPicker'/// 使用`SwiftyGif`加载GIF图片
pod 'HXPhotoPicker/SwiftyGif'/// 使用`SDWebImage`加载GIF/网络图片
pod 'HXPhotoPicker/SDWebImage'/// 使用`Kingfisher v6.0.0`加载GIF/网络图片
pod 'HXPhotoPicker/Kingfisher'/// 相机不包含定位功能
pod `HXPhotoPicker/NoLocation`/// 只有选择器
pod `HXPhotoPicker/Picker`/// 只有编辑器
pod `HXPhotoPicker/Editor`/// 只有相机
pod `HXPhotoPicker/Camera`
/// 不包含定位功能
pod `HXPhotoPicker/Camera/Lite`v4.0以下的ObjC版本
pod 'HXPhotoPickerObjC'
```### 准备工作
按需在你的 Info.plist 中添加以下键值:
| Key | 模块 | 备注 |
| ----- | ---- | ---- |
| NSPhotoLibraryUsageDescription | Picker | 允许访问相册 |
| NSPhotoLibraryAddUsageDescription | Picker | 允许保存图片至相册 |
| PHPhotoLibraryPreventAutomaticLimitedAccessAlert | Picker | 设置为 `YES` iOS 14+ 以禁用自动弹出添加更多照片的弹框(Picker 已适配 Limited 功能,可由用户主动触发,提升用户体验) |
| NSCameraUsageDescription | Camera | 允许使用相机 |
| NSMicrophoneUsageDescription | Camera | 允许使用麦克风 |### 快速上手
```swift
import HXPhotoPickerclass ViewController: UIViewController {
func presentPickerController() {
// 设置与微信主题一致的配置
let config = PickerConfiguration.default
// 方法一:async/await
// 使用`Photo`
let images: [UIImage] = try await Photo.picker(config)
let urls: [URL] = try await Photo.picker(config)
let urlResult: [AssetURLResult] = try await Photo.picker(config)
let assetResult: [AssetResult] = try await Photo.picker(config)
// 使用`PhotoPickerController`
let images: [UIImage] = try await PhotoPickerController.picker(config)
let urls: [URL] = try await PhotoPickerController.picker(config)
let urlResult: [AssetURLResult] = try await PhotoPickerController.picker(config)
let assetResult: [AssetResult] = try await PhotoPickerController.picker(config)
let pickerResult = try await Photo.picker(config)
let images: [UIImage] = try await pickerResult.objects()
let urls: [URL] = try await pickerResult.objects()
let urlResults: [AssetURLResult] = try await pickerResult.objects()
let assetResults: [AssetResult] = try await pickerResult.objects()
// 方法二:
let pickerController = PhotoPickerController(picker: config)
pickerController.pickerDelegate = self
// 当前被选择的资源对应的 PhotoAsset 对象数组
pickerController.selectedAssetArray = selectedAssets
// 是否选中原图
pickerController.isOriginal = isOriginal
present(pickerController, animated: true, completion: nil)
// 方法三:
Photo.picker(
config
) { result, pickerController in
// 选择完成的回调
// result 选择结果
// .photoAssets 当前选择的数据
// .isOriginal 是否选中了原图
// photoPickerController 对应的照片选择控制器
} cancel: { pickerController in
// 取消的回调
// photoPickerController 对应的照片选择控制器
}
}
}extension ViewController: PhotoPickerControllerDelegate {
/// 选择完成之后调用
/// - Parameters:
/// - pickerController: 对应的 PhotoPickerController
/// - result: 选择的结果
/// result.photoAssets 选择的资源数组
/// result.isOriginal 是否选中原图
func pickerController(
_ pickerController: PhotoPickerController,
didFinishSelection result: PickerResult
) {
// async/await
let images: [UIImage] = try await result.objects()
let urls: [URL] = try await result.objects()
let urlResults: [AssetURLResult] = try await result.objects()
let assetResults: [AssetResult] = try await result.objects()
result.getImage { (image, photoAsset, index) in
if let image = image {
print("success", image)
}else {
print("failed")
}
} completionHandler: { (images) in
print(images)
}
}
/// 点击取消时调用
/// - Parameter pickerController: 对应的 PhotoPickerController
func pickerController(didCancel pickerController: PhotoPickerController) {
}
}
```### 如何支持GIF/网络图片 [HXImageViewProtocol](https://github.com/SilenceLove/HXPhotoPicker/tree/master/Sources/HXPhotoPicker/Core/Config/HXImageViewProtocol.swift)
SwiftyGif
```swift
PickerConfiguration.imageViewProtocol = GIFImageView.selfpublic class GIFImageView: UIImageView, HXImageViewProtocol {
public func setImageData(_ imageData: Data?) {
guard let imageData else {
clear()
SwiftyGifManager.defaultManager.deleteImageView(self)
image = nil
return
}
if let image = try? UIImage(gifData: imageData) {
setGifImage(image)
}else {
image = .init(data: imageData)
}
}
public func _startAnimating() {
startAnimatingGif()
}
public func _stopAnimating() {
stopAnimatingGif()
}
}
```SDWebImage
```swift
PickerConfiguration.imageViewProtocol = SDImageView.selfpublic class SDImageView: SDAnimatedImageView, HXImageViewProtocol {
public func setImageData(_ imageData: Data?) {
guard let imageData else { return }
let image = SDAnimatedImage(data: imageData)
self.image = image
}
@discardableResult
public func setImage(with resource: ImageDownloadResource, placeholder: UIImage?, options: ImageDownloadOptionsInfo?, progressHandler: ((CGFloat) -> Void)?, completionHandler: ((Result) -> Void)?) -> ImageDownloadTask? {
var sdOptions: SDWebImageOptions = []
var context: [SDWebImageContextOption: Any] = [:]
if let options {
for option in options {
switch option {
case .imageProcessor(let size):
let imageProcessor = SDImageResizingTransformer(size: size, scaleMode: .aspectFill)
context[.imageTransformer] = imageProcessor
case .onlyLoadFirstFrame:
sdOptions.insert(.decodeFirstFrameOnly)
case .memoryCacheExpirationExpired:
sdOptions.insert(.refreshCached)
case .cacheOriginalImage, .fade, .scaleFactor:
break
}
}
}
sd_setImage(with: resource.downloadURL, placeholderImage: placeholder, options: sdOptions, context: context) { receivedSize, totalSize, _ in
let progress = CGFloat(receivedSize) / CGFloat(totalSize)
DispatchQueue.main.async {
progressHandler?(progress)
}
} completed: { image, error, cacheType, sourceURL in
if let image {
completionHandler?(.success(image))
}else {
if let error = error as? NSError, error.code == NSURLErrorCancelled {
completionHandler?(.failure(.cancel))
return
}
completionHandler?(.failure(.error(error)))
}
}
let downloadTask = ImageDownloadTask { [weak self] in
self?.sd_cancelCurrentImageLoad()
}
return downloadTask
}
@discardableResult
public func setVideoCover(with url: URL, placeholder: UIImage?, completionHandler: ((Result) -> Void)?) -> ImageDownloadTask? {
let cacheKey = url.absoluteString
if SDImageView.isCached(forKey: cacheKey) {
SDImageCache.shared.queryImage(forKey: cacheKey, options: [], context: nil) { (image, data, _) in
if let image {
completionHandler?(.success(image))
}else {
completionHandler?(.failure(.error(nil)))
}
}
return nil
}
var imageGenerator: AVAssetImageGenerator?
let avAsset = PhotoTools.getVideoThumbnailImage(url: url, atTime: 0.1) {
imageGenerator = $0
} completion: { _, image, _ in
guard let image else {
completionHandler?(.failure(.error(nil)))
return
}
SDImageCache.shared.store(image, imageData: nil, forKey: cacheKey, cacheType: .all) {
DispatchQueue.main.async {
completionHandler?(.success(image))
}
}
}
let task = ImageDownloadTask {
avAsset.cancelLoading()
imageGenerator?.cancelAllCGImageGeneration()
}
return task
}
@discardableResult
public static func download(with resource: ImageDownloadResource, options: ImageDownloadOptionsInfo?, progressHandler: ((CGFloat) -> Void)?, completionHandler: ((Result) -> Void)?) -> ImageDownloadTask? {
var sdOptions: SDWebImageDownloaderOptions = []
var context: [SDWebImageContextOption: Any] = [:]
if let options {
for option in options {
switch option {
case .imageProcessor(let size):
let imageProcessor = SDImageResizingTransformer(size: size, scaleMode: .aspectFill)
context[.imageTransformer] = imageProcessor
case .onlyLoadFirstFrame:
sdOptions.insert(.decodeFirstFrameOnly)
default:
break
}
}
}
let key = resource.cacheKey
if SDImageView.isCached(forKey: key) {
SDImageCache.shared.queryImage(forKey: key, options: [], context: nil) { (image, data, _) in
if let data = data {
completionHandler?(.success(.init(imageData: data)))
} else if let image = image as? SDAnimatedImage, let data = image.animatedImageData {
completionHandler?(.success(.init(imageData: data)))
} else if let image {
completionHandler?(.success(.init(image: image)))
} else {
completionHandler?(.failure(.error(nil)))
}
}
return nil
}
let operation = SDWebImageDownloader.shared.downloadImage(
with: resource.downloadURL,
options: sdOptions,
context: context,
progress: { receivedSize, totalSize, _ in
let progress = CGFloat(receivedSize) / CGFloat(totalSize)
DispatchQueue.main.async {
progressHandler?(progress)
}
},
completed: { image, data, error, finished in
guard let data = data, finished, error == nil else {
completionHandler?(.failure(.error(error)))
return
}
DispatchQueue.global().async {
let format = NSData.sd_imageFormat(forImageData: data)
if format == SDImageFormat.GIF, let gifImage = SDAnimatedImage(data: data) {
SDImageCache.shared.store(gifImage, imageData: data, forKey: key, options: [], context: nil, cacheType: .all) {
DispatchQueue.main.async {
completionHandler?(.success(.init(imageData: data)))
}
}
return
}
if let image = image {
SDImageCache.shared.store(image, imageData: data, forKey: key, options: [], context: nil, cacheType: .all) {
DispatchQueue.main.async {
completionHandler?(.success(.init(image: image)))
}
}
}
}
}
)
let downloadTask = ImageDownloadTask {
operation?.cancel()
}
return downloadTask
}
public func _startAnimating() {
startAnimating()
}
public func _stopAnimating() {
stopAnimating()
}
public static func getCacheKey(forURL url: URL) -> String {
SDWebImageManager.shared.cacheKey(for: url) ?? ""
}
public static func getCachePath(forKey key: String) -> String {
SDImageCache.shared.cachePath(forKey: key) ?? ""
}
public static func isCached(forKey key: String) -> Bool {
FileManager.default.fileExists(atPath: getCachePath(forKey: key))
}
public static func getInMemoryCacheImage(forKey key: String) -> UIImage? {
SDImageCache.shared.imageFromMemoryCache(forKey: key)
}
public static func getCacheImage(forKey key: String, completionHandler: ((UIImage?) -> Void)?) {
SDImageCache.shared.queryImage(forKey: key, context: nil, cacheType: .all) { image, data, _ in
if let data, let image = SDAnimatedImage(data: data) {
completionHandler?(image)
}else if let image {
completionHandler?(image)
}else {
completionHandler?(nil)
}
}
}
}
```Kingfisher(v6.0.0)
```swift
PickerConfiguration.imageViewProtocol = KFImageView.selfpublic class KFImageView: AnimatedImageView, HXImageViewProtocol {
public func setImageData(_ imageData: Data?) {
guard let imageData else { return }
let image: KFCrossPlatformImage? = DefaultImageProcessor.default.process(item: .data(imageData), options: .init([]))
self.image = image
}
@discardableResult
public func setImage(with resource: ImageDownloadResource, placeholder: UIImage?, options: ImageDownloadOptionsInfo?, progressHandler: ((CGFloat) -> Void)?, completionHandler: ((Result) -> Void)?) -> ImageDownloadTask? {
var kfOptions: KingfisherOptionsInfo = []
if let options {
for option in options {
switch option {
case .fade(let duration):
kfOptions += [.transition(.fade(duration))]
case .imageProcessor(let size):
let imageProcessor = DownsamplingImageProcessor(size: size)
kfOptions += [.processor(imageProcessor)]
case .onlyLoadFirstFrame:
kfOptions += [.onlyLoadFirstFrame]
case .cacheOriginalImage:
kfOptions += [.cacheOriginalImage]
case .memoryCacheExpirationExpired:
kfOptions += [.memoryCacheExpiration(.expired)]
case .scaleFactor(let scale):
kfOptions += [.scaleFactor(scale)]
}
}
}
let imageResource = Kingfisher.ImageResource(downloadURL: resource.downloadURL, cacheKey: resource.cacheKey)
if let indicatorColor = resource.indicatorColor {
kf.indicatorType = .activity
(kf.indicator?.view as? UIActivityIndicatorView)?.color = indicatorColor
}
let task = kf.setImage(with: imageResource, placeholder: placeholder, options: kfOptions) { receivedSize, totalSize in
progressHandler?(CGFloat(receivedSize) / CGFloat(totalSize))
} completionHandler: {
switch $0 {
case .success(let result):
completionHandler?(.success(result.image))
case .failure(let error):
completionHandler?(.failure(error.isTaskCancelled ? .cancel : .error(error)))
}
}
let downloadTask = ImageDownloadTask {
task?.cancel()
}
return downloadTask
}
public func setVideoCover(with url: URL, placeholder: UIImage?, completionHandler: ((Result) -> Void)?) -> ImageDownloadTask? {
let provider = AVAssetImageDataProvider(assetURL: url, seconds: 0.1)
provider.assetImageGenerator.appliesPreferredTrackTransform = true
let task = KF.dataProvider(provider)
.placeholder(placeholder)
.onSuccess { result in
completionHandler?(.success(result.image))
}
.onFailure { error in
completionHandler?(.failure(error.isTaskCancelled ? .cancel : .error(error)))
}
.set(to: self)
let downloadTask = ImageDownloadTask {
task?.cancel()
}
return downloadTask
}
@discardableResult
public static func download(with resource: ImageDownloadResource, options: ImageDownloadOptionsInfo?, progressHandler: ((CGFloat) -> Void)?, completionHandler: ((Result) -> Void)?) -> ImageDownloadTask? {
let key = resource.cacheKey
var kfOptions: KingfisherOptionsInfo = []
if let options {
for option in options {
switch option {
case .fade(let duration):
kfOptions += [.transition(.fade(duration))]
case .imageProcessor(let size):
let imageProcessor = DownsamplingImageProcessor(size: size)
kfOptions += [.processor(imageProcessor)]
case .onlyLoadFirstFrame:
kfOptions += [.onlyLoadFirstFrame]
case .cacheOriginalImage:
kfOptions += [.cacheOriginalImage]
case .memoryCacheExpirationExpired:
kfOptions += [.memoryCacheExpiration(.expired)]
case .scaleFactor(let scale):
kfOptions += [.scaleFactor(scale)]
}
}
}
if ImageCache.default.isCached(forKey: key) {
ImageCache.default.retrieveImage(
forKey: key,
options: kfOptions
) { (result) in
switch result {
case .success(let value):
if let data = value.image?.kf.gifRepresentation() {
completionHandler?(.success(.init(imageData: data)))
}else if let image = value.image {
completionHandler?(.success(.init(image: image)))
}else {
completionHandler?(.failure(.error(nil)))
}
case .failure(let error):
completionHandler?(.failure(.error(error)))
}
}
return nil
}
let task = ImageDownloader.default.downloadImage(with: resource.downloadURL, options: kfOptions) { receivedSize, totalSize in
let progress = CGFloat(receivedSize) / CGFloat(totalSize)
progressHandler?(progress)
} completionHandler: {
switch $0 {
case .success(let value):
DispatchQueue.global().async {
if let gifImage = DefaultImageProcessor.default.process(
item: .data(value.originalData),
options: .init([])
) {
ImageCache.default.store(
gifImage,
original: value.originalData,
forKey: key
)
DispatchQueue.main.async {
completionHandler?(.success(.init( imageData: value.originalData)))
}
return
}
ImageCache.default.store(
value.image,
original: value.originalData,
forKey: key
)
DispatchQueue.main.async {
completionHandler?(.success(.init(image: value.image)))
}
}
case .failure(let error):
completionHandler?(.failure(.error(error)))
}
}
let downloadTask = ImageDownloadTask {
task?.cancel()
}
return downloadTask
}
public func _startAnimating() {
startAnimating()
}
public func _stopAnimating() {
stopAnimating()
}
public static func getCacheKey(forURL url: URL) -> String {
url.cacheKey
}
public static func getCachePath(forKey key: String) -> String {
ImageCache.default.cachePath(forKey: key)
}
public static func isCached(forKey key: String) -> Bool {
ImageCache.default.isCached(forKey: key)
}
public static func getInMemoryCacheImage(forKey key: String) -> UIImage? {
ImageCache.default.retrieveImageInMemoryCache(forKey: key)
}
public static func getCacheImage(forKey key: String, completionHandler: ((UIImage?) -> Void)?) {
ImageCache.default.retrieveImage(forKey: key, options: []) {
switch $0 {
case .success(let result):
completionHandler?(result.image)
case .failure:
completionHandler?(nil)
}
}
}
}
```#### 获取 UIImage
```swift
/// 如果为视频的话获取则是视频封面
// async/await
// compression: 压缩参数,不传则不压缩
let image: UIImage = try await photoAsset.object(compression)/// 获取指定`Size`的`UIImage`
/// targetSize: 指定imageSize
/// targetMode: 裁剪模式
let image = try await photoAsset.image(targetSize: .init(width: 200, height: 200), targetMode: .fill)// compressionQuality: 压缩参数,不传则不压缩
photoAsset.getImage(compressionQuality: compressionQuality) { image in
print(image)
}
```#### 获取 URL
```swift
// async/await
// compression: 压缩参数,不传则不压缩
let url: URL = try await photoAsset.object(compression)
let urlResult: AssetURLResult = try await photoAsset.object(compression)// compression: 压缩参数,不传则不压缩
photoAsset.getURL(compression: compression) { result in
switch result {
case .success(let urlResult):
// 媒体类型
switch urlResult.mediaType {
case .photo:
// 图片
case .video:
// 视频
}
// url类型
switch urlResult.urlType {
case .local:
// 本地URL
case .network:
// 网络URL
}
// 获取的地址
print(urlResult.url)
// LivePhoto 里面包含的 图片和视频 url
print(urlResult.livePhoto)
case .failure(let error):
print(error)
}
}
```#### 获取其他
```swift
/// 获取缩略图
let thumImage = try await photoAsset.requesThumbnailImage()/// 获取预览图
let previewImage = try await photoAsset.requestPreviewImage()/// 获取 AVAsset
let avAsset = try await photoAsset.requestAVAsset()/// 获取 AVPlayerItem
let playerItem = try await photoAsset.requestPlayerItem()/// 获取 PHLivePhoto
let livePhoto = try await photoAsset.requestLivePhoto()
```最近更新
| 版本 | 发布时间 | Xcode | Swift | iOS |
| ---- | ---- | ---- | ---- | ---- |
| [v5.0.1](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#501) | 2025-03-31 | 16.0.0 | 6.0.0 | 10.0+ |历史记录
| 版本 | 发布时间 | Xcode | Swift | iOS |
| ---- | ---- | ---- | ---- | ---- |
| [v5.0.0](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#500) | 2025-03-03 | 16.0.0 | 6.0.0 | 10.0+ |
| [v4.2.5](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#425) | 2025-02-12 | 16.0.0 | 6.0.0 | 13.0+ |
| [v4.2.4](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#424) | 2024-12-14 | 16.0.0 | 6.0.0 | 13.0+ |
| [v4.2.3](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#423) | 2024-08-05 | 16.0.0 | 6.0.0 | 12.0+ |
| [v4.2.2](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#422) | 2024-07-08 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.2.1](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#421) | 2024-05-18 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.2.0](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#420) | 2024-04-23 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.1.9](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#419) | 2024-04-09 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.1.8](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#418) | 2024-03-24 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.1.7](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#417) | 2024-03-09 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.1.6](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#416) | 2024-02-16 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.1.5](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#415) | 2024-01-10 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.1.4](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#414) | 2023-12-24 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.1.3](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#413) | 2023-12-16 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.1.2](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#412) | 2023-12-02 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.1.1](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#411) | 2023-11-14 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.1.0](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#410) | 2023-11-07 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.0.9](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#409) | 2023-10-22 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.0.8](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#408) | 2023-10-13 | 15.0.0 | 5.9.0 | 12.0+ |
| [v4.0.7](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#407) | 2023-09-23 | 14.3.0 | 5.7.0 | 12.0+ |
| [v4.0.6](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#406) | 2023-09-09 | 14.3.0 | 5.7.0 | 12.0+ |
| [v4.0.5](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#405) | 2023-08-12 | 14.3.0 | 5.7.0 | 12.0+ |
| [v4.0.4](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#404) | 2023-07-30 | 14.3.0 | 5.7.0 | 12.0+ |
| [v4.0.3](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#403) | 2023-07-06 | 14.3.0 | 5.7.0 | 12.0+ |
| [v4.0.2](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#402) | 2023-06-24 | 14.3.0 | 5.7.0 | 12.0+ |
| [v4.0.1](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#401) | 2023-06-17 | 14.3.0 | 5.7.0 | 12.0+ |
| [v4.0.0](https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/RELEASE_NOTE_CN.md#400) | 2023-06-15 | 14.3.0 | 5.7.0 | 12.0+ |
| [v3.0.0](https://github.com/SilenceLove/HXPhotoPickerObjC#-%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95---update-history) | 2022-09-18 | 14.0.0 | ----- | 8.0+ || 选择照片 | 图片编辑 | 视频编辑 |
| ---- | ---- | ---- |
| [](http://oss-cn-hangzhou.aliyuncs.com/tsnrhapp/shop/videos/83862ab94facfd8979eb6148094908b2.mp4) | [](http://oss-cn-hangzhou.aliyuncs.com/tsnrhapp/shop/videos/3c81199474e33006e2cebd5f6241ead5.mp4) | [](http://oss-cn-hangzhou.aliyuncs.com/tsnrhapp/shop/videos/8c1cf86f32329e6464d327781f15041a.mp4) ||
|
|
|
| ---- | ---- | ---- |
||
|
|
|
|
|
| ---- | ---- |## 版权协议
HXPhotoPicker 基于 MIT 协议进行分发和使用,更多信息参见[协议文件](./LICENSE)。## 支持❤️
* [**★ Star**](#)
* 支持作者☕️
|
|
|
|
| ------ | ------ | ------ |[](https://starchart.cc/SilenceLove/HXPhotoPicker)
[🔝回到顶部](#readme)