SwiftUI使用DropDelegate时如何在iPadOS/iOS获取拖拽图片的源URL及元数据
你有一个遵循DropDelegate协议的SwiftUI视图,可以正常在macOS和iPadOS中接收其他应用拖拽的图片,但两个操作系统的处理行为存在明显差异:
struct ContentView: View { var body: some View { MyView { ... } .onDrop(of: [.fileURL, .image], delegate: self) } }
DropDelegate的实现代码如下:
extension ContentView: DropDelegate { func performDrop(info: DropInfo) -> Bool { // 调试实现 if info.hasItemsConforming(to: [.image]) { print("CONTAINS AN IMAGE") } else if info.hasItemsConforming(to: [.fileURL]) { print("CONTAINS AN URL") } } }
实际运行时,macOS环境下输出为CONTAINS AN URL,iPadOS环境下输出为CONTAINS AN IMAGE。
你需要获取图片源URL的元数据(例如creationDate创建日期),但若拖拽返回的是UIImage对象则无法读取对应元数据。需要解决的问题:如何通过DropDelegate在iPadOS/iOS中获取拖拽图片的源URL?或者如何获取DropDelegate提供的UIImage对应的创建日期?
适用开发环境:Xcode 13 | macOS 12 | iPadOS 15 | Swift 5.5 | SwiftUI 3.0
iPadOS/iOS的拖拽会话中,图片类型的拖拽项通常会同时携带.image和.fileURL两种UTI标识,你当前的代码优先判断是否符合.image类型,所以会优先命中图片分支,跳过了URL的读取逻辑;而macOS下拖拽图片优先提供.fileURL类型,所以会走到URL分支。
方案1:优先读取文件URL(推荐)
调整判断逻辑,优先尝试读取拖拽项的文件URL,绝大多数场景下即使拖拽项符合.image的UTI,依然可以拿到对应的源文件地址,进而读取元数据:
extension ContentView: DropDelegate { func performDrop(info: DropInfo) -> Bool { // 按优先级处理拖拽项,优先读取URL for itemProvider in info.itemProviders(for: [.fileURL, .image]) { // 先尝试加载文件URL if itemProvider.canLoadObject(ofClass: NSURL.self) { itemProvider.loadObject(ofClass: NSURL.self) { obj, error in guard let url = obj as? URL, error == nil else { return } // 跨应用URL需要申请临时访问权限 let isAccessible = url.startAccessingSecurityScopedResource() defer { if isAccessible { url.stopAccessingSecurityScopedResource() } } // 读取文件元数据 do { let resourceValues = try url.resourceValues(forKeys: [.creationDateKey]) if let createDate = resourceValues.creationDate { print("图片创建日期:\(createDate)") } // 若需要读取EXIF中的拍摄日期,使用ImageIO if let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil), let exifInfo = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [AnyHashable: Any], let exifDate = exifInfo[kCGImagePropertyExifDateTimeOriginal] as? String { print("EXIF拍摄日期:\(exifDate)") } } catch { print("读取元数据失败:\(error.localizedDescription)") } } return true } // 无URL时降级处理UIImage else if itemProvider.canLoadObject(ofClass: UIImage.self) { itemProvider.loadObject(ofClass: UIImage.self) { obj, error in guard let image = obj as? UIImage, error == nil else { return } // 此处处理无URL的图片场景 } return true } } return false } }
方案2:相册拖拽场景特殊处理
如果是从系统「照片」App拖拽的图片无法直接拿到文件URL,可以通过读取照片本地标识查询PHAsset获取元数据,需要提前申请相册访问权限:
import Photos // 在加载UIImage的分支中添加如下逻辑 itemProvider.loadDataRepresentation(forTypeIdentifier: "com.apple.photos.asset.local-identifier") { data, error in guard let data = data, let localId = String(data: data, encoding: .utf8) else { return } let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localId], options: nil) if let asset = fetchResult.firstObject { print("相册图片创建日期:\(asset.creationDate ?? Date())") // 可读取PHAsset的所有元数据字段 } }
- 跨应用获取的文件URL必须调用
startAccessingSecurityScopedResource获取临时访问权限,使用完成后需要调用停止方法释放权限 - 若要访问相册元数据,需要在Info.plist中添加
NSPhotoLibraryUsageDescription权限声明 - 原判断逻辑的分支顺序是触发系统差异的核心原因,调整判断优先级即可统一多平台的处理逻辑
内容的提问来源于stack exchange,提问作者AlbertUI

