设置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)) } } }
问题原因与解决方案
核心问题
你的代码逻辑顺序存在两个关键问题:
- 后台队列的数据库操作是异步执行的,主线程不会等待它完成,导致你刚把
isLoading设为true,立刻就执行了isLoading = false和dismiss(),加载指示器还没来得及显示就被关闭。 - 直接在后台线程使用主线程的
viewContext,违反了Core Data的线程安全规则,容易引发崩溃或数据异常。
修复方案
- 调整代码执行顺序:将
isLoading = false和dismiss()移到后台任务的末尾,并且必须切回主线程更新UI状态。 - 修复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
相关产品推荐
相关产品推荐

