SwiftUI 中的分组列表数据可按部分显示

Shi*_*ami 6 ios swift swiftui swiftui-list

我正在尝试获取一组项目(结构)并使用 SwiftUI 将它们显示在分组表视图中。

我的(简化的)模型如下所示:

struct CheckIn: Identifiable {
  ...
  let id = UUID()
  let date = Date().atMidnight // removes the time component
  var completed: Bool
  ...
}

class Store: ObservableObject {
  @Published var checkIns = [...] {
    didSet { persist() }
  }
}
Run Code Online (Sandbox Code Playgroud)

在列表中显示签到之前,我想按日期对它们进行分组。所以我有另一个模型:

struct DailyCheckIns {
  let date: Date
  let checkIns: [CheckIn]
}

// and a function to group the check-ins array:
func groupByDate(_ checkIns: [CheckIn]) -> [DailyCheckIns] {...}
Run Code Online (Sandbox Code Playgroud)

视图是我遇到问题的地方。下面的版本可以工作,但数据没有明显分组。我所说的“有效”是指数据被传入CheckInView并且它可以更新其签入,然后正确地反映在商店和 UI 中。

struct ContentView: View {
  @EnvironmentObject var store: Store

  var body: some View {
    NavigationView {
      List {
        ForEach(store.checkIns.indices) { idx in
          CheckInView(checkIn: self.$store.checkIns[idx]) // checkIn is a @Binding
        }
      }
      .navigationBarTitle("Check Ins")
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

下一个版本是我对数据进行分组的尝试。通过这种方法,我必须将CheckInViewcheckIn属性从更改@Binding@State。分组有效并显示数据,但当切换签入完成时,模型会更新,但 UI 不会更新。

struct ContentView: View {
  @EnvironmentObject var store: Store

  var body: some View {
    NavigationView {
      List {
        ForEach(groupByDate(store.checkIns), id: \.date) { daily in
          Section(header: Text(dateFormatter.string(from: daily.date))) {
            ForEach(daily.checkIns, id: \.id) { checkIn in
              CheckInView(checkIn: checkIn) // I can't use a binding here, so in this version I need to make checkIn a @State.
            }
          }
        }
      }
      .navigationBarTitle("Check Ins")
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

目前,我没有CheckInView直接修改签入。相反,它将更新发布到商店,然后商店更新模型:

struct CheckInView: View {
  @Binding var checkIn: CheckIn
  @EnvironmentObject var store: Store

  var body: some View {
    HStack {
      Button(action: {
        self.store.update(checkIn: self.checkIn, with: true)

      }) {
        Image(systemName: "...")
          .font(.largeTitle)
          .foregroundColor(checkIn.completed ? .gray : .red)
      }
      .buttonStyle(BorderlessButtonStyle())
...
Run Code Online (Sandbox Code Playgroud)

所以问题是:如何保持列表分组并保持绑定在视图层次结构中一直有效?

Shi*_*ami 2

我已经想出了如何做到这一点。我不知道这是否是最优雅的解决方案。对我来说它看起来确实不优雅,但我想不出其他方法可以做到这一点。

在我发布的代码中,DailyCheckIns是一个用于分组签入的中间模型。该函数groupByDate接受一个 s 数组CheckIn并将它们转换为DailyCheckIns 进行显示。问题是它DailyCheckIn本质上保存了存储中数据的副本,因此我无法真正使用它来创建从该数据到存储的绑定。

我发现解决这个问题的方法是使用DailyCheckIns 创建部分和每个部分中的行数,但是当创建需要绑定到存储的视图时,我直接使用存储的数据。为了实现这一点,我必须更改DailyCheckIns (和)来跟踪商店属性中groupByDate每个的索引:CheckIn

typealias CheckInWithIndex = (Int, CheckIn)

struct DailyCheckIns {
  let date: Date
  var checkIns: [CheckInWithIndex]

  func appending(_ ci: CheckInWithIndex) -> DailyCheckIns {
    DailyCheckIns(date: date, checkIns: checkIns + [ci])
  }
}

private func groupByDate(_ checkIns: [CheckIn]) -> [DailyCheckIn] { ... }

struct ContentView: View {
  @EnvironmentObject var store: Store

  var body: some View {
    NavigationView {
      List {
        ForEach(groupByDate(store.checkIns), id: \.date) { daily in
          Section(header: Text(dateFormatter.string(from: daily.date))) {
            ForEach(daily.checkIns, id: \.id) { checkInWithIndex in
              CheckInView(checkIn: self.$store.checkIns[checkInWithIndex.0])
            }
          }
        }
      }
      .navigationBarTitle("Check Ins")
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

希望这对某人有帮助。但同时,我也希望有人能更好地解决这种情况。