Search code examples
imageswiftuistorageprofile

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


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()

Solution

  • 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!