如何在受 header-based 身份验证保护的 SwiftUI 中显示图像
How to display an image in SwiftUI protected by header-based authentication
我使用 AsyncImage
在我的 SwiftUI 应用程序中渲染远程图像已有一段时间了。
https://developer.apple.com/documentation/swiftui/asyncimage
我现在需要呈现受保护资产的图像 - 我必须在 header 中提供不记名令牌才能下载这些图像。
很明显 AsyncImage
不能胜任这里的工作,因为无论如何都无法自定义请求。
我有一个 ImageLoader
协议;
protocol ImageLoader {
func loadImage(for url: URL) async throws -> UIImage
}
其中描述了我编写的用于下载远程图像的服务。
如何为视图层次结构中向下嵌套的多个级别的视图提供此服务。
例如;
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()
}
}
这个视图可以向下嵌套很多层,我已经把它写得尽可能笨了,但在某些时候我需要获取图像并更新这个视图。
这个视图本身应该获取图像吗?
我可以实现接受 ImageLoader
实例的 UserAvatarViewModel
- 但我不清楚如何创建具有所需依赖项的模型。
我很想避免单例实例,我不认为视图应该负责创建视图模型,这排除了 Environment
将加载程序向下传递的可能性。
我曾认为最上面的视图负责所有繁重的工作,但后来我可以将属性传递给视图,只是让它们将它们传递给它们的 child 视图,这感觉就像 anti-pattern 再次.
您只需将AsyncImage 组件替换为您自己的组件即可。我创建了这个组件,类似于 AsyncImage 但它接收自定义异步函数来获取图像
struct CustomAsyncImage<Content: View, Placeholder: View>: View {
@State var uiImage: UIImage?
let getImage: () async -> UIImage?
let content: (Image) -> Content
let placeholder: () -> Placeholder
init(
getImage: @escaping () async -> UIImage?,
@ViewBuilder content: @escaping (Image) -> Content,
@ViewBuilder placeholder: @escaping () -> Placeholder
){
self.getImage = getImage
self.content = content
self.placeholder = placeholder
}
var body: some View {
if let uiImage = uiImage {
content(Image(uiImage: uiImage))
}else {
placeholder()
.task {
self.uiImage = await getImage()
}
}
}
}
这是一个使用示例。
CustomAsyncImage(getImage: getImage) { image in
image
.resizable()
.scaledToFit()
.frame(maxWidth: .infinity)
} placeholder: {
Text("PlaceHolder")
}
其中 getImage 是 return UIImage 的异步函数,您可以根据需要创建该函数。
我将向您展示我的功能作为示例。
func getImage() async -> UIImage? {
guard let url = URL(string: BASE_URL + "cards/5fd36d31937b6b32d47ad9db/attachments/5fd36d31937b6b32d47ad9de/download") else {
fatalError("Missing URL")
}
var request = URLRequest(url: url)
request.addValue("OAuth oauth_consumer_key=\"\(API_KEY)\", oauth_token=\"\(ACCOUNT_TOKEN)\"", forHTTPHeaderField: "Authorization")
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { fatalError("Error while fetching attchment") }
return UIImage(data: data)
} catch {
return nil
}
}
我使用 AsyncImage
在我的 SwiftUI 应用程序中渲染远程图像已有一段时间了。
https://developer.apple.com/documentation/swiftui/asyncimage
我现在需要呈现受保护资产的图像 - 我必须在 header 中提供不记名令牌才能下载这些图像。
很明显 AsyncImage
不能胜任这里的工作,因为无论如何都无法自定义请求。
我有一个 ImageLoader
协议;
protocol ImageLoader {
func loadImage(for url: URL) async throws -> UIImage
}
其中描述了我编写的用于下载远程图像的服务。
如何为视图层次结构中向下嵌套的多个级别的视图提供此服务。
例如;
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()
}
}
这个视图可以向下嵌套很多层,我已经把它写得尽可能笨了,但在某些时候我需要获取图像并更新这个视图。
这个视图本身应该获取图像吗?
我可以实现接受 ImageLoader
实例的 UserAvatarViewModel
- 但我不清楚如何创建具有所需依赖项的模型。
我很想避免单例实例,我不认为视图应该负责创建视图模型,这排除了 Environment
将加载程序向下传递的可能性。
我曾认为最上面的视图负责所有繁重的工作,但后来我可以将属性传递给视图,只是让它们将它们传递给它们的 child 视图,这感觉就像 anti-pattern 再次.
您只需将AsyncImage 组件替换为您自己的组件即可。我创建了这个组件,类似于 AsyncImage 但它接收自定义异步函数来获取图像
struct CustomAsyncImage<Content: View, Placeholder: View>: View {
@State var uiImage: UIImage?
let getImage: () async -> UIImage?
let content: (Image) -> Content
let placeholder: () -> Placeholder
init(
getImage: @escaping () async -> UIImage?,
@ViewBuilder content: @escaping (Image) -> Content,
@ViewBuilder placeholder: @escaping () -> Placeholder
){
self.getImage = getImage
self.content = content
self.placeholder = placeholder
}
var body: some View {
if let uiImage = uiImage {
content(Image(uiImage: uiImage))
}else {
placeholder()
.task {
self.uiImage = await getImage()
}
}
}
}
这是一个使用示例。
CustomAsyncImage(getImage: getImage) { image in
image
.resizable()
.scaledToFit()
.frame(maxWidth: .infinity)
} placeholder: {
Text("PlaceHolder")
}
其中 getImage 是 return UIImage 的异步函数,您可以根据需要创建该函数。
我将向您展示我的功能作为示例。
func getImage() async -> UIImage? {
guard let url = URL(string: BASE_URL + "cards/5fd36d31937b6b32d47ad9db/attachments/5fd36d31937b6b32d47ad9de/download") else {
fatalError("Missing URL")
}
var request = URLRequest(url: url)
request.addValue("OAuth oauth_consumer_key=\"\(API_KEY)\", oauth_token=\"\(ACCOUNT_TOKEN)\"", forHTTPHeaderField: "Authorization")
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { fatalError("Error while fetching attchment") }
return UIImage(data: data)
} catch {
return nil
}
}