// // RemoteImage.swift // suixinkan // // Created by Codex on 2026/6/22. // import Kingfisher import SwiftUI /// 网络图片组件,统一使用 Kingfisher 加载和缓存远程图片。 struct RemoteImage: 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) } } }