Created
July 8, 2021 00:31
-
-
Save k3zi/856c86a08c5869639e90a6afd094e01e to your computer and use it in GitHub Desktop.
AsyncImage for iOS 13+
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Combine | |
import SwiftUI | |
/// A view that asynchronously loads and displays an image. | |
/// | |
/// Loading an image from a URL uses the shared URLSession. | |
struct AsyncImage<Content> : View where Content : View { | |
final class Loader: ObservableObject { | |
@Published var data: Data? = nil | |
private var cancellables = Set<AnyCancellable>() | |
init(_ url: URL?) { | |
guard let url = url else { return } | |
URLSession.shared.dataTaskPublisher(for: url) | |
.map(\.data) | |
.map { $0 as Data? } | |
.replaceError(with: nil) | |
.receive(on: RunLoop.main) | |
.assign(to: \.data, on: self) | |
.store(in: &cancellables) | |
} | |
} | |
@ObservedObject private var imageLoader: Loader | |
private let conditionalContent: ((Image?) -> Content)? | |
private let scale: CGFloat | |
/// Loads and displays an image from the given URL. | |
/// | |
/// When no image is available, standard placeholder content is shown. | |
/// | |
/// In the example below, the image from the specified URL is loaded and shown. | |
/// | |
/// AsyncImage(url: URL(string: "https://example.com/screenshot.png")) | |
/// | |
/// - Parameters: | |
/// - url: The URL for the image to be shown. | |
/// - scale: The scale to use for the image. | |
init(url: URL?, scale: CGFloat = 1) where Content == Image { | |
self.imageLoader = Loader(url) | |
self.scale = scale | |
self.conditionalContent = nil | |
} | |
/// Loads and displays an image from the given URL. | |
/// | |
/// When an image is loaded, the `image` content is shown; when no image is | |
/// available, the `placeholder` is shown. | |
/// | |
/// In the example below, the image from the specified URL is loaded and | |
/// shown as a tiled resizable image. While it is loading, a green | |
/// placeholder is shown. | |
/// | |
/// AsyncImage(url: URL(string: "https://example.com/tile.png")) { image in | |
/// image.resizable(resizingMode: .tile) | |
/// } placeholder: { | |
/// Color.green | |
/// } | |
/// | |
/// - Parameters: | |
/// - url: The URL for the image to be shown. | |
/// - scale: The scale to use for the image. | |
/// - content: The view to show when the image is loaded. | |
/// - placeholder: The view to show while the image is still loading. | |
init<I, P>(url: URL?, scale: CGFloat = 1, @ViewBuilder content: @escaping (Image) -> I, @ViewBuilder placeholder: @escaping () -> P) where Content == _ConditionalContent<I, P>, I : View, P : View { | |
self.imageLoader = Loader(url) | |
self.scale = scale | |
self.conditionalContent = { image in | |
if let image = image { | |
return ViewBuilder.buildEither(first: content(image)) | |
} else { | |
return ViewBuilder.buildEither(second: placeholder()) | |
} | |
} | |
} | |
private var image: Image? { | |
imageLoader.data | |
.flatMap { | |
UIImage(data: $0, scale: scale) | |
} | |
.flatMap(Image.init) | |
} | |
var body: some View { | |
if let conditionalContent = conditionalContent { | |
conditionalContent(image) | |
} else if let image = image { | |
image | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment