NetworkImage

1.1.0

Simple image downloading, caching and displaying for SwiftUI
gonzalezreal/NetworkImage

What's New

NetworkImage 1.1

2020-09-25T20:40:57Z
  • Add proper support for SwiftUI: NetworkImage view and customization via NetworkImageStyle.
  • Add CI and format jobs
  • Add macOS and watchOS support.

NetworkImage

CI Twitter: @gonzalezreal

NetworkImage is a Swift ┬Ápackage that provides image downloading, caching, and displaying for your SwiftUI apps. It leverages the foundation URLCache, providing persistent and in-memory caches.

You can explore all the capabilities of this package in the companion playground.

Displaying network images

You can use a NetworkImage view to display an image from a given URL. The download happens asynchronously, and the resulting image is cached both in disk and memory.

struct ContentView: View {
    var body: some View {
        NetworkImage(url: URL(string: "https://picsum.photos/id/237/300/200"))
            .frame(width: 300, height: 200)
    }
}

By default, remote images are resizable and fill the available space while maintaining the aspect ratio.

Customizing network images

You can customize a network image's appearance by using a network image style. The default image style is an instance of ResizableNetworkImageStyle configured to fill the available space. To set a specific style for all network images within a view, you can use the networkImageStyle(_:) modifier.

struct ContentView: View {
    var body: some View {
        HStack {
            NetworkImage(url: URL(string: "https://picsum.photos/id/1025/300/200"))
                .frame(width: 200, height: 200)
            NetworkImage(url: URL(string: "https://picsum.photos/id/237/300/200"))
                .frame(width: 200, height: 200)
        }
        .networkImageStyle(
            ResizableNetworkImageStyle(
                backgroundColor: .yellow,
                contentMode: .fit
            )
        )
    }
}

Creating custom network image styles

To add a custom appearance, create a type that conforms to the NetworkImageStyle protocol. You can customize a network image's appearance in all of its different states: loading, displaying an image or failed.

struct RoundedImageStyle: NetworkImageStyle {
    var width: CGFloat?
    var height: CGFloat?

    func makeBody(state: NetworkImageState) -> some View {
        ZStack {
            Color(.secondarySystemBackground)

            switch state {
            case .loading:
                EmptyView()
            case let .image(image, _):
                image
                    .resizable()
                    .aspectRatio(contentMode: .fill)
            case .failed:
                Image(systemName: "photo")
                    .foregroundColor(Color(.systemFill))
            }
        }
        .frame(width: width, height: height)
        .clipShape(RoundedRectangle(cornerRadius: 5))
    }
}

Then set the custom style for all network images within a view, using the networkImageStyle(_:) modifier:

struct ContentView: View {
    var body: some View {
        HStack {
            NetworkImage(url: URL(string: "https://picsum.photos/id/1025/300/200"))
            NetworkImage(url: URL(string: "https://picsum.photos/id/237/300/200"))
        }
        .networkImageStyle(
            RoundedImageStyle(width: 200, height: 200)
        )
    }
}

Displaying network images in UIKit

The simplest way to display remote images in UIKit is by using NetworkImageView. You need to provide the URL where the image is located and optionally configure a placeholder image that will be displayed if the download fails or the URL is nil. When there is no cached image for the given URL, and the download takes more than a specific time, the view performs a cross-fade transition between the placeholder and the result.

class MyViewController: UIViewController {
    override func loadView() {
        let view = UIView()
        view.backgroundColor = .systemBackground

        let imageView = NetworkImageView()
        imageView.url = URL(string: "https://picsum.photos/id/237/300/200")

        imageView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(imageView)

        NSLayoutConstraint.activate([
            imageView.widthAnchor.constraint(equalToConstant: 300),
            imageView.heightAnchor.constraint(equalToConstant: 200),
            imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])

        self.view = view
    }
}

Using the shared ImageDownloader

If you need a more customized behavior, like applying image transformations or providing custom animations, you can use the shared ImageDownloader object directly.

Click to expand!
class MyViewController: UIViewController {
    private lazy var imageView = UIImageView()
    private var cancellables: Set<AnyCancellable> = []

    override func loadView() {
        let view = UIView()
        view.backgroundColor = .systemBackground

        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.backgroundColor = .secondarySystemBackground
        view.addSubview(imageView)

        NSLayoutConstraint.activate([
            imageView.widthAnchor.constraint(equalToConstant: 300),
            imageView.heightAnchor.constraint(equalToConstant: 200),
            imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])

        self.view = view
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        ImageDownloader.shared.image(for: URL(string: "https://picsum.photos/id/237/300/200")!)
            .map { image in
                // tint the image with a yellow color
                UIGraphicsImageRenderer(size: image.size).image { _ in
                    image.draw(at: .zero)
                    UIColor.systemYellow.setFill()
                    UIRectFillUsingBlendMode(CGRect(origin: .zero, size: image.size), .multiply)
                }
            }
            .replaceError(with: UIImage(systemName: "film")!)
            .receive(on: DispatchQueue.main)
            .sink(receiveValue: { [imageView] image in
                imageView.image = image
            })
            .store(in: &cancellables)
    }
}

Installation

Using the Swift Package Manager

Add NetworkImage as a dependency to your Package.swift file. For more information, see the Swift Package Manager documentation.

.package(url: "https://github.com/gonzalezreal/NetworkImage", from: "1.1.0")

Help & Feedback

  • Open an issue if you need help, if you found a bug, or if you want to discuss a feature request.
  • Open a PR if you want to make some change to NetworkImage.
  • Contact @gonzalezreal on Twitter.

Description

  • Swift Tools 5.3.0

Dependencies

Last updated: Mon Nov 23 2020 08:48:38 GMT-0500 (GMT-05:00)