Search code examples
swiftswiftui

How to display Image from a URL in SwiftUI


So I'm trying to create a content feed using data fetched from my Node.js server.

Here I fetch data from my API:

class Webservice {
    func getAllPosts(completion: @escaping ([Post]) -> ()) {
        guard let url = URL(string: "http://localhost:8000/albums")
     else {
     fatalError("URL is not correct!")
    }
        URLSession.shared.dataTask(with: url) { data, _, _ in
            let posts = try!
                JSONDecoder().decode([Post].self, from: data!); DispatchQueue.main.async {
                    completion(posts)
            }
        }.resume()
    }
}

Set the variables to the data fetched from the API:

final class PostListViewModel: ObservableObject {
    init() {
        fetchPosts()
    }
    
    @Published var posts = [Post]()
    
    private func fetchPosts() {
        Webservice().getAllPosts {
            self.posts = $0
        }
    } 
}

Post model:

struct Post: Codable, Hashable, Identifiable {
    let id: String
    let title: String
    let path: String
    let description: String
}

SwiftUI:

struct ContentView: View {
    @ObservedObject var model = PostListViewModel()
    
    var body: some View {
        List(model.posts) { post in
            HStack {
                Text(post.title)
                Image("http://localhost:8000/" + post.path)
                Text(post.description)
            }
        }
    }
}

The Text from post.title and post.description are displayed correctly but nothing displays from Image. How can I use a URL from my server to display with my image?


Solution

  • iOS 15 update:

    you can use asyncImage in this way:
    AsyncImage(url: URL(string: "https://your_image_url_address"))
    

    more info on Apple developers document: AsyncImage

    Using ObservableObject (Before iOS 15)

    first you need to fetch image from url :

    class ImageLoader: ObservableObject {
        var didChange = PassthroughSubject<Data, Never>()
        var data = Data() {
            didSet {
                didChange.send(data)
            }
        }
    
        init(urlString:String) {
            guard let url = URL(string: urlString) else { return }
            let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
                guard let data = data, self != nil else { return }
                DispatchQueue.main.async { [weak self]
                    self?.data = data
                }
            }
            task.resume()
        }
    }
    

    you can put this as a part of your Webservice class function too.

    then in your ContentView struct you can set @State image in this way :

    struct ImageView: View {
        @ObservedObject var imageLoader:ImageLoader
        @State var image:UIImage = UIImage()
    
        init(withURL url:String) {
            imageLoader = ImageLoader(urlString:url)
        }
    
        var body: some View {
            
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width:100, height:100)
                    .onReceive(imageLoader.didChange) { data in
                    self.image = UIImage(data: data) ?? UIImage()
            }
        }
    }
    

    Also, this tutorial is a good reference if you need more