How to display Image from a url in SwiftUI

SwiftSwiftui

Swift Problem Overview


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
        }
    }
    
    
}
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 display correctly but nothing displays from Image(). How can I use a URL from my server to display with my image?

Swift Solutions


Solution 1 - Swift

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) { data, response, error in
            guard let data = data else { return }
            DispatchQueue.main.async {
                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

Solution 2 - Swift

Try with this implementation:

    AsyncImage(url: URL(string: "http://mydomain/image.png")!, 
               placeholder: { Text("Loading ...") },
               image: { Image(uiImage: $0).resizable() })
       .frame(idealHeight: UIScreen.main.bounds.width / 2 * 3) // 2:3 aspect ratio

Looks simple, right? This function has the ability to save in cache the images, and also to make an async image request.

Now, copy this in a new file:

import Foundation
import SwiftUI
import UIKit
import Combine

struct AsyncImage<Placeholder: View>: View {
    @StateObject private var loader: ImageLoader
    private let placeholder: Placeholder
    private let image: (UIImage) -> Image
    
    init(
        url: URL,
        @ViewBuilder placeholder: () -> Placeholder,
        @ViewBuilder image: @escaping (UIImage) -> Image = Image.init(uiImage:)
    ) {
        self.placeholder = placeholder()
        self.image = image
        _loader = StateObject(wrappedValue: ImageLoader(url: url, cache: Environment(\.imageCache).wrappedValue))
    }
    
    var body: some View {
        content
            .onAppear(perform: loader.load)
    }
    
    private var content: some View {
        Group {
            if loader.image != nil {
                image(loader.image!)
            } else {
                placeholder
            }
        }
    }
}

protocol ImageCache {
    subscript(_ url: URL) -> UIImage? { get set }
}

struct TemporaryImageCache: ImageCache {
    private let cache = NSCache<NSURL, UIImage>()
    
    subscript(_ key: URL) -> UIImage? {
        get { cache.object(forKey: key as NSURL) }
        set { newValue == nil ? cache.removeObject(forKey: key as NSURL) : cache.setObject(newValue!, forKey: key as NSURL) }
    }
}

class ImageLoader: ObservableObject {
    @Published var image: UIImage?
    
    private(set) var isLoading = false
    
    private let url: URL
    private var cache: ImageCache?
    private var cancellable: AnyCancellable?
    
    private static let imageProcessingQueue = DispatchQueue(label: "image-processing")
    
    init(url: URL, cache: ImageCache? = nil) {
        self.url = url
        self.cache = cache
    }
    
    deinit {
        cancel()
    }
    
    func load() {
        guard !isLoading else { return }

        if let image = cache?[url] {
            self.image = image
            return
        }
        
        cancellable = URLSession.shared.dataTaskPublisher(for: url)
            .map { UIImage(data: $0.data) }
            .replaceError(with: nil)
            .handleEvents(receiveSubscription: { [weak self] _ in self?.onStart() },
                          receiveOutput: { [weak self] in self?.cache($0) },
                          receiveCompletion: { [weak self] _ in self?.onFinish() },
                          receiveCancel: { [weak self] in self?.onFinish() })
            .subscribe(on: Self.imageProcessingQueue)
            .receive(on: DispatchQueue.main)
            .sink { [weak self] in self?.image = $0 }
    }
    
    func cancel() {
        cancellable?.cancel()
    }
    
    private func onStart() {
        isLoading = true
    }
    
    private func onFinish() {
        isLoading = false
    }
    
    private func cache(_ image: UIImage?) {
        image.map { cache?[url] = $0 }
    }
}

struct ImageCacheKey: EnvironmentKey {
    static let defaultValue: ImageCache = TemporaryImageCache()
}

extension EnvironmentValues {
    var imageCache: ImageCache {
        get { self[ImageCacheKey.self] }
        set { self[ImageCacheKey.self] = newValue }
    }
}

Done!

Original source code: https://github.com/V8tr/AsyncImage

Solution 3 - Swift

Combining @naishta (iOS 13+) and @mrmins (placeholder & configure) answers, plus exposing Image (instead UIImage) to allow configuring it (resize, clip, etc)

Usage Example:

var body: some View {

  RemoteImageView(
    url: someUrl,
    placeholder: { 
      Image("placeholder").frame(width: 40) // etc.
    },
    image: { 
      $0.scaledToFit().clipShape(Circle()) // etc.
    }
  )

}
struct RemoteImageView<Placeholder: View, ConfiguredImage: View>: View {
    var url: URL
    private let placeholder: () -> Placeholder
    private let image: (Image) -> ConfiguredImage

    @ObservedObject var imageLoader: ImageLoaderService
    @State var imageData: UIImage?

    init(
        url: URL,
        @ViewBuilder placeholder: @escaping () -> Placeholder,
        @ViewBuilder image: @escaping (Image) -> ConfiguredImage
    ) {
        self.url = url
        self.placeholder = placeholder
        self.image = image
        self.imageLoader = ImageLoaderService(url: url)
    }

    @ViewBuilder private var imageContent: some View {
        if let data = imageData {
            image(Image(uiImage: data))
        } else {
            placeholder()
        }
    }

    var body: some View {
        imageContent
            .onReceive(imageLoader.$image) { imageData in
                self.imageData = imageData
            }
    }
}

class ImageLoaderService: ObservableObject {
    @Published var image = UIImage()

    convenience init(url: URL) {
        self.init()
        loadImage(for: url)
    }

    func loadImage(for url: URL) {
        let task = URLSession.shared.dataTask(with: url) { data, _, _ in
            guard let data = data else { return }
            DispatchQueue.main.async {
                self.image = UIImage(data: data) ?? UIImage()
            }
        }
        task.resume()
    }
}

Solution 4 - Swift

For iOS 13, 14 (before AsyncImage) and with the latest property wrappers ( without having to use PassthroughSubject<Data, Never>()

Main View

import Foundation
import SwiftUI
import Combine

struct TransactionCardRow: View {
    var transaction: Transaction

    var body: some View {
        CustomImageView(urlString: "https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.png") // This is where you extract urlString from Model ( transaction.imageUrl)
    }
}

Creating CustomImageView

struct CustomImageView: View {
    var urlString: String
    @ObservedObject var imageLoader = ImageLoaderService()
    @State var image: UIImage = UIImage()
    
    var body: some View {
        Image(uiImage: image)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width:100, height:100)
            .onReceive(imageLoader.$image) { image in
                self.image = image
            }
            .onAppear {
                imageLoader.loadImage(for: urlString)
            }
    }
}

Creating a service layer to download the Images from url string, using a Publisher

class ImageLoaderService: ObservableObject {
    @Published var image: UIImage = UIImage()
    
    func loadImage(for urlString: String) {
        guard let url = URL(string: urlString) else { return }
        
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data else { return }
            DispatchQueue.main.async {
                self.image = UIImage(data: data) ?? UIImage()
            }
        }
        task.resume()
    }
    
}

Solution 5 - Swift

New in iOS 15 , SwiftUI has a dedicated AsyncImage for downloading and displaying remote images from the internet. In its simplest form you can just pass a URL, like this:

AsyncImage(url: URL(string: "https://www.thiscoolsite.com/img/nice.png"))

Solution 6 - Swift

AsyncImage with animation transactions, placeholders, and network phase states in iOS 15+!

As other answers have covered, AsyncImage is the recommended way to achieve this in SwiftUI but the new View is much more capable than the standard config shown here:

AsyncImage(url: URL(string: "https://your_image_url_address"))

AsyncImage downloads images from URLs without URLSessions boilerplate. However, rather than simply downloading the image and displaying nothing while loading, Apple recommends using placeholders while waiting for the best UX. Oh, we can also display custom views for error states, and add animations to further improve phase transitions. :D

Animations

We can add animations using transaction: and change the underlying Image properties between states. Placeholders can have a different aspect mode, image, or have different modifiers. e.g. .resizable.

Here's an example of that:

AsyncImage(
  url: "https://dogecoin.com/assets/img/doge.png",
  transaction: .init(animation: .easeInOut),
  content: { image in
  image
    .resizable()
    .aspectRatio(contentMode: .fit)
}, placeholder: {
  Color.gray
})
  .frame(width: 500, height: 500)
  .mask(RoundedRectangle(cornerRadius: 16)

Handling Network Result State

To display different views when a request fails, succeeds, is unknown, or is in progress, we can use a phase handler. This updates the view dynamically, similar to a URLSessionDelegate handler. Animations are applied automatically between states using SwiftUI syntax in a param.

AsyncImage(url: url, transaction: .init(animation: .spring())) { phase in
  switch phase {
  case .empty:
    randomPlaceholderColor()
      .opacity(0.2)
      .transition(.opacity.combined(with: .scale))
  case .success(let image):
    image
      .resizable()
      .aspectRatio(contentMode: .fill)
      .transition(.opacity.combined(with: .scale))
  case .failure(let error):
    ErrorView(error)
  @unknown default:
    ErrorView()
  }
}
.frame(width: 400, height: 266)
.mask(RoundedRectangle(cornerRadius: 16))

NOTE

We shouldn't use AsyncImage for all instances where we need to load an image from a URL. Instead, when images need to be downloaded on request, it's better to use the .refreshable or .task modifiers. Only use AsyncImage sparingly because the image will be re-downloaded for every View state change (streamline requests). Here, Apple suggests await to prevent blocking the main thread 0 (Swift 5.5+).

Solution 7 - Swift

You can use KingFisher and SDWebImage

  1. KingFisher https://github.com/onevcat/Kingfisher

     var body: some View {
         KFImage(URL(string: "https://example.com/image.png")!)
     }
    
  2. SDWebImage https://github.com/SDWebImage/SDWebImageSwiftUI

     WebImage(url: url)
    

Solution 8 - Swift

            Button(action: {
                    self.onClickImage()
                }, label: {
                    CustomNetworkImageView(urlString: self.checkLocalization())
                })
                
                Spacer()
            }
            
            if self.isVisionCountryPicker {
                if #available(iOS 14.0, *) {
                    Picker(selection: $selection, label: EmptyView()) {
                        ForEach(0 ..< self.countries.count) {
                            Text(self.countries[$0].name?[self.language] ?? "N/A").tag($0)
                        }
                    }
                    .labelsHidden()
                    .onChange(of: selection) { tag in self.countryChange(tag) }
                } else {
                    Picker(selection: $selection.onChange(countryChange), label: EmptyView()) {
                        ForEach(0 ..< self.countries.count) {
                            Text(self.countries[$0].name?[self.language] ?? "N/A").tag($0)
                        }
                    }
                    .labelsHidden()
                }
            }

