【问题标题】:How to update filtered list in swiftui, when the value in the filter is changed?当过滤器中的值更改时,如何在 swiftui 中更新过滤列表?
【发布时间】:2023-02-06 03:28:37
【问题描述】:

如果这是一个简单的问题,通常是 swiftui 新手的警告和道歉。

我有一个视图,其中有一个日期选择器,以及两个用于增加/减少日期的箭头。更新此日期后,我试图从数据库中筛选出与当前显示日期匹配的“会话”列表。

我有一个 filteredSessions 变量,它对数据库中的所有“会话”应用过滤器。但是,每次更改日期时,我似乎都没有刷新该过滤器。

我将要使用的日期存储为视图中的“@State”对象。我认为这会在该字段更改时触发视图更新?但是我运行了调试器,发现“filteredSessions”变量只被调用一次,而不是在日期更改时(通过选择器或按钮)。

我在这里缺少什么吗?我是否需要一种特殊的方法来将此日期值“绑定”到列表,因为显示器不直接使用它?

下面的代码。谢谢

import SwiftUI

struct TrainingSessionListView: View {
    
    @StateObject var viewModel = TrainingSessionsViewModel()
    @State private var displayDate: Date = Date.now
    @State private var presentAddSessionSheet = false
    
    private var dateManager = DateManager()
    
    private let oneDay : Double = 86400
    
    private var addButton : some View {
        Button(action: { self.presentAddSessionSheet.toggle() }) {
            Image(systemName: "plus")
        }
    }
    
    private var decreaseDayButton : some View {
        Button(action: { self.decreaseDay() }) {
            Image(systemName: "chevron.left")
        }
    }
    
    private var increaseDayButton : some View {
        Button(action: { self.increaseDay() }) {
            Image(systemName: "chevron.right")
        }
    }
    

    private func sessionListItem(session: TrainingSession) -> some View {
        NavigationLink(destination: TrainingSessionDetailView(session: session)) {
            VStack(alignment: .leading) {
                Text(session.title)
                    .bold()
                Text("\(session.startTime) - \(session.endTime)")
            }
        }
    }
    
    
    private func increaseDay() {
        self.displayDate.addTimeInterval(oneDay)
    }
    
    private func decreaseDay() {
        self.displayDate.addTimeInterval(-oneDay)
    }
    
    
    var body: some View {
        NavigationView {
            VStack {
                HStack {
                    Spacer()
                    decreaseDayButton
                    Spacer()
                    DatePicker("", selection: $displayDate, displayedComponents: .date)
                        .labelsHidden()
                    Spacer()
                    increaseDayButton
                    Spacer()
                }
                .padding(EdgeInsets(top: 25, leading: 0, bottom: 0, trailing: 0))
                    
                Spacer()
                
                ForEach(filteredSessions) { session in
                    sessionListItem(session: session)
                }
                
                Spacer()
                
            }
            .navigationTitle("Training Sessions")
            .navigationBarTitleDisplayMode(.inline)
            .navigationBarItems(trailing: addButton)
            .sheet(isPresented: $presentAddSessionSheet) {
                TrainingSessionEditView()
            }
            
        }
    }
    
    var filteredSessions : [TrainingSession] {
        print("filteredSessions called")
        return viewModel.sessions.filter { $0.date == dateManager.dateToStr(date: displayDate) }
    }
}

struct TrainingSessionListView_Previews: PreviewProvider {
    static var previews: some View {
        TrainingSessionListView()
    }
}

【问题讨论】:

  • 您可能想在分配 filteredSessions 的地方引入另一个 @State 变量,即创建一个返回过滤集的函数并为其分配一个相应的 @State 变量。

标签: ios swift swiftui filter


【解决方案1】:

有两种方法,对于您的情况和您所描述的内容,我会采用第一种方法。如果我有更复杂的过滤器和任务,我只使用第二种方法

您可以直接在 ForEach 上设置过滤器,这将确保它在 displayDate 更改时得到更新。

ForEach(viewModel.sessions.filter { $0.date == dateManager.dateToStr(date: displayDate) }) { session in
    sessionListItem(session: session)
}

或者您可以像 CouchDeveloper 所说的那样,引入一个新的状态变量并触发状态更改,您可以使用 willSet 扩展名(绑定中不存在,但您可以创建它)

