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

SwiftUI macOS应用切换视图后@State变量状态丢失如何解决?

解决方案

1. 把状态和任务管理移到可观察对象中

@State是视图本地状态,当视图切换再返回时,视图实例可能被重新初始化,导致busy重置,但之前的下载任务还在后台运行。要解决这个问题,需要把状态和任务逻辑抽离到ObservableObject中,用@StateObject持有它——这个属性的生命周期和视图绑定,即使视图重建,对象也不会被重新初始化,状态能一直保留。

示例代码:

import SwiftUI

class DownloadManager: ObservableObject {
    @Published var isBusy = false
    private var downloadTask: Task<Void, Error>?
    
    func startDownload(for content: Content) {
        // 避免重复触发同一下载
        guard !isBusy else { return }
        
        isBusy = true
        downloadTask = Task { [weak self] in
            do {
                try await content.download()
            } catch {
                // 这里可以添加错误处理,比如弹窗提示用户
                print("下载失败: \(error)")
            }
            // 任务被取消时,不要更新状态
            guard !Task.isCancelled else { return }
            self?.isBusy = false
        }
    }
    
    // 可选:添加取消下载的功能
    func cancelDownload() {
        downloadTask?.cancel()
        downloadTask = nil
        isBusy = false
    }
}

struct DownloadRow: View {
    let content: Content
    @StateObject private var downloadManager = DownloadManager()
    
    var body: some View {
        if !downloadManager.isBusy {
            Button(action: {
                downloadManager.startDownload(for: content)
            }) {
                Label("Download")
            }
        } else {
            HStack {
                ProgressView()
                // 可选:显示取消按钮
                Button("Cancel") {
                    downloadManager.cancelDownload()
                }
            }
        }
    }
}

2. 关键细节说明

  • @StateObject的作用:专门用于持有ObservableObject实例,确保视图重建时对象不会被销毁,状态得以保留。
  • 任务实例管理:用属性保存Task实例,既可以防止重复触发下载,也能支持后续的取消操作。
  • 弱引用self:在Task闭包中使用[weak self],避免循环引用导致内存泄漏。
  • 任务取消检查:通过Task.isCancelled判断任务是否被取消,避免在任务取消后错误地更新状态。

3. 多内容场景优化

如果你的界面有多个DownloadRow对应不同的Content,可以修改DownloadManager为每个Content单独维护下载状态,比如用字典存储任务和状态:

class DownloadManager: ObservableObject {
    @Published var busyContentIDs: Set<Content.ID> = []
    private var tasks: [Content.ID: Task<Void, Error>] = [:]
    
    func startDownload(for content: Content) {
        let id = content.id
        guard !busyContentIDs.contains(id) else { return }
        
        busyContentIDs.insert(id)
        tasks[id] = Task { [weak self] in
            do {
                try await content.download()
            } catch {
                print("下载失败: \(error)")
            }
            guard !Task.isCancelled else { return }
            self?.busyContentIDs.remove(id)
            self?.tasks.removeValue(forKey: id)
        }
    }
    
    func cancelDownload(for content: Content) {
        let id = content.id
        tasks[id]?.cancel()
        tasks.removeValue(forKey: id)
        busyContentIDs.remove(id)
    }
}

// 假设你的Content类型有唯一ID属性
protocol Content {
    var id: UUID { get }
    func download() async throws
}

struct DownloadRow: View {
    let content: Content
    @StateObject private var downloadManager = DownloadManager()
    
    var body: some View {
        let isBusy = downloadManager.busyContentIDs.contains(content.id)
        if !isBusy {
            Button(action: {
                downloadManager.startDownload(for: content)
            }) {
                Label("Download")
            }
        } else {
            HStack {
                ProgressView()
                Button("Cancel") {
                    downloadManager.cancelDownload(for: content)
                }
            }
        }
    }
}

这样每个Content的下载状态都能独立管理,即使切换视图再返回,所有正在下载的任务状态都能正确显示。

内容的提问来源于stack exchange,提问作者Milan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 06:15:36