fileprivate struct CustomNetworkImageView: View { var urlString: String @ObservedObject var imageLoader = ImageLoaderService() @State var image: UIImage = UIImage()

var body: some View {
    Group {
        if image.pngData() == nil {
            if #available(iOS 14.0, *) {
                ProgressView()
                    .frame(height: 120.0)
                    .onReceive(imageLoader.$image) { image in
                        self.image = image
                        self.image = image
                        if imageLoader.image == image {
                            imageLoader.loadImage(for: urlString)
                        }
                    }
                    .onAppear {
                        imageLoader.loadImage(for: urlString)
                    }
            } else {
                EmptyView()
                    .frame(height: 120.0)
                    .onReceive(imageLoader.$image) { image in
                        self.image = image
                        self.image = image
                        if imageLoader.image == image {
                            imageLoader.loadImage(for: urlString)
                        }
                    }
                    .onAppear {
                        imageLoader.loadImage(for: urlString)
                    }
            }
        } else {
            Image(uiImage: image)
                .resizable()
                .cornerRadius(15)
                .scaledToFit()
                .frame(width: 150.0)
                .onReceive(imageLoader.$image) { image in
                    self.image = image
                    self.image = image
                    if imageLoader.image == image {
                        imageLoader.loadImage(for: urlString)
                    }
                }
                .onAppear {
                    imageLoader.loadImage(for: urlString)
                }
        }
    }
}

}

