SwiftUI点击手势选择错误:自定义仿Instagram图片选择器选中项不符
问题根因
- 核心原因是相册图片加载完成后
append到数组的顺序可能和PHAsset枚举顺序不一致:你在后台队列逐个发起同步图片请求,不同大小的图片加载耗时不同,通过DispatchQueue.main.async回调插入数组的顺序就会错乱,导致界面展示的图片顺序和allPhotos数组的存储顺序不匹配,点击时拿到的photo自然和你看到的图片对应不上。 - 次要问题是
LazyVGrid的视图复用未绑定唯一标识:动态更新数组时SwiftUI可能复用旧视图的手势绑定,出现点击错位。 - 额外错误:
@Environment(\.dismiss)不能放在ObservableObject子类中,该属性包装器仅支持在SwiftUI视图中使用。
修复方案
首先调整SelectNewPostScreenManager的图片加载逻辑,保证数组顺序和枚举顺序一致:
class SelectNewPostScreenManager: ObservableObject { @Published var selectedPhoto: Photo? @Published var allPhotos: [Photo] = [] init() { fetchPhotos() } private func assetsFetchOptions() -> PHFetchOptions { let fetchOptions = PHFetchOptions() let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false) fetchOptions.sortDescriptors = [sortDescriptor] return fetchOptions } func fetchPhotos() { print("Fetching Photos") let options = assetsFetchOptions() let allAssets = PHAsset.fetchAssets(with: .image, options: options) let assetCount = allAssets.count // 先创建占位数组,保证顺序固定 var tempPhotos: [Photo?] = Array(repeating: nil, count: assetCount) let imageManager = PHImageManager.default() let targetSize = CGSize(width: 250, height: 250) let requestOptions = PHImageRequestOptions() requestOptions.isSynchronous = true DispatchQueue.global(qos: .background).async { allAssets.enumerateObjects { asset, index, _ in imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: requestOptions) { image, info in guard let image = image else { return } let photo = Photo(uiImage: image) // 按索引插入到对应位置,保证顺序和枚举顺序一致 tempPhotos[index] = photo // 所有图片加载完成后再一次性更新数组 if index == assetCount - 1 { DispatchQueue.main.async { self.allPhotos = tempPhotos.compactMap { $0 } } } } } } } }
然后调整NewPostScreen的LazyVGrid部分,给每个图片视图绑定唯一ID,避免复用错位:
LazyVGrid(columns: columns, spacing: 1) { ForEach(manager.allPhotos, id: \.id) { photo in Image(uiImage: photo.uiImage) .resizable() .scaledToFill() .frame(maxWidth: UIScreen.main.bounds.width/3, minHeight: UIScreen.main.bounds.width/3, maxHeight: UIScreen.main.bounds.width/3) .clipped() .id(photo.id) // 绑定唯一标识,防止视图复用时手势错位 .onTapGesture { manager.selectedPhoto = photo } } }
可选优化
如果相册图片数量较多,一次性加载所有图片会占用过高内存,可以改为先加载低清缩略图,或者支持分页加载,同时保留索引对齐的逻辑即可。
内容的提问来源于stack exchange,提问作者Luis Ramirez
相关产品推荐
相关产品推荐

