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