{"id":13696721,"url":"https://github.com/SilenceLove/HXPhotoPicker","last_synced_at":"2025-05-03T17:32:06.962Z","repository":{"id":42234301,"uuid":"83772716","full_name":"SilenceLove/HXPhotoPicker","owner":"SilenceLove","description":"图片/视频选择器 - 支持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","archived":false,"fork":false,"pushed_at":"2024-10-16T09:12:29.000Z","size":59410,"stargazers_count":3085,"open_issues_count":13,"forks_count":662,"subscribers_count":54,"default_branch":"master","last_synced_at":"2024-10-29T15:09:24.936Z","etag":null,"topics":["3d-touch","browser","editor","gif","icloud","image","imagepicker","ios","livephoto","photo","photoeditor","photokit","photopicker","phpicker","picker","previewimage","swift","uiimagepickercontroller","videoeditor"],"latest_commit_sha":null,"homepage":"https://github.com/SilenceLove/HXPhotoPicker","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/SilenceLove.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":["silence"],"custom":["https://github.com/sponsors/SilenceLove"]}},"created_at":"2017-03-03T07:53:12.000Z","updated_at":"2024-10-29T09:29:20.000Z","dependencies_parsed_at":"2024-04-13T00:42:22.635Z","dependency_job_id":"126f0fd8-b85b-4fa6-a177-81a4b8a387b1","html_url":"https://github.com/SilenceLove/HXPhotoPicker","commit_stats":{"total_commits":457,"total_committers":12,"mean_commits":"38.083333333333336","dds":0.05032822757111599,"last_synced_commit":"44a082aa0ef6682affba505d2c666b5b9f09f2eb"},"previous_names":["lovezyforever/hxweibophotopicker"],"tags_count":107,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SilenceLove%2FHXPhotoPicker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SilenceLove%2FHXPhotoPicker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SilenceLove%2FHXPhotoPicker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SilenceLove%2FHXPhotoPicker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SilenceLove","download_url":"https://codeload.github.com/SilenceLove/HXPhotoPicker/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224115711,"owners_count":17258238,"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":["3d-touch","browser","editor","gif","icloud","image","imagepicker","ios","livephoto","photo","photoeditor","photokit","photopicker","phpicker","picker","previewimage","swift","uiimagepickercontroller","videoeditor"],"created_at":"2024-08-02T18:00:45.569Z","updated_at":"2025-05-03T17:32:06.941Z","avatar_url":"https://github.com/SilenceLove.png","language":"Swift","funding_links":["https://github.com/sponsors/silence","https://github.com/sponsors/SilenceLove","https://www.buymeacoffee.com/fengye"],"categories":["UI Components","iOS"],"sub_categories":["PhotoPicker"],"readme":"\u003ch4 align=\"right\"\u003e中文 | \u003cstrong\u003e\u003ca href=\"https://github.com/SilenceLove/HXPhotoPicker/blob/master/Documentation/README_EN.md\"\u003eEnglish\u003c/a\u003e\u003c/strong\u003e\u003c/h4\u003e\n      \n\u003cp align=\"center\"\u003e\n    \u003ca\u003e\u003cimg src=\"https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/README/sample_graph.png?raw=true\"  width = \"384\" height = \"292.65\" \u003e\u003c/a\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/SilenceLove/HXPhotoPicker\"\u003e\u003cimg src=\"https://travis-ci.org/SilenceLove/HXPhotoPicker.svg?branch=master\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/SilenceLove/HXPhotoPicker\"\u003e\u003cimg src=\"https://badgen.net/badge/icon/iOS%2010.0%2B?color=cyan\u0026icon=apple\u0026label\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/SilenceLove/HXPhotoPicker\"\u003e\u003cimg src=\"http://img.shields.io/cocoapods/v/HXPhotoPicker.svg?logo=cocoapods\u0026logoColor=ffffff\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://developer.apple.com/Swift\"\u003e\u003cimg src=\"http://img.shields.io/badge/language-Swift-orange.svg?logo=common-workflow-language\"\u003e\u003c/a\u003e\n    \u003ca href=\"http://mit-license.org\"\u003e\u003cimg src=\"http://img.shields.io/badge/license-MIT-333333.svg?logo=letterboxd\u0026logoColor=ffffff\"\u003e\u003c/a\u003e\n    \u003cdiv align=\"center\"\u003e一款图片/视频选择器-支持LivePhoto、GIF选择、iCloud/网络资源在线下载、图片/视频编辑\u003c/div\u003e\n\u003c/p\u003e\n\n## 目录\n* [功能](#功能)\n* [要求](#要求)\n* [安装](#安装)\n* [示例](#示例)\n    * [快速使用](#示例)\n    * [如何支持GIF/网络图片](#如何支持GIF/网络图片)\n    * [如何获取](#如何获取)\n* [更新记录](#更新记录)\n* [演示效果](#演示效果)\n* [界面展示](#界面展示)\n* [支持❤️](#支持❤️) \n\n## \u003ca id=\"功能\"\u003e\u003c/a\u003e 功能\n\n- [x] UI 外观支持浅色/深色/自动/自定义\n- [x] 支持多选/混合内容选择\n- [x] 支持的媒体类型：\n    - [x] Photo\n    - [x] GIF\n    - [x] Live Photo\n    - [x] Video\n- [x] 支持的本地资源类型：\n    - [x] Photo\n    - [x] Video\n    - [x] GIF\n    - [x] Live Photo\n- [x] 支持的网络资源类型：\n    - [x] Photo\n    - [x] Video\n- [x] 支持下载iCloud上的资源\n- [x] 支持手势返回\n- [x] 支持滑动选择\n- [x] 编辑图片（支持动图、网络资源）\n    - [x] 涂鸦\n    - [x] 贴纸\n    - [x] 文字\n    - [x] 裁剪\n    - [x] 旋转任意角度\n    - [x] 自定义蒙版\n    - [x] 马赛克\n    - [x] 画面调整\n    - [x] 滤镜\n- [x] 编辑视频（支持网络资源）\n    - [x] 涂鸦\n    - [x] 贴纸（支持GIF）\n    - [x] 文字\n    - [x] 配乐（支持歌词字幕）\n    - [x] 裁剪时长\n    - [x] 裁剪尺寸\n    - [x] 旋转任意角度\n    - [x] 自定义蒙版\n    - [x] 画面调整\n    - [x] 滤镜\n- [x] 相册展现方式\n    - [x] 单独列表\n    - [x] 弹窗\n- [x] 多平台支持\n    - [x] iOS\n    - [x] iPadOS\n    - [x] Mac Catalyst\n- [x] 国际化支持\n    - [x] 🇨🇳 简体中文 (zh-Hans)\n    - [x] 🇨🇳 繁体中文 (zh-Hant)\n    - [x] 🇬🇧 英文 (en)\n    - [x] 🇯🇵 日语 (ja)\n    - [x] 🇰🇷 韩语 (ko)\n    - [x] 🇹🇭 泰语 (th)\n    - [x] 🇮🇳 印尼语 (id)\n    - [x] 🇻🇳 越南语 (vi)\n    - [x] 🇷🇺 俄罗斯 (ru)\n    - [x] 🇩🇪 德国 (de)\n    - [x] 🇫🇷 法国 (fr)\n    - [x] 🇸🇦 阿拉伯 (ar)\n    - [x] ✍️ 自定义语言 (custom)\n    - [ ] 🤝 更多支持... (欢迎PR)\n\n## \u003ca id=\"要求\"\u003e\u003c/a\u003e 要求\n\n- iOS 10.0+\n- Xcode 12.5+\n- Swift 5.4+\n\n## \u003ca id=\"安装\"\u003e\u003c/a\u003e 安装\n\n### [Swift Package Manager](https://swift.org/package-manager/)\n\n⚠️ 需要 Xcode 13.0 及以上版本来支持资源文件/本地化文件的添加。\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/SilenceLove/HXPhotoPicker.git\", .upToNextMajor(from: \"5.0.1\"))\n]\n```\n\n### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)\n\n将下面内容添加到 `Podfile`，并执行依赖更新。\n\n```swift\n\n/// iOS 10.0+ 默认不支持GIF和网络图片\npod 'HXPhotoPicker'\n\n/// 使用`SwiftyGif`加载GIF图片\npod 'HXPhotoPicker/SwiftyGif'\n\n/// 使用`SDWebImage`加载GIF/网络图片\npod 'HXPhotoPicker/SDWebImage'\n\n/// 使用`Kingfisher v6.0.0`加载GIF/网络图片\npod 'HXPhotoPicker/Kingfisher'\n\n/// 相机不包含定位功能\npod `HXPhotoPicker/NoLocation`\n\n/// 只有选择器\npod `HXPhotoPicker/Picker`\n\n/// 只有编辑器\npod `HXPhotoPicker/Editor`\n\n/// 只有相机\npod `HXPhotoPicker/Camera`\n/// 不包含定位功能\npod `HXPhotoPicker/Camera/Lite`\n\nv4.0以下的ObjC版本\npod 'HXPhotoPickerObjC'\n```\n\n### 准备工作\n\n按需在你的 Info.plist 中添加以下键值:\n\n| Key | 模块 | 备注 |\n| ----- | ----  | ---- |\n| NSPhotoLibraryUsageDescription | Picker | 允许访问相册 |\n| NSPhotoLibraryAddUsageDescription | Picker | 允许保存图片至相册 |\n| PHPhotoLibraryPreventAutomaticLimitedAccessAlert | Picker | 设置为 `YES` iOS 14+ 以禁用自动弹出添加更多照片的弹框(Picker 已适配 Limited 功能，可由用户主动触发，提升用户体验) |\n| NSCameraUsageDescription | Camera | 允许使用相机 |\n| NSMicrophoneUsageDescription | Camera | 允许使用麦克风 |\n\n### \u003ca id=\"示例\"\u003e\u003c/a\u003e 快速上手\n```swift\nimport HXPhotoPicker\n\nclass ViewController: UIViewController {\n\n    func presentPickerController() {\n        // 设置与微信主题一致的配置\n        let config = PickerConfiguration.default\n        \n        // 方法一：async/await\n        // 使用`Photo`\n        let images: [UIImage] = try await Photo.picker(config)\n        let urls: [URL] = try await Photo.picker(config)\n        let urlResult: [AssetURLResult] = try await Photo.picker(config)\n        let assetResult: [AssetResult] = try await Photo.picker(config)\n        // 使用`PhotoPickerController`\n        let images: [UIImage] = try await PhotoPickerController.picker(config)\n        let urls: [URL] = try await PhotoPickerController.picker(config)\n        let urlResult: [AssetURLResult] = try await PhotoPickerController.picker(config)\n        let assetResult: [AssetResult] = try await PhotoPickerController.picker(config)\n        \n        let pickerResult = try await Photo.picker(config)\n        let images: [UIImage] = try await pickerResult.objects()\n        let urls: [URL] = try await pickerResult.objects()\n        let urlResults: [AssetURLResult] = try await pickerResult.objects()\n        let assetResults: [AssetResult] = try await pickerResult.objects()\n        \n        // 方法二：\n        let pickerController = PhotoPickerController(picker: config)\n        pickerController.pickerDelegate = self\n        // 当前被选择的资源对应的 PhotoAsset 对象数组\n        pickerController.selectedAssetArray = selectedAssets \n        // 是否选中原图\n        pickerController.isOriginal = isOriginal\n        present(pickerController, animated: true, completion: nil)\n        \n        // 方法三：\n        Photo.picker(\n            config\n        ) { result, pickerController in\n            // 选择完成的回调\n            // result 选择结果\n            //  .photoAssets 当前选择的数据\n            //  .isOriginal 是否选中了原图\n            // photoPickerController 对应的照片选择控制器\n        } cancel: { pickerController in\n            // 取消的回调\n            // photoPickerController 对应的照片选择控制器 \n        }\n    }\n}\n\nextension ViewController: PhotoPickerControllerDelegate {\n    \n    /// 选择完成之后调用\n    /// - Parameters:\n    ///   - pickerController: 对应的 PhotoPickerController\n    ///   - result: 选择的结果\n    ///     result.photoAssets  选择的资源数组\n    ///     result.isOriginal   是否选中原图\n    func pickerController(\n        _ pickerController: PhotoPickerController, \n        didFinishSelection result: PickerResult\n    ) {\n        // async/await\n        let images: [UIImage] = try await result.objects()\n        let urls: [URL] = try await result.objects()\n        let urlResults: [AssetURLResult] = try await result.objects()\n        let assetResults: [AssetResult] = try await result.objects()\n        \n        result.getImage { (image, photoAsset, index) in\n            if let image = image {\n                print(\"success\", image)\n            }else {\n                print(\"failed\")\n            }\n        } completionHandler: { (images) in\n            print(images)\n        }\n    }\n    \n    /// 点击取消时调用\n    /// - Parameter pickerController: 对应的 PhotoPickerController\n    func pickerController(didCancel pickerController: PhotoPickerController) {\n        \n    }\n}\n```\n\n### \u003ca id=\"如何支持GIF/网络图片\"\u003e\u003c/a\u003e 如何支持GIF/网络图片 [HXImageViewProtocol](https://github.com/SilenceLove/HXPhotoPicker/tree/master/Sources/HXPhotoPicker/Core/Config/HXImageViewProtocol.swift)\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003e\u003ca href=\"https://github.com/SilenceLove/HXPhotoPicker/tree/master/Sources/ImageView/GIFImageView.swift\"\u003eSwiftyGif\u003c/a\u003e \u003c/strong\u003e\u003c/summary\u003e\n  \n```swift\nPickerConfiguration.imageViewProtocol = GIFImageView.self\n\npublic class GIFImageView: UIImageView, HXImageViewProtocol {\n    public func setImageData(_ imageData: Data?) {\n        guard let imageData else {\n            clear()\n            SwiftyGifManager.defaultManager.deleteImageView(self)\n            image = nil\n            return\n        }\n        if let image = try? UIImage(gifData: imageData) {\n            setGifImage(image)\n        }else {\n            image = .init(data: imageData)\n        }\n    }\n    \n    public func _startAnimating() {\n        startAnimatingGif()\n    }\n    \n    public func _stopAnimating() {\n        stopAnimatingGif()\n    }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003e\u003ca href=\"https://github.com/SilenceLove/HXPhotoPicker/tree/master/Sources/ImageView/SDImageView.swift\"\u003eSDWebImage\u003c/a\u003e\u003c/strong\u003e\u003c/summary\u003e\n  \n```swift\nPickerConfiguration.imageViewProtocol = SDImageView.self\n\npublic class SDImageView: SDAnimatedImageView, HXImageViewProtocol {\n    public func setImageData(_ imageData: Data?) {\n        guard let imageData else { return }\n        let image = SDAnimatedImage(data: imageData)\n        self.image = image\n    }\n    \n    @discardableResult\n    public func setImage(with resource: ImageDownloadResource, placeholder: UIImage?, options: ImageDownloadOptionsInfo?, progressHandler: ((CGFloat) -\u003e Void)?, completionHandler: ((Result\u003cUIImage, ImageDownloadError\u003e) -\u003e Void)?) -\u003e ImageDownloadTask? {\n        var sdOptions: SDWebImageOptions = []\n        var context: [SDWebImageContextOption: Any] = [:]\n        if let options {\n            for option in options {\n                switch option {\n                case .imageProcessor(let size):\n                    let imageProcessor = SDImageResizingTransformer(size: size, scaleMode: .aspectFill)\n                    context[.imageTransformer] = imageProcessor\n                case .onlyLoadFirstFrame:\n                    sdOptions.insert(.decodeFirstFrameOnly)\n                case .memoryCacheExpirationExpired:\n                    sdOptions.insert(.refreshCached)\n                case .cacheOriginalImage, .fade, .scaleFactor:\n                    break\n                }\n            }\n        }\n        sd_setImage(with: resource.downloadURL, placeholderImage: placeholder, options: sdOptions, context: context) { receivedSize, totalSize, _ in\n            let progress = CGFloat(receivedSize) / CGFloat(totalSize)\n            DispatchQueue.main.async {\n                progressHandler?(progress)\n            }\n        } completed: { image, error, cacheType, sourceURL in\n            if let image {\n                completionHandler?(.success(image))\n            }else {\n                if let error = error as? NSError, error.code == NSURLErrorCancelled {\n                    completionHandler?(.failure(.cancel))\n                    return\n                }\n                completionHandler?(.failure(.error(error)))\n            }\n        }\n        let downloadTask = ImageDownloadTask { [weak self] in\n            self?.sd_cancelCurrentImageLoad()\n        }\n        return downloadTask\n    }\n    \n    @discardableResult\n    public func setVideoCover(with url: URL, placeholder: UIImage?, completionHandler: ((Result\u003cUIImage, ImageDownloadError\u003e) -\u003e Void)?) -\u003e ImageDownloadTask? {\n        let cacheKey = url.absoluteString\n        if SDImageView.isCached(forKey: cacheKey) {\n            SDImageCache.shared.queryImage(forKey: cacheKey, options: [], context: nil) { (image, data, _) in\n                if let image {\n                    completionHandler?(.success(image))\n                }else {\n                    completionHandler?(.failure(.error(nil)))\n                }\n            }\n            return nil\n        }\n        var imageGenerator: AVAssetImageGenerator?\n        let avAsset = PhotoTools.getVideoThumbnailImage(url: url, atTime: 0.1) {\n            imageGenerator = $0\n        } completion: { _, image, _ in\n            guard let image else {\n                completionHandler?(.failure(.error(nil)))\n                return\n            }\n            SDImageCache.shared.store(image, imageData: nil, forKey: cacheKey, cacheType: .all) {\n                DispatchQueue.main.async {\n                    completionHandler?(.success(image))\n                }\n            }\n        }\n        let task = ImageDownloadTask {\n            avAsset.cancelLoading()\n            imageGenerator?.cancelAllCGImageGeneration()\n        }\n        return task\n    }\n    \n    @discardableResult\n    public static func download(with resource: ImageDownloadResource, options: ImageDownloadOptionsInfo?, progressHandler: ((CGFloat) -\u003e Void)?, completionHandler: ((Result\u003cImageDownloadResult, ImageDownloadError\u003e) -\u003e Void)?) -\u003e ImageDownloadTask? {\n        var sdOptions: SDWebImageDownloaderOptions = []\n        var context: [SDWebImageContextOption: Any] = [:]\n        if let options {\n            for option in options {\n                switch option {\n                case .imageProcessor(let size):\n                    let imageProcessor = SDImageResizingTransformer(size: size, scaleMode: .aspectFill)\n                    context[.imageTransformer] = imageProcessor\n                case .onlyLoadFirstFrame:\n                    sdOptions.insert(.decodeFirstFrameOnly)\n                default:\n                    break\n                }\n            }\n        }\n        let key = resource.cacheKey\n        if SDImageView.isCached(forKey: key) {\n            SDImageCache.shared.queryImage(forKey: key, options: [], context: nil) { (image, data, _) in\n                if let data = data  {\n                    completionHandler?(.success(.init(imageData: data)))\n                } else if let image = image as? SDAnimatedImage, let data = image.animatedImageData {\n                    completionHandler?(.success(.init(imageData: data)))\n                } else if let image {\n                    completionHandler?(.success(.init(image: image)))\n                } else {\n                    completionHandler?(.failure(.error(nil)))\n                }\n            }\n            return nil\n        }\n        let operation = SDWebImageDownloader.shared.downloadImage(\n            with: resource.downloadURL,\n            options: sdOptions,\n            context: context,\n            progress: { receivedSize, totalSize, _ in\n                let progress = CGFloat(receivedSize) / CGFloat(totalSize)\n                DispatchQueue.main.async {\n                    progressHandler?(progress)\n                }\n            },\n            completed: { image, data, error, finished in\n                guard let data = data, finished, error == nil else {\n                    completionHandler?(.failure(.error(error)))\n                    return\n                }\n                DispatchQueue.global().async {\n                    let format = NSData.sd_imageFormat(forImageData: data)\n                    if format == SDImageFormat.GIF, let gifImage = SDAnimatedImage(data: data) {\n                        SDImageCache.shared.store(gifImage, imageData: data, forKey: key, options: [], context: nil, cacheType: .all) {\n                            DispatchQueue.main.async {\n                                completionHandler?(.success(.init(imageData: data)))\n                            }\n                        }\n                        return\n                    }\n                    if let image = image {\n                        SDImageCache.shared.store(image, imageData: data, forKey: key, options: [], context: nil, cacheType: .all) {\n                            DispatchQueue.main.async {\n                                completionHandler?(.success(.init(image: image)))\n                            }\n                        }\n                    }\n                }\n            }\n        )\n        let downloadTask = ImageDownloadTask {\n            operation?.cancel()\n        }\n        return downloadTask\n    }\n    \n    public func _startAnimating() {\n        startAnimating()\n    }\n    \n    public func _stopAnimating() {\n        stopAnimating()\n    }\n    \n    public static func getCacheKey(forURL url: URL) -\u003e String {\n        SDWebImageManager.shared.cacheKey(for: url) ?? \"\"\n    }\n    \n    public static func getCachePath(forKey key: String) -\u003e String {\n        SDImageCache.shared.cachePath(forKey: key) ?? \"\"\n    }\n    \n    public static func isCached(forKey key: String) -\u003e Bool {\n        FileManager.default.fileExists(atPath: getCachePath(forKey: key))\n    }\n    \n    public static func getInMemoryCacheImage(forKey key: String) -\u003e UIImage? {\n        SDImageCache.shared.imageFromMemoryCache(forKey: key)\n    }\n    \n    public static func getCacheImage(forKey key: String, completionHandler: ((UIImage?) -\u003e Void)?) {\n        SDImageCache.shared.queryImage(forKey: key, context: nil, cacheType: .all) { image, data, _ in\n            if let data, let image = SDAnimatedImage(data: data) {\n                completionHandler?(image)\n            }else if let image {\n                completionHandler?(image)\n            }else {\n                completionHandler?(nil)\n            }\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003e\u003ca href=\"https://github.com/SilenceLove/HXPhotoPicker/tree/master/Sources/ImageView/KFImageView.swift\"\u003eKingfisher(v6.0.0)\u003c/a\u003e\u003c/strong\u003e\u003c/summary\u003e\n  \n```swift\nPickerConfiguration.imageViewProtocol = KFImageView.self\n\npublic class KFImageView: AnimatedImageView, HXImageViewProtocol {\n    public func setImageData(_ imageData: Data?) {\n        guard let imageData else { return }\n        let image: KFCrossPlatformImage? = DefaultImageProcessor.default.process(item: .data(imageData), options: .init([]))\n        self.image = image\n    }\n    \n    @discardableResult\n    public func setImage(with resource: ImageDownloadResource, placeholder: UIImage?, options: ImageDownloadOptionsInfo?, progressHandler: ((CGFloat) -\u003e Void)?, completionHandler: ((Result\u003cUIImage, ImageDownloadError\u003e) -\u003e Void)?) -\u003e ImageDownloadTask? {\n        var kfOptions: KingfisherOptionsInfo = []\n        if let options {\n            for option in options {\n                switch option {\n                case .fade(let duration):\n                    kfOptions += [.transition(.fade(duration))]\n                case .imageProcessor(let size):\n                    let imageProcessor = DownsamplingImageProcessor(size: size)\n                    kfOptions += [.processor(imageProcessor)]\n                case .onlyLoadFirstFrame:\n                    kfOptions += [.onlyLoadFirstFrame]\n                case .cacheOriginalImage:\n                    kfOptions += [.cacheOriginalImage]\n                case .memoryCacheExpirationExpired:\n                    kfOptions += [.memoryCacheExpiration(.expired)]\n                case .scaleFactor(let scale):\n                    kfOptions += [.scaleFactor(scale)]\n                }\n            }\n        }\n        let imageResource = Kingfisher.ImageResource(downloadURL: resource.downloadURL, cacheKey: resource.cacheKey)\n        if let indicatorColor = resource.indicatorColor {\n            kf.indicatorType = .activity\n            (kf.indicator?.view as? UIActivityIndicatorView)?.color = indicatorColor\n        }\n        let task = kf.setImage(with: imageResource, placeholder: placeholder, options: kfOptions) { receivedSize, totalSize in\n            progressHandler?(CGFloat(receivedSize) / CGFloat(totalSize))\n        } completionHandler: {\n            switch $0 {\n            case .success(let result):\n                completionHandler?(.success(result.image))\n            case .failure(let error):\n                completionHandler?(.failure(error.isTaskCancelled ? .cancel : .error(error)))\n            }\n        }\n        let downloadTask = ImageDownloadTask {\n            task?.cancel()\n        }\n        return downloadTask\n    }\n    \n    public func setVideoCover(with url: URL, placeholder: UIImage?, completionHandler: ((Result\u003cUIImage, ImageDownloadError\u003e) -\u003e Void)?) -\u003e ImageDownloadTask? {\n        let provider = AVAssetImageDataProvider(assetURL: url, seconds: 0.1)\n        provider.assetImageGenerator.appliesPreferredTrackTransform = true\n        let task = KF.dataProvider(provider)\n            .placeholder(placeholder)\n            .onSuccess { result in\n                completionHandler?(.success(result.image))\n            }\n            .onFailure { error in\n                completionHandler?(.failure(error.isTaskCancelled ? .cancel : .error(error)))\n            }\n            .set(to: self)\n        let downloadTask = ImageDownloadTask {\n            task?.cancel()\n        }\n        return downloadTask\n    }\n    \n    @discardableResult\n    public static func download(with resource: ImageDownloadResource, options: ImageDownloadOptionsInfo?, progressHandler: ((CGFloat) -\u003e Void)?, completionHandler: ((Result\u003cImageDownloadResult, ImageDownloadError\u003e) -\u003e Void)?) -\u003e ImageDownloadTask? {\n        let key = resource.cacheKey\n        var kfOptions: KingfisherOptionsInfo = []\n        if let options {\n            for option in options {\n                switch option {\n                case .fade(let duration):\n                    kfOptions += [.transition(.fade(duration))]\n                case .imageProcessor(let size):\n                    let imageProcessor = DownsamplingImageProcessor(size: size)\n                    kfOptions += [.processor(imageProcessor)]\n                case .onlyLoadFirstFrame:\n                    kfOptions += [.onlyLoadFirstFrame]\n                case .cacheOriginalImage:\n                    kfOptions += [.cacheOriginalImage]\n                case .memoryCacheExpirationExpired:\n                    kfOptions += [.memoryCacheExpiration(.expired)]\n                case .scaleFactor(let scale):\n                    kfOptions += [.scaleFactor(scale)]\n                }\n            }\n        }\n        if ImageCache.default.isCached(forKey: key) {\n            ImageCache.default.retrieveImage(\n                forKey: key,\n                options: kfOptions\n            ) { (result) in\n                switch result {\n                case .success(let value):\n                    if let data = value.image?.kf.gifRepresentation() {\n                        completionHandler?(.success(.init(imageData: data)))\n                    }else if let image = value.image {\n                        completionHandler?(.success(.init(image: image)))\n                    }else {\n                        completionHandler?(.failure(.error(nil)))\n                    }\n                case .failure(let error):\n                    completionHandler?(.failure(.error(error)))\n                }\n            }\n            return nil\n        }\n        let task =  ImageDownloader.default.downloadImage(with: resource.downloadURL, options: kfOptions) { receivedSize, totalSize in\n            let progress = CGFloat(receivedSize) / CGFloat(totalSize)\n            progressHandler?(progress)\n        } completionHandler: {\n            switch $0 {\n            case .success(let value):\n                DispatchQueue.global().async {\n                    if let gifImage = DefaultImageProcessor.default.process(\n                        item: .data(value.originalData),\n                        options: .init([])\n                    ) {\n                        ImageCache.default.store(\n                            gifImage,\n                            original: value.originalData,\n                            forKey: key\n                        )\n                        DispatchQueue.main.async {\n                            completionHandler?(.success(.init( imageData: value.originalData)))\n                        }\n                        return\n                    }\n                    ImageCache.default.store(\n                        value.image,\n                        original: value.originalData,\n                        forKey: key\n                    )\n                    DispatchQueue.main.async {\n                        completionHandler?(.success(.init(image: value.image)))\n                    }\n                }\n            case .failure(let error):\n                completionHandler?(.failure(.error(error)))\n            }\n        }\n        let downloadTask = ImageDownloadTask {\n            task?.cancel()\n        }\n        return downloadTask\n    }\n    \n    public func _startAnimating() {\n        startAnimating()\n    }\n    \n    public func _stopAnimating() {\n        stopAnimating()\n    }\n    \n    public static func getCacheKey(forURL url: URL) -\u003e String {\n        url.cacheKey\n    }\n    \n    public static func getCachePath(forKey key: String) -\u003e String {\n        ImageCache.default.cachePath(forKey: key)\n    }\n    \n    public static func isCached(forKey key: String) -\u003e Bool {\n        ImageCache.default.isCached(forKey: key)\n    }\n    \n    public static func getInMemoryCacheImage(forKey key: String) -\u003e UIImage? {\n        ImageCache.default.retrieveImageInMemoryCache(forKey: key)\n    }\n    \n    public static func getCacheImage(forKey key: String, completionHandler: ((UIImage?) -\u003e Void)?) {\n        ImageCache.default.retrieveImage(forKey: key, options: []) {\n            switch $0 {\n            case .success(let result):\n                completionHandler?(result.image)\n            case .failure:\n                completionHandler?(nil)\n            }\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n\n### \u003ca id=\"如何获取\"\u003e\u003c/a\u003e 如何获取\n\n#### 获取 UIImage\n\n```swift\n/// 如果为视频的话获取则是视频封面\n// async/await\n// compression: 压缩参数，不传则不压缩 \nlet image: UIImage = try await photoAsset.object(compression)\n\n/// 获取指定`Size`的`UIImage`\n/// targetSize: 指定imageSize\n/// targetMode: 裁剪模式\nlet image = try await photoAsset.image(targetSize: .init(width: 200, height: 200), targetMode: .fill)\n\n// compressionQuality: 压缩参数，不传则不压缩 \nphotoAsset.getImage(compressionQuality: compressionQuality) { image in\n    print(image)\n}\n```\n\n#### 获取 URL\n\n```swift\n// async/await \n// compression: 压缩参数，不传则不压缩 \nlet url: URL = try await photoAsset.object(compression)\nlet urlResult: AssetURLResult = try await photoAsset.object(compression)\n\n// compression: 压缩参数，不传则不压缩\nphotoAsset.getURL(compression: compression) { result in\n    switch result {\n    case .success(let urlResult):\n        // 媒体类型\n        switch urlResult.mediaType {\n        case .photo:\n            // 图片\n        case .video:\n            // 视频\n        }\n        \n        // url类型\n        switch urlResult.urlType {\n        case .local:\n            // 本地URL\n        case .network:\n            // 网络URL\n        }\n        \n        // 获取的地址\n        print(urlResult.url)\n        \n        // LivePhoto 里面包含的 图片和视频 url\n        print(urlResult.livePhoto) \n        \n    case .failure(let error):\n        print(error)\n    }\n}\n```\n\n#### 获取其他\n\n```swift\n/// 获取缩略图\nlet thumImage = try await photoAsset.requesThumbnailImage()\n\n/// 获取预览图\nlet previewImage = try await photoAsset.requestPreviewImage()\n\n/// 获取 AVAsset\nlet avAsset = try await photoAsset.requestAVAsset()\n\n/// 获取 AVPlayerItem\nlet playerItem = try await photoAsset.requestPlayerItem()\n\n/// 获取 PHLivePhoto\nlet livePhoto = try await photoAsset.requestLivePhoto()\n```\n\n## \u003ca id=\"更新记录\"\u003e\u003c/a\u003e 更新日志\n\n\u003cdetails open id=\"最近更新\"\u003e\n  \u003csummary\u003e\u003cstrong\u003e最近更新\u003c/strong\u003e\u003c/summary\u003e\n  \n| 版本 | 发布时间 | Xcode | Swift | iOS |\n| ---- | ----  | ---- | ---- | ---- |\n| [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+ | \n\n\u003c/details\u003e\n\n\u003cdetails id=\"历史记录\"\u003e\n  \u003csummary\u003e\u003cstrong\u003e历史记录\u003c/strong\u003e\u003c/summary\u003e\n  \n| 版本 | 发布时间 | Xcode | Swift | iOS |\n| ---- | ----  | ---- | ---- | ---- |\n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ | \n| [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+ |\n| [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+ |\n| [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+ |\n| [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+ |\n| [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+ |\n| [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+ |\n| [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+ |\n| [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+ |\n| [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+ |\n| [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+ |\n| [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+ | \n\n\u003c/details\u003e\n\n## \u003ca id=\"演示效果\"\u003e\u003c/a\u003e 演示效果\n\n| 选择照片 | 图片编辑 | 视频编辑 | \n| ---- | ----  | ---- |\n| [![IMAGE ALT TEXT](https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/README/Videos/photo_list_picker_cover.png?raw=true)](http://oss-cn-hangzhou.aliyuncs.com/tsnrhapp/shop/videos/83862ab94facfd8979eb6148094908b2.mp4) | [![IMAGE ALT TEXT](https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/README/Videos/photo_editor_cover.png?raw=true)](http://oss-cn-hangzhou.aliyuncs.com/tsnrhapp/shop/videos/3c81199474e33006e2cebd5f6241ead5.mp4) | [![IMAGE ALT TEXT](https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/README/Videos/video_editor_cover.png?raw=true)](http://oss-cn-hangzhou.aliyuncs.com/tsnrhapp/shop/videos/8c1cf86f32329e6464d327781f15041a.mp4) | \n\n## \u003ca id=\"界面展示\"\u003e\u003c/a\u003e 界面展示\n\n| \u003cimg src=\"https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/README/Photos/sample_graph_photo_picker_list.png?raw=true\"\u003e | \u003cimg src=\"https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/README/Photos/sample_graph_photo_preview.png?raw=true\"\u003e | \u003cimg src=\"https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/README/Photos/sample_graph_photo_editor_filter.png?raw=true\"\u003e | \n| ---- | ----  | ---- |\n| \u003cimg src=\"https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/README/Photos/sample_graph_video_editor_time.png?raw=true\"\u003e | \u003cimg src=\"https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/README/Photos/sample_graph_video_editor_edit.png?raw=true\"\u003e | \u003cimg src=\"https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/README/Photos/sample_graph_video_editor_crop_size.png?raw=true\"\u003e |\n\n| \u003cimg src=\"https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/README/Photos/sample_graph_photo_editor_crop_size_horizontal_screen.png?raw=true\"\u003e | \u003cimg src=\"https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/README/Photos/sample_graph_video_editor_crop_size_horizontal_screen.png?raw=true\"\u003e |\n| ---- | ----  |\n\n## 版权协议 \nHXPhotoPicker 基于 MIT 协议进行分发和使用，更多信息参见[协议文件](./LICENSE)。 \n\n## \u003ca id=\"支持❤️\"\u003e\u003c/a\u003e 支持❤️\n* [**★ Star**](#)\n* 支持作者☕️ \n    \n\u003cdiv align=\"left\"\u003e\u003ca href=\"https://www.buymeacoffee.com/fengye\" target=\"_blank\"\u003e\u003cimg src=\"https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png\" alt=\"Buy Me A Coffee\" style=\"height: 60px !important;width: 217px !important;\" \u003e\u003c/a\u003e\u003c/div\u003e \n\n| \u003cimg src=\"https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/Support/bmc_qr.png?raw=true\" width = \"135\" height = \"135\" /\u003e | \u003cimg src=\"https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/Support/ap.jpeg?raw=true\" width = \"100\" height = \"135.75\" /\u003e   | \u003cimg src=\"https://github.com/SilenceLove/PictureMaterial/blob/main/HXPhotoPicker/Support/wp.jpeg?raw=true\" width = \"100\" height = \"135.75\" /\u003e |\n| ------ | ------ | ------ | \n\n[![Stargazers over time](https://starchart.cc/SilenceLove/HXPhotoPicker.svg)](https://starchart.cc/SilenceLove/HXPhotoPicker)\n\n\n[🔝回到顶部](#readme)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSilenceLove%2FHXPhotoPicker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FSilenceLove%2FHXPhotoPicker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSilenceLove%2FHXPhotoPicker/lists"}