I'm practicing swiftUI by building a small camera album app that accesses the photo album to allow the user to select and load a photo in the app. Then, the uploaded photos are displayed so the user selects a photo as the primary photo. The problem is with the .onLongPressGesture()
modifier, an action sheet is shown when the user long presses a photo to ask the user to update the selected or delete it. The deletion works fine. However, if the user selects "update photo" then photo album is displayed to select a new photo, and when a photo is selected it is not shown and the old photo is still displayed. When the same photo is long pressed again the new photo is finally shown and the action sheet is shown again.
I need the photo to be updated once the user selects a new photo.
Please, any suggestions to solve this issue? The code is shown below, thanks:
class EnvImage: ObservableObject {
@Published var id: UUID
@Published var imagesArray: [UserImages]
init() {
id = UUID()
imagesArray = []
self.objectWillChange.send()
}
}
class UserImages: Identifiable {
let id: UUID
var image: Image
var isSelected: Bool
var isLongPressed: Bool
init(image: Image) {
id = UUID()
self.image = image
isSelected = false
isLongPressed = false
}
}
Usage:
struct ContentView: View {
@StateObject var envImage = EnvImage()
@State private var photo: UIImage = UIImage(systemName: "person.circle")!
@State private var isSelectingPhoto = false
@State private var isShowingActionSheet = false
@State private var isPlusPressed = false
@State private var selectedCardImage: UserImages?
var imageSize: CGFloat = 150
var body: some View {
VStack {
if let selectedCardImage {
selectedCardImage.image
.resizable()
.scaledToFit()
.frame(width: imageSize, height: imageSize)
.cornerRadius(20)
.padding()
} else {
Image(systemName: "person.circle")
.resizable()
.scaledToFit()
.frame(width: imageSize, height: imageSize)
.cornerRadius(20)
.padding()
}
HStack {
ScrollView(.horizontal) {
HStack {
ForEach(envImage.imagesArray, id: \.id) { image in
image.image
.resizable()
.scaledToFit()
.frame(width: imageSize, height: imageSize)
.cornerRadius(20)
.onTapGesture() { // works fine
onTapGesture(image: image) // works fine
}
.onLongPressGesture(minimumDuration: 1) { // issue: must be longe pressed twice to see photo gets updated
onLongGesture(image: image)
}
.padding(10)
}
}
.padding()
}
Spacer()
if envImage.imagesArray.count < 3 {
Button {
isSelectingPhoto = true
isPlusPressed = true
} label: {
Image(systemName: "plus.circle")
.font(.system(size: 40))
}
}
}
}
.sheet(isPresented: $isSelectingPhoto) {
PhotoPicker(vCardImage: $photo)
}
.actionSheet(isPresented: $isShowingActionSheet) {
ActionSheet(title: Text("what would you like to do?"), buttons: [
.default(Text("update photo"), action: {
isSelectingPhoto = true
}),
.destructive(Text("delete photo"), action: {
for (index,image) in envImage.imagesArray.enumerated() where image.isLongPressed == true {
deleteImage(index: index, imageToDelete: image)
}
}),
.cancel()
])
}
.onChange(of: photo) { _ in
if isPlusPressed {
envImage.imagesArray.append(UserImages(image: Image(uiImage: photo)))
isPlusPressed = false
} else {
for image in envImage.imagesArray where image.isLongPressed == true {
if let tempSelectedCard = selectedCardImage, tempSelectedCard.id == image.id {
image.image = Image(uiImage: photo)
selectedCardImage = image
} else {
image.image = Image(uiImage: photo)
}
image.isLongPressed = false
break
}
}
}
.padding()
.preferredColorScheme(.light)
}
func onTapGesture(image: UserImages) {
selectedCardImage = image
image.isSelected = true
for others in envImage.imagesArray where others.id != image.id {
others.isSelected = false
}
}
func onLongGesture(image: UserImages) {
image.isLongPressed = true
for others in envImage.imagesArray where others.id != image.id {
others.isLongPressed = false
}
isShowingActionSheet = true
}
}
func deleteImage(index: Int, imageToDelete: UserImages) {
if let tempSelectedCardImage = selectedCardImage, tempSelectedCardImage.id == imageToDelete.id {
print("same image to delete")
envImage.imagesArray.remove(at: index)
selectedCardImage = nil
} else if let selectedCardImage, selectedCardImage.id != imageToDelete.id {
print("different image to delete, and selectedCardImage not empty")
envImage.imagesArray.remove(at: index)
} else if selectedCardImage == nil {
print("selectedCardImage is empty")
envImage.imagesArray.remove(at: index)
}
}
Thanks!
Edit:
I'm editing the question because I found a temporary solution.
The .onLongPressGesture
works as expected only if the value of the .image
is assigned to an @State Image
variable. As a result, I declared @State private var otherImage = Image(systemName: "person.circle")
, and changed the the image using the code inside the for-loop block of the .onChange(of: photo)
function as shown below:
.onChange(of: photo) { _ in
if isPlusPressed {
envImage.imagesArray.append(UserImages(image: Image(uiImage: photo)))
isPlusPressed = false
}
else {
for image in envImage.imagesArray where image.isLongPressed == true {
if image.isSelected {
print("image is selected")
image.image = Image(uiImage: photo)
selectedCardImage = image
otherImage = image.image
} else {
image.image = Image(uiImage: photo)
print("other image is selected")
otherImage = image.image
}
break
}
}
}
.padding()
.preferredColorScheme(.light)
Then, inside the body
I gave the otherImage
the following modifiers:
otherImage
.resizable()
.scaledToFit()
.frame(width: 1, height: 1)
.hidden()
It is not ideal solution and not sure why using EnvImage
and UserImages
do not work as expected!! Any ideas please?