首页 > 解决方案 > DispatchQueue 使用 SwiftUI 触发 foreach 循环两次

问题描述

我的代码有一个奇怪的行为。

User存在从对象获取其文本值的带有文本的视图。

如果我把引用拿出来,String一切都很好,但是得到这个值的 foreach 循环会运行两次。

此外,如果我将分配从DispatchQueue它运行一次,但视图不会自行更新(因为它是一个@Publishedvar)。即使我删除 @Published 它仍然运行两次。

我敢肯定这种行为有一个简单的解释,但我真的不知道是什么导致这种情况发生。

带有注释的代码中更好的解释:

风景:

struct MenuGroupBoxList: View {
    
    @EnvironmentObject var session: SessionStore
    
    var columns = [
        GridItem(.adaptive(minimum: 100, maximum: 200), spacing: 8),
        GridItem(.adaptive(minimum: 100, maximum: 200), spacing: 8)
    ]
    
    var body: some View {
        
        LazyVGrid(columns: columns, spacing: 8) {
            ForEach(MenuTitles.allCases, id:\.self) { item in
                if item == .sentShifts{
                    GroupColumn(menuItem: item, unit: Text(session.user?.totalShiftConsts.description ?? "0"))
                }else if item == .weekdays{

                    //Deleting this line: session.user?.totalShifts.description. will run the loop once.

                    GroupColumn(menuItem: item, unit: Text(session.user?.totalShifts.description ?? "0"))
                }else{
                    GroupColumn(menuItem: item, unit: Text("0"))
                }
            }
        }.background(Color(UIColor.systemGroupedBackground))
    }
}

foreach循环在哪里:

enum ChooseWeekdays{
    case current, next
}

class WeekdaysModel: Identifiable, ObservableObject{
    
    private var session = SessionStore.shared
    private var date = Date()

    @Published var week = [Day]()
    @Published var isFetching = false
    
    init(_ weekdays: ChooseWeekdays){
        
        switch weekdays {
        case .current:
            currentWeekdaysFetch()
        case .next:
            nextWeekdaysFetch()
        }
        
    }
    
    private func currentWeekdaysFetch(){

        DispatchQueue.main.async {
            self.isFetching = true
            var count = 0
            for (index, symbol) in self.date.weekDaySymbols.enumerated(){
                
                let day = Day(date: self.date.currentWeek[index], isSelected: false, hasShifts: false, daySymbol: symbol, dayNumber: self.date.currentWeekNumbers[index])
                
                day.setShifts(morning: self.session.employeesList, middle: self.session.employeesList, evening: self.session.employeesList)
                
                if index == Date().getCurrentDayIndex(){
                    day.isSelected = true
                }
                
                count = count + day.shifts.compactMap({$0.employees.contains(where: {$0.uid == self.session.user?.uid})}).count

                
                self.week.append(day)
                self.isFetching = false
                //Printing to see if loop run twice.
                print("Index:", index)
            }
            //OR - take this line out from DispatchQueue, will run once but wouldn't update the UI because totalShifts is a @Published var
            self.session.user?.totalShifts = count
        }
        
    }
}

我得到的打印结果是Index: 0...6两次。

创建地点WeekdaysModel

struct WeekdaysView: View {
    
    @ObservedObject var viewModel = WeekdaysModel(.current)
    
    var body: some View {
        ScrollView{
            WeekRow(model: viewModel)
            ForEach(viewModel.week){ day in
                ShiftTypesView(day: day)
            }
        }
        .navigationTitle(LocalizedStringKey("Weekdays"))
        .background(Color(UIColor.systemGroupedBackground))
        .edgesIgnoringSafeArea(.bottom)
    }
}

在不同的文件中有另一个启动但不同的枚举(WeekdaysModel(.next)

标签: swiftforeachswiftuiqueuegrand-central-dispatch

解决方案


在 SwiftUI 上似乎很常见。我确实实验了类似的行为填充表格视图的单元格,每个单元格被调用两次:似乎 swiftUI 填充了每个查看的元素两次(我假设满足旋转)

正如建议的那样,从 swiftUI“视图构建”中填充数据结构一次(您的数据将被读取两次,但不会对数据库或网络进行两次昂贵的调用)

这种行为与 std UiKit 非常不同,如果涉及网络,可能会令人沮丧(调用将发出两次......双重网络完成,服务器错误(例如双重登录等......)


推荐阅读