I have been using AsyncImage for some time now to render remote images in my SwiftUI app.
https://developer.apple.com/documentation/swiftui/asyncimage
I now have a requirement to render images that are protected assets - I must supply a bearer token in the header to download these images.
It is clear that AsyncImage is not up to the job here as the request is not customisable in anyway.
I have an ImageLoader protocol;
protocol ImageLoader {
func loadImage(for url: URL) async throws -> UIImage
}
Which describes a service I have written for downloading remote images.
How can I provide this service to views nested multiple levels down within my view hierarchy.
For example;
struct UserAvatarView: View {
private let name: String
private let initials: String
private let image: UIImage?
init(name: String, initials: String, image: UIImage?) {
self.name = name
self.initials = initials
self.image = image
}
var body: some View {
GeometryReader { proxy in
ZStack {
if let image = image {
Image(uiImage: image)
.resizable()
.scaledToFill()
.clipped()
.aspectRatio(contentMode: .fill)
.clipShape(Rectangle())
.frame(width: proxy.size.width - 4, height: proxy.size.width - 4)
.clipShape(Circle())
.padding(2)
} else {
Circle()
.fill(Color(AvatarColor.makeColor(forText: name)))
.padding(2)
Text(initials)
.font(Font(UIFont.preferredFont(for: .body, weight: .regular, size: proxy.size.width / 2.55)))
.foregroundColor(.white)
}
Circle()
.strokeBorder(Color.white, lineWidth: 2)
}
}
.shadowed()
}
}
This view could be nested many levels down, I have written it to be as dumb as possible, but at some point I need to fetch the image and update this view.
Should this view itself fetch the image?
I could implement a UserAvatarViewModel that accepts an ImageLoader instance - but it is not clear to me how I would create that model with the required dependency.
I am keen to avoid a singleton instance and I do not believe the view should be responsible for creating the view model, which rules out Environment for passing the loader down.
I had considered the upper most view be responsible for all the heavy lifting, but then I could be passing properties to views, only for them to pass them to their child views, which feels like an anti-pattern again.