对于第二个选项,您可以这样做。

  1. 开始为didSetwillSet 创建绑定扩展
    extension Binding {
        func didSet(execute: @escaping (Value) ->Void) -> Binding {
            return Binding(
                get: {
                    return self.wrappedValue
                },
                set: {
                    let snapshot = self.wrappedValue
                    self.wrappedValue = $0
                    execute(snapshot)
                }
            )
        }
        func willSet(execute: @escaping (Value) ->Void) -> Binding {
            return Binding(
                get: {
                    return self.wrappedValue
                },
                set: {
                    execute($0)
                    self.wrappedValue = $0
                }
            )
        }
    }
    
    1. 引入新的状态变量
    @State var filteredSessions: [TrainingSession] = []
    // removing the other var
    
    1. 我们介绍将更新状态变量的函数
    func filterSessions(_ filter: Date) {
        filteredSessions = viewModel.sessions.filter { $0.date == dateManager.dateToStr(date: date) }
    }
    
    1. 我们更新 DatePicker 以使用 willSet 运行函数
    DatePicker("", selection: $displayDate.willSet { self.filterSessions($0) }, displayedComponents: .date)
    
    1. 最后我们添加一个onAppear 以便我们立即填充 filteredSessions(如果需要)
    .onAppear { filterSessions(displayDate) } // uses the displayDate that you set as initial value
    

    不要忘记在increaseDay()decreaseDay() 函数中在addTimeInterval 之后添加以下内容

    self.filterSessions(displayDate)
    

    正如我所说,第二种方法可能更适合更复杂的过滤器

【讨论】:

  • 感谢您的答复。我喜欢这种范式,将来很可能会使用它。然而,对于这个问题,使用 @FirestoreQuery 似乎解决了我遇到的问题。详情见下文。再次感谢。
【解决方案2】:

谢谢大家的回复。我不确定问题最初是什么,但它似乎更新了我的视图以使用 Firebase 的 @FirestoreQuery 访问集合更新 var filteredSessions... 比我以前的要好得多。

下面的新代码现在似乎运行良好。

import SwiftUI
import FirebaseFirestoreSwift

struct TrainingSessionListView: View {
    
    @FirestoreQuery(collectionPath: "training_sessions") var sessions : [TrainingSession]
    
    @State private var displayDate: Date = Date.now
    @State private var presentAddSessionSheet = false
    
    
    private var dateManager = DateManager()
    
    private let oneDay : Double = 86400
    
    private var addButton : some View {
        Button(action: { self.presentAddSessionSheet.toggle() }) {
            Image(systemName: "plus")
        }
    }
    
    private var todayButton : some View {
        Button(action: { self.displayDate = Date.now }) {
            Text("Today")
        }
    }
    
    private var decreaseDayButton : some View {
        Button(action: { self.decreaseDay() }) {
            Image(systemName: "chevron.left")
        }
    }
    
    private var increaseDayButton : some View {
        Button(action: { self.increaseDay() }) {
            Image(systemName: "chevron.right")
        }
    }
    

    private func sessionListItem(session: TrainingSession) -> some View {
        NavigationLink(destination: TrainingSessionDetailView(sessionId: session.id!)) {
            VStack(alignment: .leading) {
                Text(session.title)
                    .bold()
                Text("(session.startTime) - (session.endTime)")
            }
        }
    }
    
    
    private func increaseDay() {
        self.displayDate.addTimeInterval(oneDay)
    }
    
    private func decreaseDay() {
        self.displayDate.addTimeInterval(-oneDay)
    }
    
    
    var body: some View {
        NavigationView {
            VStack {
                HStack {
                    Spacer()
                    decreaseDayButton
                    Spacer()
                    DatePicker("", selection: $displayDate, displayedComponents: .date)
                        .labelsHidden()
                    Spacer()
                    increaseDayButton
                    Spacer()
                }
                .padding(EdgeInsets(top: 25, leading: 0, bottom: 10, trailing: 0))
                    
                if filteredSessions.isEmpty {
                    Spacer()
                    Text("No Training Sessions found")
                } else {
                    List {
                        ForEach(filteredSessions) { session in
                            sessionListItem(session: session)
                        }
                    }
                }
                Spacer()
                
            }
            .navigationTitle("Training Sessions")
            .navigationBarTitleDisplayMode(.inline)
            .navigationBarItems(leading: todayButton, trailing: addButton)
            .sheet(isPresented: $presentAddSessionSheet) {
                TrainingSessionEditView()
            }
        }
    }
    
    var filteredSessions : [TrainingSession] {
        return sessions.filter { $0.date == dateManager.dateToStr(date: displayDate)}
    }
    
}

struct TrainingSessionListView_Previews: PreviewProvider {
    static var previews: some View {
        TrainingSessionListView()
    }
}

【讨论】:

    猜你喜欢
    • 2021-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多