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

设置isAnimating为TRUE时,Activity Indicator不显示问题求助

问题

我有多个Activity Indicator,订阅选择界面的那个可以正常工作,但当用户点击按钮向数据库写入信息时,把isAnimating参数设为true后,Activity Indicator却不显示。不过如果我在声明isLoading变量时直接设为true,Activity Indicator能正常显示。

以下是执行数据库操作的代码:

import SwiftUI

struct LanguagePickerWheel: View {

    @State private var selectedLanguage: String = ""
    @State private var isLoading = false

    var availableLanguages: [String] = []

    @Environment(\.presentationMode) var presentation
    @Environment(\.managedObjectContext) private var viewContext
    
    private func dismiss() {
        self.presentation.wrappedValue.dismiss()
    }
    
    var body: some View {
        GeometryReader { geometry in
            VStack {
                HStack {
                    Button("Cancel") { self.dismiss() }
                        .padding(.top)
                        .padding(.trailing, 125)
                    Button("Select") {
                        DispatchQueue.main.async {
                            isLoading = true
                        }
                        let queue = DispatchQueue(label: "work-queue-1")
                        queue.async {
                            if selectedLanguage == "" {
                                selectedLanguage = availableLanguages[0]
                            }
                            // submit language to the addAndSaveLanguage method
                            let newLanguage = Language(context: viewContext)
                            newLanguage.name = selectedLanguage
                            newLanguage.setAsRevision = false
                            PersistenceController.shared.saveDB()
                            do {
                                    // This solution assumes you've got the file in your bundle
                                if let path = Bundle.main.path(forResource: "\(selectedLanguage)_English_Over_2500_Words", ofType: "txt") {
                                    let data = try String(contentsOfFile:path, encoding: String.Encoding.utf8)
                                    var arrayOfStrings: [String]
                                    arrayOfStrings = data.components(separatedBy: ";")

                                    for string in arrayOfStrings {
                                        let newCommonWord = CommonWordThing(context: viewContext)
                                        newCommonWord.native = string.components(separatedBy: "_")[1]
                                        newCommonWord.foreign = string.components(separatedBy: "_")[0]
                                        newCommonWord.ckimage = false
                                        newCommonWord.inUse = false
                                        newCommonWord.typingTestCorrect = 0
                                        newCommonWord.arrangeWordsCorrect = 0
                                        newCommonWord.ckreference = newLanguage.ckrecordname
                                        newCommonWord.attempts = 0
                                        newCommonWord.image = nil
                                        newCommonWord.repetitionInterval = 0
                                        newCommonWord.testsUntilPresented = 0
                                        newCommonWord.setAsRevision = false
                                        newCommonWord.language = newLanguage
                                        
                                        var stringNumber = string.split(separator: "_")[2]
                                        if stringNumber.contains("\r\n") {
                                            stringNumber.removeLast(1)
                                        }
                                        newCommonWord.count = NumberFormatter().number(from: String(stringNumber) as String)?.int64Value ?? 0
                                    }
                                    
                                }
                            } catch let err as NSError {
                                    // do something with Error
                                print("Couldn't save new language to database: \(err)")
                            }
                            PersistenceController.shared.saveDB()
                        }
                        isLoading = false
                        self.dismiss()
                    }
                    .padding(.top)
                    .padding(.leading, 125)
                }
                Text("Choose a language:")
                    .font(.title)
                    .padding(.top, 50)
                Picker("Choose a language:", selection: $selectedLanguage, content: {
                    ForEach(Array(availableLanguages), id: \.self) { language in
                        Text(language)
                    }
                })
                .pickerStyle(WheelPickerStyle())
                .padding(.leading)
                .padding(.trailing)
                
            }
            ActivityIndicatorView(isAnimating: $isLoading)
                .position(x: geometry.frame(in: .local).midX, y: geometry.frame(in: .local).midY)
        }
    }
}

