异步加载数据时iOS界面抖动(刷新轮跳转)问题修复咨询
嘿,咱们来一步步解决你的界面抖动和元素显示问题~先从代码里的异步逻辑问题入手,再调整UI的显示时机:
一、修正异步数据加载的核心问题
你的getAlertsDataDebug方法里混用了Firebase的闭包回调和async/await,这会导致SwiftUI无法准确感知数据加载的完成时机——await会直接返回,而闭包还在后台跑,界面会提前刷新,这就是闪空白和刷新轮抖动的根源之一。
把Firebase的回调改成async/await风格(Firebase 9+支持getData()方法),确保await真正等待数据加载完成:
class AlertListDebug: ObservableObject { @Published var alerts = [String:[String:Any]]() @MainActor func getAlertsDataDebug(refAlertsRaw: DatabaseReference!, officialTwitter: String) async { self.alerts = [:] let query = refAlertsRaw.child("alerts").child("raw_" + officialTwitter) do { // 用async/await替代闭包回调 let snapshot = try await query.getData() if let dict = snapshot.value as? [String: Any] { for (key, alertRaw) in dict { guard let line = alertRaw["Line"], let text = alertRaw["Text"] else { continue } self.alerts[key] = ["lines": line, "text": text] } } } catch { print("加载告警数据失败:\(error.localizedDescription)") } } }
二、解决刷新轮跳转与闪空白问题
1. 添加加载状态追踪
在AlertListDebug里新增一个isLoading标记,用来控制加载指示器和内容的显示时机:
class AlertListDebug: ObservableObject { @Published var alerts = [String:[String:Any]]() @Published var isLoading = false // 新增加载状态 @MainActor func getAlertsDataDebug(refAlertsRaw: DatabaseReference!, officialTwitter: String) async { self.isLoading = true // 开始加载时标记为true self.alerts = [:] let query = refAlertsRaw.child("alerts").child("raw_" + officialTwitter) do { let snapshot = try await query.getData() if let dict = snapshot.value as? [String: Any] { for (key, alertRaw) in dict { guard let line = alertRaw["Line"], let text = alertRaw["Text"] else { continue } self.alerts[key] = ["lines": line, "text": text] } } } catch { print("加载告警数据失败:\(error.localizedDescription)") } finally { self.isLoading = false // 无论成功失败,结束加载标记为false } } }
2. 控制UI的显示逻辑
在视图里根据isLoading和alerts.count来切换显示内容,确保加载时只显示固定位置的加载指示器,数据加载完成后再展示列表和「All Tweets」:
struct AlertsViewDebug: View { @StateObject var alertList = AlertListDebug() @State var officialTwitter = "official" var refAlertsRaw: DatabaseReference! = Database.database().reference() var body: some View { VStack(alignment: .leading, spacing: 0){ // 顶部切换按钮区域保持不变 HStack { Button(action: { officialTwitter = "official"; Task { await alertList.getAlertsDataDebug(refAlertsRaw: refAlertsRaw, officialTwitter: officialTwitter) } }) { Text("Official Alerts") } Spacer() Button(action: { officialTwitter = "twitter"; Task { await alertList.getAlertsDataDebug(refAlertsRaw: refAlertsRaw, officialTwitter: officialTwitter) } }) { Text("Twitter Feed") } } ScrollView { VStack(spacing: 0) { // 加载中显示固定位置的进度条,防止跳转 if alertList.isLoading { ProgressView() .frame(maxWidth: .infinity, maxHeight: 200) } // 数据加载完成且有内容时显示列表 else if alertList.alerts.count > 0 { ForEach(alertList.alerts.keys.sorted(), id: \.self) {alertKey in Text(alertKey) .font(.system(size: 16)) .foregroundColor(.black) .frame(width: 320) } .overlay( RoundedRectangle(cornerRadius: 10) .stroke(.black, lineWidth: 1) ) .padding(.vertical, 5) // 仅在Twitter模式且数据加载完成后显示「All Tweets」 if officialTwitter == "twitter" { Text("All Tweets") .padding(.top, 10) } } // 无数据时显示提示 else { Text("暂无告警数据") .frame(maxWidth: .infinity, maxHeight: 200) .foregroundColor(.gray) } } } .task { await alertList.getAlertsDataDebug(refAlertsRaw: refAlertsRaw, officialTwitter: officialTwitter) } .refreshable { await alertList.getAlertsDataDebug(refAlertsRaw: refAlertsRaw, officialTwitter: officialTwitter) } Spacer() } .navigationTitle("Alerts") .navigationBarTitleDisplayMode(.inline) } }
三、额外优化建议
- 用自定义结构体替代
[String:[String:Any]]存储告警数据,避免强制解包的风险,代码可读性也更高:
struct Alert: Identifiable { let id: String let lines: String let text: String }
然后把alerts改成@Published var alerts = [Alert](),遍历的时候更方便。
- 按钮点击时可以添加防抖逻辑,避免重复点击触发多次数据请求。
内容的提问来源于stack exchange,提问作者Jack Weisman
相关产品推荐
相关产品推荐