fileprivate class ImageLoaderService: ObservableObject { @Published var image: UIImage = UIImage()

func loadImage(for urlString: String) {
    guard let url = URL(string: urlString) else { return }
    
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data else { return }
        DispatchQueue.main.async {
            self.image = UIImage(data: data) ?? UIImage()
        }
    }
    task.resume()
}

}

Solution 9 - Swift

You also can try my way. This is the documentation link

> https://sdwebimage.github.io/SDWebImageSwiftUI/

Here is my code Snippet

struct SettingsProfileImageSectionView: View {
            var body: some View {
                ZStack(alignment: .leading) {
                    Color(hex: "fcfcfc")
                    HStack(spacing: 20) {
                        Spacer()
                            .frame(width: 4)
                        CustomImageView(imageManager: ImageManager(url: URL(string: imageURL)))         }
                }
                .frame(height: 104)
            }
        }

Load image from URL

struct CustomImageView: View {
        @State private var myImage: UIImage = UIImage(named: "Icon/User")!
        @ObservedObject var imageManager: ImageManager
        var body: some View {
            Image(uiImage: myImage)
                .resizable()
                .frame(width: 56.0, height: 56.0)
                .background(Color.gray)
                .scaledToFit()
                .clipShape(Circle())
                .onReceive(imageManager.$image) { image in
                    if imageManager.image != nil {
                        myImage = imageManager.image!
                    }
                }
                .onAppear {self.imageManager.load()}
                .onDisappear { self.imageManager.cancel() }
        }
    }

Solution 10 - Swift

Example for iOS 15+ with loader :

AsyncImage(
    url: URL(string: "https://XXX"),
    content: { image in
        image.resizable()
            .aspectRatio(contentMode: .fit)
            .frame(maxWidth: 200, maxHeight: 100)
    },
    placeholder: {
        ProgressView()
    }
)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDDavis25View Question on Stackoverflow
Solution 1 - SwiftMac3nView Answer on Stackoverflow
Solution 2 - SwiftBenjamin RDView Answer on Stackoverflow
Solution 3 - SwiftAviel GrossView Answer on Stackoverflow
Solution 4 - SwiftNaishtaView Answer on Stackoverflow
Solution 5 - SwiftSh_KhanView Answer on Stackoverflow
Solution 6 - SwiftPranav KasettiView Answer on Stackoverflow
Solution 7 - SwiftLi JinView Answer on Stackoverflow
Solution 8 - SwiftsikiView Answer on Stackoverflow
Solution 9 - SwiftMd. ShofiullaView Answer on Stackoverflow
Solution 10 - SwiftMedhiView Answer on Stackoverflow