swiftswiftuikingfisher

Kingfisher updating KFImage url is not updating the image in SwiftUI


Sorry for beginner question, I am trying to transition from UIKit to SwiftUI. @State variable's didSet does not get triggered like it does in UIKit.

I have KFImage that loads an image from a user's photoUrl and I want it when tapped, launches image picker, then update the userUIImage but since KFImage needs a url to be updated, I am not sure how I can call my updateUserImage() to update the photoUrl to update KFImage.

Here' my code below

struct ProfileView: View {
    
    //MARK: Properties
    let screenWidth = UIScreen.main.bounds.width
    let screenHeight = UIScreen.main.bounds.height
    
    @State private var isImageLoaded: Bool = false
    @State private var showImagePicker = false
    @State private var photoUrl: URL? = URL(string: (Customer.current?.photoUrl)!)
    @State private var userImage: Image? = Image(uiImage: UIImage())
    @State private var userUIImage: UIImage? = UIImage() {
        didSet {
            if isImageLoaded {
                updateUserImage()
            }
        }
    }
    
    var body: some View {
        KFImage(photoUrl)
            .resizable()
            .onSuccess { result in
                self.userUIImage = result.image
                self.isImageLoaded = true
            }
            .aspectRatio(contentMode: .fill)
            .frame(width: screenWidth / 2.5)
            .clipShape(Circle())
            .overlay(Circle().stroke(Color.white, lineWidth: 4))
            .shadow(radius: 10)
            .onTapGesture { self.showImagePicker = true }
            .sheet(isPresented: $showImagePicker) {
                CustomImagePickerView(sourceType: .photoLibrary, image: $userImage, uiImage: $userUIImage, isPresented: $showImagePicker)
            }
    }
    
    //MARK: Methods
    func updateUserImage() {
        CustomerService.updateUserImage(image: userUIImage!) { (photoUrl, error) in
            if let error = error {
                handleError(errorBody: error)
                return
            }
            guard var user = Customer.current else { return }
            self.photoUrl = photoUrl
            user.photoUrl = photoUrl?.absoluteString
            CustomerService.updateUserDatabase(user: user) { (error) in
                if let error = error {
                    handleError(errorBody: error)
                    return
                }
                Customer.setCurrent(user, writeToUserDefaults: true)
            }
        }
    }
}

Solution

  • Try using onChange / onReceive instead:

    KFImage(photoUrl)
        .resizable()
        // ...
        // remove .onSuccess implementation as self.userUIImage = result.image will result in an infinite loop
        .onChange(of: userUIImage) { newImage in
            updateUserImage()
        }
    

    or

    import Combine
    ...
    KFImage(photoUrl)
        .resizable()
        // ...
        .onReceive(Just(userUIImage)) { newImage in
            updateUserImage()
        }