我有一个应用程序,用户可以在其中为城市创建活动。用户可以为每个事件选择日期和时间。
在城市页面上,用户可以查看所有活动,还可以设置每个活动的日期和时间。城市页面分为两个部分:无组织活动和有组织活动。无组织的活动只是没有设定日期的活动。
因此,当用户导航到城市页面时,我的 API 返回两个数组:一个无组织事件数组和一个有组织事件数组。
当用户设置未组织事件的日期时,会将事件移动到有组织事件数组中。
此外,在城市页面上,用户可以创建一个新事件,该事件会自动添加到无组织事件数组中。
但是,我遇到了崩溃,发生在以下情况:
错误是:
线程 1:致命错误:索引超出范围
我相信这可能与绑定未被维护以及一旦将事件从无组织事件数组移动到有组织事件数组后就无法找到事件有关。
这是我的代码。我省略了一些不重要的部分。
MyApp 类充当单一事实来源:
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject var cityManager = CityManager(cityService: CityService())
var body: some Scene {
WindowGroup {
NavigationView {
MainView()
.environmentObject(cityManager)
}
}
}
}
我显示城市列表的主页视图:
struct HomeView: View {
@StateObject private var vm = HomeViewModel(homeService: HomeService())
@Binding var tabSelection: Int
@EnvironmentObject var cityManager: CityManager
var body: some View {
NavigationStack {
ScrollView(showsIndicators: false) {
VStack(spacing: 0) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top, spacing: 32) {
ForEach($cityManager.cities, id: \.id) { $city in
CityItem(city: $city, axes: .horizontal)
}
}
}
}
}
}
.onAppear {
vm.fetchHome()
}
.onChange(of: vm.cities?.count) {
cityManager.cities = vm.cities ?? []
}
.environmentObject(cityManager)
}
}
城市项目:
struct StadiumItem: View {
@Binding var city: City
var body: some View {
NavigationLink(destination: CityPageView(city: $city)) {
VStack {
Text(city.name)
}
}
}
}
CityPageView,这是显示城市中发生的所有事件的主页:
struct CityPageView: View {
@Binding var city: City
@EnvironmentObject var cityManager: CityManager
@StateObject var cityPageViewModel = CityPageViewModel()
var body: some View {
NavigationView {
ZStack(alignment: .top) {
ScrollViewReader { value in
ScrollView(showsIndicators: false) {
VStack(spacing: 0) {
// List of unorganized events
UnorganizedEventsView(city: $city)
// List of organized events
OrganizedEventsView(days: $city.organizedEvents)
}
}
}
}
}
.environmentObject(cityManager)
.environmentObject(cityPageViewModel)
}
}
struct UnorganizedEventsView: View {
@Binding var city: City
var body: some View {
let unorganizedEvents = $city.unorganizedEvents
if (!unorganizedEvents.isEmpty) {
VStack(alignment: .leading, spacing: 0) {
Text("Unorganized")
LazyVStack(spacing: 0) {
ForEach(unorganizedEvents, id: \.id) { $event in
EventView(
event: $event,
isLast: event == unorganizedEvents.wrappedValue.last
)
}
}
}
.id("Unorganized")
}
}
}
struct OrganizedEventsView: View {
@Binding var days: [Day]
var body: some View {
ForEach($days, id: \.id) { $day in
VStack(alignment: .leading, spacing: 0) {
Text(day.date.toDayDate() ?? "")
LazyVStack {
ListEventsView(events: $day.events)
}
}
.id(day.date)
}
}
}
这是我的城市模型课程:
struct City: Codable, Identifiable {
var id: Int
var name: String
var events: [Event]? {
didSet {
self.unorganizedEvents = self.getUnorganizedEvents()
self.organizedEvents = self.getOrganizedEvents()
}
}
var unorganizedEvents: [Event] = []
var organizedEvents: [Day] = []
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case events = "events"
}
init(id: Int, name: String, events: [Event] = [], unorganizedEvents: [Event] = []) {
self.id = id
self.name = name
self.events = events
self.unorganizedEvents = unorganizedEvents
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.events = try container.decodeIfPresent([Event].self, forKey: .events) ?? []
self.unorganizedEvents = self.getUnorganizedEvents()
self.organizedEvents = self.getOrganizedEvents()
}
func getUnorganizedEvents() -> [Event] {
return events?.filter({ $0.date == nil }) ?? []
}
func getOrganizedEvents() -> [Day] {
guard let events = events else {
return []
}
var eventsByDay: [String: [Event]] = [:] // Dictionary to store events grouped by date
// Group events by date
for event in events {
if let date = event.date {
if var eventsOnDate = eventsByDay[date] {
eventsOnDate.append(event)
eventsByDay[date] = eventsOnDate
} else {
eventsByDay[date] = [event]
}
}
}
// Create Day instances for each group of events
var days: [Day] = []
for (date, events) in eventsByDay {
let sortedEvents = events.sorted { ($0.timeStart ?? "24:59:59") < ($1.timeStart ?? "24:59:59") }
let day = Day(date: date, events: sortedEvents)
days.append(day)
}
days.sort { $0.date < $1.date }
return days
}
}
我做错了什么会导致这次崩溃?
编辑
根据评论,我决定仅依赖 1 个数组。但是,我正在努力研究如何过滤事件,同时在原始
city.events
更新时保持过滤后的数组更新。
struct CityListView: View {
@Binding var city: City
var body: some View {
VStack(alignment: .leading, spacing: 0) {
LazyVStack(spacing: 0) {
// Here, I want to filter by events where date = nil
ForEach($city.events, id: \.id) { $event in
EventView(
event: $event
)
}
}
}
}
}
这是我的
City
模型:
struct City: Codable, Identifiable {
var id: Int
var name: String
var events: [Event] = []
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case events = "events"
}
init(id: Int, name: String, events: [Event] = []) {
self.id = id
self.name = name
self.events = events
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.events = try container.decodeIfPresent([Event].self, forKey: .events) ?? []
}
}
在
filter
循环中使用 ForEach
尝试此方法。
由于您有绑定的附加要求
EventView
,代码比单纯的过滤器稍微复杂一些。
这是测试代码,根据您的要求仅显示带有
date == nil
的城市。
struct ContentView: View {
@State private var city = City(id: 1, name: "Tokyo", events: [Event(name: "manga", date: Date()), Event(name: "techno", date: nil)])
var body: some View {
CityListView(city: $city)
}
}
struct EventView: View {
@Binding var event: Event
var body: some View {
HStack {
Text(event.name)
Text(" date: \(event.date)")
}
}
}
struct CityListView: View {
@Binding var city: City
var body: some View {
VStack(alignment: .leading, spacing: 0) {
LazyVStack(spacing: 0) {
// Here, I want to filter by events where date = nil
ForEach(city.events.filter{ $0.date == nil }) { event in
if let index = city.events.firstIndex(where: {$0.id == event.id}) {
EventView(event: $city.events[index])
}
}
}
}
}
}
struct City: Codable, Identifiable {
var id: Int
var name: String
var events: [Event] = []
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case events = "events"
}
// no need for your other code, its all done for you by the struct
}
struct Event: Codable, Identifiable {
let id = UUID()
var name: String
var date: Date?
enum CodingKeys: String, CodingKey {
case name, date
}
}