将项目从一个数组移动到另一个数组时保持绑定吗?

问题描述 投票:0回答:1

我有一个应用程序,用户可以在其中为城市创建活动。用户可以为每个事件选择日期和时间。

在城市页面上,用户可以查看所有活动,还可以设置每个活动的日期和时间。城市页面分为两个部分:无组织活动和有组织活动。无组织的活动只是没有设定日期的活动。

因此,当用户导航到城市页面时,我的 API 返回两个数组:一个无组织事件数组和一个有组织事件数组。

当用户设置未组织事件的日期时,会将事件移动到有组织事件数组中。

此外,在城市页面上,用户可以创建一个新事件,该事件会自动添加到无组织事件数组中。

但是,我遇到了崩溃,发生在以下情况:

  1. 用户创建一个新事件
  2. 用户点击该新事件
  3. 设置日期(将其添加到组织的事件数组中)
  4. 设置时间(这里崩溃了)

错误是:

线程 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) ?? []
    }
}
ios swift xcode swiftui
1个回答
0
投票

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
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.