You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建与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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 17:54:03