如何创建与AsyncImage类似的带分阶段状态协议的SwiftUI自定义视图
自定义带状态回调视图实现参考
要实现和AsyncImage逻辑一致的自定义组件,核心是定义状态枚举、内部维护状态流转、通过ViewBuilder闭包把状态透传给调用方。下面是完整可运行的实现:
1. 首先定义状态枚举
这个枚举对应你说的phase变量,每个状态可以带自定义关联值:
enum AsyncPhase<Success, Failure: Error = Error> { case empty // 加载中/未开始 case success(Success) // 加载成功,关联返回的结果 case failure(Failure) // 加载失败,关联错误信息 }
2. 自定义核心视图实现
我们以异步加载场景为例,实现通用的自定义视图:
import SwiftUI struct AsyncComponentView<Content: View, Success, Failure: Error = Error>: View { // 内部维护当前状态 @State private var phase: AsyncPhase<Success, Failure> = .empty // 外部传入的异步加载任务 private let loadTask: () async throws -> Success // 外部传入的视图构建闭包,接受phase参数 private let content: (AsyncPhase<Success, Failure>) -> Content // 动画事务配置,和系统AsyncImage的transaction参数对齐 private let transaction: Transaction // 初始化方法 init( transaction: Transaction = Transaction(), loadTask: @escaping () async throws -> Success, @ViewBuilder content: @escaping (AsyncPhase<Success, Failure>) -> Content ) { self.transaction = transaction self.loadTask = loadTask self.content = content } var body: some View { content(phase) .task { do { let result = try await loadTask() // 按照指定事务更新状态,保证动画生效 withTransaction(transaction) { phase = .success(result) } } catch let error as Failure { withTransaction(transaction) { phase = .failure(error) } } } } } // 针对加载图片的场景做扩展,和你给出的AsyncImage用法完全对齐 extension AsyncComponentView where Success == UIImage, Failure == Error { init( url: URL, transaction: Transaction = Transaction(), @ViewBuilder content: @escaping (AsyncPhase<UIImage, Error>) -> Content ) { self.init(transaction: transaction) { let (data, _) = try await URLSession.shared.data(from: url) guard let image = UIImage(data: data) else { throw NSError(domain: "AsyncComponent", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的图片数据"]) } return image } content: { phase in content(phase) } } }
3. 使用示例
用法和你给出的AsyncImage示例完全一致:
let testImageURL = URL(string: "https://example.com/test.jpg")! struct DemoView: View { var body: some View { AsyncComponentView( url: testImageURL, transaction: Transaction(animation: .easeInOut) ) { phase in switch phase { case .empty: ProgressView() case .success(let image): // 这里的image就是成功状态的关联值,和你示例中的`let image`用法完全相同 Image(uiImage: image) .resizable() .scaledToFit() .transition(.scale(scale: 0.1, anchor: .center)) case .failure: Image(systemName: "wifi.slash") @unknown default: EmptyView() } } .frame(width: 200, height: 200) } }
核心特性说明
- 自定义状态扩展:你可以根据业务需求修改
AsyncPhase枚举,添加你需要的状态类型,关联值可以自定义任意类型,不局限于图片 - 子视图完全自定义:通过
@ViewBuilder修饰的闭包,所有子视图都由调用方自行实现,和系统组件的使用习惯一致 - 动画逻辑对齐系统实现:通过transaction参数统一控制状态切换的动画效果
内容的提问来源于stack exchange,提问作者J. Edgell
相关产品推荐
相关产品推荐