Activity Indicator代码:

struct ActivityIndicatorView: View {
    @Binding var isAnimating:Bool
    var body: some View {
        if isAnimating{
            ZStack{
                RoundedRectangle(cornerRadius: 15).fill(Color.lightGray)
                ProgressView {
                    Text("Loading...")
                        .font(.title2)
                }
            }.frame(width: 120, height: 120, alignment: .center)
            .background(RoundedRectangle(cornerRadius: 25).stroke(.gray,lineWidth: 2))
        }
    }
}
问题原因与解决方案

核心问题

你的代码逻辑顺序存在两个关键问题:

  1. 后台队列的数据库操作是异步执行的,主线程不会等待它完成,导致你刚把isLoading设为true,立刻就执行了isLoading = false和dismiss(),加载指示器还没来得及显示就被关闭。
  2. 直接在后台线程使用主线程的viewContext,违反了Core Data的线程安全规则,容易引发崩溃或数据异常。

修复方案

  1. 调整代码执行顺序:将isLoading = false和dismiss()移到后台任务的末尾,并且必须切回主线程更新UI状态。
  2. 修复Core Data线程问题:使用后台上下文处理数据库操作,避免直接在后台线程使用主线程上下文。

修复后的按钮点击逻辑代码

Button("Select") {
    // 按钮闭包本身就在主线程,直接更新加载状态
    isLoading = true
    
    // 使用全局后台队列执行耗时操作
    DispatchQueue.global(qos: .background).async {
        let targetLanguage = self.selectedLanguage.isEmpty ? self.availableLanguages[0] : self.selectedLanguage
        
        // 用Core Data的后台上下文处理操作,保证线程安全
        PersistenceController.shared.container.performBackgroundTask { backgroundContext in
            do {
                // 创建新语言实体
                let newLanguage = Language(context: backgroundContext)
                newLanguage.name = targetLanguage
                newLanguage.setAsRevision = false
                
                // 读取并解析本地文件
                if let path = Bundle.main.path(forResource: "\(targetLanguage)_English_Over_2500_Words", ofType: "txt") {
                    let data = try String(contentsOfFile: path, encoding: .utf8)
                    let wordEntries = data.components(separatedBy: ";")
                    
                    for entry in wordEntries {
                        let components = entry.components(separatedBy: "_")
                        // 跳过格式不正确的条目,避免数组越界
                        guard components.count >= 3 else { continue }
                        
                        let newWord = CommonWordThing(context: backgroundContext)
                        newWord.foreign = components[0]
                        newWord.native = components[1]
                        newWord.ckimage = false
                        newWord.inUse = false
                        newWord.typingTestCorrect = 0
                        newWord.arrangeWordsCorrect = 0
                        newWord.ckreference = newLanguage.ckrecordname
                        newWord.attempts = 0
                        newWord.image = nil
                        newWord.repetitionInterval = 0
                        newWord.testsUntilPresented = 0
                        newWord.setAsRevision = false
                        newWord.language = newLanguage
                        
                        // 处理计数字段,去除多余换行符和空白字符
                        let countString = components[2].trimmingCharacters(in: .whitespacesAndNewlines)
                        newWord.count = NumberFormatter().number(from: countString)?.int64Value ?? 0
                    }
                }
                
                // 保存后台上下文的修改
                try backgroundContext.save()
            } catch let error as NSError {
                print("保存语言失败: \(error)")
            }
            
            // 所有后台操作完成后,切回主线程更新UI并关闭页面
            DispatchQueue.main.async {
                self.isLoading = false
                self.dismiss()
            }
        }
    }
}

额外优化点

  • 移除了不必要的DispatchQueue.main.async包裹isLoading = true,因为按钮点击事件本身就在主线程执行。
  • 增加了条目格式校验,避免数组越界崩溃。
  • 使用trimmingCharacters替代手动移除换行符,逻辑更健壮。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 08:25:33