Search code examples
viewswiftuicombine

Observe property change in custom view SwiftUI


I created a custom view, NetworkImage, to load image asynchronously.

import Combine
import SwiftUI

struct NetworkImage<Placeholder: View>: View {
    @StateObject private var viewModel = NetworkImageViewModel()
    let url: String?
    let placeholder: Placeholder
    let saveLocally:Bool
    
    init(_ url: String?, @ViewBuilder placeholder: () -> Placeholder, saveLocally:Bool = false) {
        self.url = url
        self.placeholder = placeholder()
        self.saveLocally = saveLocally
    }
    
    func loadImage(_ url:String?)  {
        if let url = url {
            viewModel.loadImage(from: url, saveLocally: saveLocally)
        }
    }
    
    var body: some View {
        Group {
            if let uiImage = viewModel.image {
                Image(uiImage: uiImage)
                    .resizable()
            } else {
                placeholder
            }
        }
        .onAppear {
            if let url = url {
                viewModel.loadImage(from: url, saveLocally: saveLocally)
            }
        }
    }
}

I use this in my views and it works really well. But now I would like to be able to replace the image with a new one each time the url change. How can I do that? How can I pass a @Published property of my view's viewmodel to this NetworkImage view and make it work?

Thanks


Solution

  • Use the onChange modifier to see if the url parameter has changed:

    struct NetworkImage<Placeholder: View>: View {
        @StateObject private var viewModel = NetworkImageViewModel()
        let url: String?
        let placeholder: Placeholder
        let saveLocally:Bool = false
        
        func loadImage(_ url:String?)  {
            if let url = url {
                viewModel.loadImage(from: url, saveLocally: saveLocally)
            }
        }
        
        var body: some View {
            Group {
                if let uiImage = viewModel.image {
                    Image(uiImage: uiImage)
                        .resizable()
                } else {
                    placeholder
                }
            }
            .onAppear {
                self.loadImage(url)
            }
            .onChange(of: url) { url in
                self.loadImage(url)
            }
        }
    }