How can I set the image Loader correct (SwiftUI)?

78 views Asked by At

I'm currently working on a ProfilImagePicker and it all works, but I've also built in a storage system and that works too. But my problem is, when I select a photo it is not set immediately, but only when I reload the page completely, or when I insert a new image, then only the first one is inserted.

I hope someone can help me.

Storage system

class AppStorageHelperProfilImage: ObservableObject {
    @Published var selectedImage: UIImage?

    static func saveProfileImage(_ image: UIImage) {
        if let data = image.jpegData(compressionQuality: 1.0) {
            do {
                try data.write(to: profileImagePath)
            } catch {
                print("Fehler beim Speichern des Profilbilds: \(error.localizedDescription)")
            }
        }
    }

    static func loadProfileImage() -> UIImage? {
        if let data = try? Data(contentsOf: profileImagePath) {
            return UIImage(data: data)
        }
        return nil
    }

    static func deleteProfileImage() {
        do {
            try FileManager.default.removeItem(at: profileImagePath)
        } catch {
            print("Mistake by saving the picture \(error.localizedDescription)")
        }
    }

    private static var profileImagePath: URL {
        FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("profileImage.jpg")
    }
}

My Variables


@StateObject private var appStorageHelper = AppStorageHelperProfilImage()

@State private var selectedImage: UIImage?
   private var profileImage: UIImage? {
       AppStorageHelperProfilImage.loadProfileImage()
    }
@State private var isProfileImagePickerPresented = false

The Image


Image(uiImage: profileImage ?? {
                            let symbolImage = UIImage(systemName: "person.circle")?.withRenderingMode(.alwaysOriginal)
                            let imageRenderer = UIGraphicsImageRenderer(size: CGSize(width: 120, height: 120))

                            return imageRenderer.image { _ in
                                symbolImage?.draw(in: CGRect(x: 0, y: 0, width: 120, height: 120))
                            }
                        }())
                        .resizable()
                        .scaledToFill()
                        .frame(width: 120, height: 120)
                        .clipShape(Circle())
                        .onTapGesture {
                            isProfileImagePickerPresented.toggle()
                        }
                        .id(appStorageHelper.selectedImage)
                        .sheet(isPresented: $isProfileImagePickerPresented) {
                            ProfileImagePicker(selectedImage: $appStorageHelper.selectedImage, isImagePickerPresented: $isProfileImagePickerPresented)
                                .onDisappear {
                                    // Aktualisieren Sie die Ansicht, wenn das Bild ausgewählt wurde
                                    appStorageHelper.selectedImage.map { saveProfileImage($0) }
                                }
                        }
                        .padding()

Profil Image Picker

struct ProfileImagePicker: UIViewControllerRepresentable {
    @Binding var selectedImage: UIImage?
    @Binding var isImagePickerPresented: Bool

    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        var parent: ProfileImagePicker

        init(parent: ProfileImagePicker) {
            self.parent = parent
        }

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
            if let selectedImage = info[.originalImage] as? UIImage {
                parent.selectedImage = selectedImage
            }
            parent.isImagePickerPresented = false
        }

        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            parent.isImagePickerPresented = false
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(parent: self)
    }

    func makeUIViewController(context: UIViewControllerRepresentableContext<ProfileImagePicker>) -> UIImagePickerController {
        let imagePicker = UIImagePickerController()
        imagePicker.delegate = context.coordinator
        return imagePicker
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ProfileImagePicker>) {
        // Update UI if needed
    }
}

Mini Version

Image(uiImage: (selectedImage ?? UIImage(systemName: "person.circle"))!)
                                .resizable()
                                .scaledToFill()
                                .frame(width: 50, height: 50)
                                .clipShape(Circle())
                                .id(appStorageHelper.selectedImage)
                                .onAppear {
                                    if selectedImage == nil {
                                        if let loadedImage = AppStorageHelperProfilImage.loadProfileImage() {
                                            selectedImage = loadedImage
                                        }
                                    }
                                }
                                .padding()
1

There are 1 answers

6
MatBuompy On BEST ANSWER

The quick solution would be to set this line like this:

Image(uiImage: appStorageHelper.selectedImage ?? // Rest of the code as it was here.

The issue is that the profileImage does not redraws the UI since it cannot be a State varibale due to the fact it is a computed property. The other solution would simply be to update the selectedImage variable once you select the new image. For that I've added a callback to the ProfileImagePicker struct like so:

struct ProfileImagePicker: UIViewControllerRepresentable {
    @Binding var selectedImage: UIImage?
    @Binding var isImagePickerPresented: Bool
    
    /// Add this callback
    var onImageChosen: (UIImage?) -> Void
    
    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        var parent: ProfileImagePicker
        
        init(parent: ProfileImagePicker) {
            self.parent = parent
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
            if let selectedImage = info[.originalImage] as? UIImage {
                parent.selectedImage = selectedImage
                /// Call the callback here and pass it the new chosen image
                parent.onImageChosen(selectedImage)
            }
            parent.isImagePickerPresented = false
        }
        
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            parent.isImagePickerPresented = false
        }
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(parent: self)
    }
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<ProfileImagePicker>) -> UIImagePickerController {
        let imagePicker = UIImagePickerController()
        imagePicker.delegate = context.coordinator
        return imagePicker
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ProfileImagePicker>) {
        // Update UI if needed
    }
}

And then update the rest of your code like this:

@StateObject private var appStorageHelper = AppStorageHelperProfilImage()

@State private var selectedImage: UIImage?
private var profileImage: UIImage? {
    AppStorageHelperProfilImage.loadProfileImage()
}
@State private var isProfileImagePickerPresented = false

var body: some View {
    VStack {
        Image(uiImage: selectedImage ?? {
            let symbolImage = UIImage(systemName: "person.circle")?.withRenderingMode(.alwaysOriginal)
            let imageRenderer = UIGraphicsImageRenderer(size: CGSize(width: 120, height: 120))
            
            return imageRenderer.image { _ in
                symbolImage?.draw(in: CGRect(x: 0, y: 0, width: 120, height: 120))
            }
        }())
        .resizable()
        .scaledToFill()
        .frame(width: 120, height: 120)
        .clipShape(Circle())
        .onTapGesture {
            isProfileImagePickerPresented.toggle()
        }
        .id(appStorageHelper.selectedImage)
        .sheet(isPresented: $isProfileImagePickerPresented) {
            ProfileImagePicker(selectedImage: $appStorageHelper.selectedImage, isImagePickerPresented: $isProfileImagePickerPresented) { image in
                withAnimation {
                    selectedImage = image
                }
            }
            .onDisappear {
                // Aktualisieren Sie die Ansicht, wenn das Bild ausgewählt wurde
                //appStorageHelper.selectedImage.map { saveProfileImage($0) }
            }
        }
        .padding()
    }
}

Let me know if this works for you!