Reputation: 1634
So I'm trying to create a custom image picker something like instagram but way more basic. This is how I created the screen using this.
struct NewPostScreen: View {
@StateObject var manager = SelectNewPostScreenManager()
let columns = [GridItem(.flexible(), spacing: 1), GridItem(.flexible(), spacing: 1), GridItem(.flexible(), spacing: 1)]
var body: some View {
ScrollView {
VStack(spacing: 1) {
Image(uiImage: manager.selectedPhoto?.uiImage ?? UIImage(named: "placeholder-image")!)
.resizable()
.scaledToFit()
.frame(width: 350, height: 350)
.id(1)
LazyVGrid(columns: columns, spacing: 1) {
ForEach(manager.allPhotos) { 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()
.onTapGesture {
manager.selectedPhoto = photo
}
}
}
}
}
}
}
The UI looks good and everything but sometimes when I click an image using the tapGesture it gives me an incorrect selectedPhoto for my manager. Here is how my manager looks and how I fetch the photos from the library.
class SelectNewPostScreenManager: ObservableObject {
@Environment(\.dismiss) var dismiss
@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)
DispatchQueue.global(qos: . background).async {
allAssets.enumerateObjects { asset, count, _ in
let imageManager = PHImageManager.default()
let targetSize = CGSize(width: 250, height: 250)
let options = PHImageRequestOptions()
options.isSynchronous = true
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { image, info in
guard let image = image else { return }
let photo = Photo(uiImage: image)
DispatchQueue.main.async {
self.allPhotos.append(photo)
}
}
}
}
}
}
This is how my photo object looks like as well.
struct Photo: Identifiable {
let id = UUID()
let uiImage: UIImage
}
I have no clue to why the tap gesture is not selecting the right item. Ive spent a couple of hours trying to figure out to why this is happening. I might just end up using the UIImagePickerController instead lol.
Anyways if someone can copy and paste this code into a new project of Xcode and run it on your actual device instead of the simulator. Let me know if its happening to you as well.
I was running it on an iPhone X.
Upvotes: 10
Views: 1525
Reputation: 1342
The problem is that the image gesture are extending beyond your defined frame, I am sure there are many ways to fix this, but I solved it by adding the contentShape modifier
Please replace your image code with the following
Image(uiImage: photo.uiImage)
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width/3, height: UIScreen.main.bounds.width/3)
.clipped()
.contentShape(Path(CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width/3, height: UIScreen.main.bounds.width/3)))
.onTapGesture {
manager.selectedPhoto = photo
}
contentShape define the hit area for the gesture
Upvotes: 22