82 lines
2.4 KiB
Swift
82 lines
2.4 KiB
Swift
//
|
||
// RemoteImage.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/22.
|
||
//
|
||
|
||
import Kingfisher
|
||
import SwiftUI
|
||
|
||
/// 网络图片组件,统一使用 Kingfisher 加载和缓存远程图片。
|
||
struct RemoteImage<Placeholder: View>: View {
|
||
let urlString: String?
|
||
let contentMode: SwiftUI.ContentMode
|
||
@ViewBuilder let placeholder: () -> Placeholder
|
||
|
||
@State private var didFail = false
|
||
|
||
/// 初始化网络图片组件。
|
||
init(
|
||
urlString: String?,
|
||
contentMode: SwiftUI.ContentMode = .fill,
|
||
@ViewBuilder placeholder: @escaping () -> Placeholder
|
||
) {
|
||
self.urlString = urlString
|
||
self.contentMode = contentMode
|
||
self.placeholder = placeholder
|
||
}
|
||
|
||
var body: some View {
|
||
Group {
|
||
if let url = imageURL, !didFail {
|
||
KFImage(url)
|
||
.placeholder { placeholder() }
|
||
.onFailure { _ in didFail = true }
|
||
.resizable()
|
||
.cacheOriginalImage()
|
||
.aspectRatio(contentMode: contentMode)
|
||
} else {
|
||
placeholder()
|
||
}
|
||
}
|
||
.onChange(of: normalizedURLString) { _, _ in
|
||
didFail = false
|
||
}
|
||
}
|
||
|
||
private var imageURL: URL? {
|
||
guard let normalizedURLString else { return nil }
|
||
return URL(string: normalizedURLString)
|
||
}
|
||
|
||
private var normalizedURLString: String? {
|
||
let text = urlString?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||
return text.isEmpty ? nil : text
|
||
}
|
||
}
|
||
|
||
/// 远程头像组件,提供统一的圆形头像占位和 Kingfisher 加载逻辑。
|
||
struct RemoteAvatarImage: View {
|
||
let urlString: String
|
||
let systemImageName: String
|
||
let iconSize: CGFloat
|
||
|
||
/// 初始化远程头像组件。
|
||
init(urlString: String, systemImageName: String = "person.fill", iconSize: CGFloat = 44) {
|
||
self.urlString = urlString
|
||
self.systemImageName = systemImageName
|
||
self.iconSize = iconSize
|
||
}
|
||
|
||
var body: some View {
|
||
RemoteImage(urlString: urlString, contentMode: .fill) {
|
||
Image(systemName: systemImageName)
|
||
.font(.system(size: iconSize, weight: .semibold))
|
||
.foregroundStyle(AppDesign.primary)
|
||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||
.background(AppDesign.primarySoft)
|
||
}
|
||
}
|
||
}
|