SwiftUI书籍按多作者/多分类分组排序及重复显示求助
SwiftUI实现多属性分组(多作者/多分类)并让书籍重复显示
核心问题分析
你之前的实现是在View层直接判断书籍是否匹配当前分组,这种方式对单一属性有效,但当书籍有多个作者/分类时,只会让书籍出现在最后一个匹配的分组中——因为遍历到最后一个属性时才触发显示逻辑,前面的属性匹配会被覆盖。
解决方案思路
要实现书籍在每个对应分组下重复显示,需要先在ViewModel层将原始数据展开转换:把每本书的每个多属性(如每个作者、每个分类)都生成一个独立的关联条目,再按属性值分组。这样View层只需要处理转换后的分组数据即可。
步骤1:定义分组辅助模型
创建一个BookGroupItem模型,用来关联书籍和对应的分组键:
struct BookGroupItem: Identifiable { let id = UUID() // 每个条目唯一ID let book: Book let groupKey: String // 分组用的键(如作者名、分类名) }
步骤2:在ViewModel中处理分组逻辑
修改ViewModel,根据选中的筛选类型,生成展开后的分组数据:
class BookViewModel: ObservableObject { @Published var arrayBooks: [Book] = [] @Published var selectedFilter: SortFilter = .title // 按当前筛选规则生成分组数据 var groupedItems: [String: [BookGroupItem]] { switch selectedFilter { case .title: return Dictionary( grouping: arrayBooks.map { BookGroupItem(book: $0, groupKey: $0.title) }, by: { $0.groupKey } ) case .author: // 展开每本书的所有作者,生成多个条目 let allItems = arrayBooks.flatMap { book in book.authors.map { author in BookGroupItem(book: book, groupKey: author.name) } } return Dictionary(grouping: allItems, by: { $0.groupKey }) case .publisher: return Dictionary( grouping: arrayBooks.map { BookGroupItem(book: $0, groupKey: $0.publisher) }, by: { $0.groupKey } ) case .kind: // 展开每本书的所有分类,生成多个条目 let allItems = arrayBooks.flatMap { book in book.kinds.map { kind in BookGroupItem(book: book, groupKey: kind.rawValue) } } return Dictionary(grouping: allItems, by: { $0.groupKey }) case .category: // 根据你的category属性逻辑调整,类似上述格式 return Dictionary( grouping: arrayBooks.map { BookGroupItem(book: $0, groupKey: $0.category) }, by: { $0.groupKey } ) } } // 排序后的分组键,保证分组列表有序 var sortedGroupKeys: [String] { groupedItems.keys.sorted() } // 调整删除逻辑:通过分组条目找到原始书籍并删除 func deleteRow(indexSet: IndexSet, groupKey: String) { guard let items = groupedItems[groupKey], let index = indexSet.first else { return } let targetItem = items[index] if let bookIndex = arrayBooks.firstIndex(where: { $0.id == targetItem.book.id }) { arrayBooks.remove(at: bookIndex) } } }
步骤3:重构View层代码
View层直接遍历转换后的分组数据,无需复杂判断:
struct BookListView: View { @StateObject var viewModel = BookViewModel() var body: some View { NavigationStack { List { ForEach(viewModel.sortedGroupKeys, id: \.self) { groupKey in Section(header: Text(groupKey)) { ForEach(viewModel.groupedItems[groupKey] ?? []) { item in NavigationLink { BookDetailView(book: item.book) } label: { BookVolumeRow(book: item.book) } } .onDelete { indexSet in viewModel.deleteRow(indexSet: indexSet, groupKey: groupKey) } } } } .navigationTitle("书籍列表") // 添加筛选器切换控件(可选) .toolbar { Picker("筛选方式", selection: $viewModel.selectedFilter) { ForEach(SortFilter.allCases) { filter in Text(filter.rawValue.capitalized) } } .pickerStyle(.segmented) } } } }
关键说明
- flatMap的作用:它会把每本书的多属性(如多个作者)展开成多个
BookGroupItem,这样每个属性值都能成为独立的分组键,书籍也会出现在每个对应的分组下。 - 删除逻辑调整:因为现在显示的是转换后的条目,删除时需要先找到该条目对应的原始书籍,再从
arrayBooks中删除。 - 枚举处理:如果
kinds是自定义枚举,确保它实现了RawRepresentable(比如String类型的rawValue),这样才能作为分组键的字符串。
内容的提问来源于stack exchange,提问作者Grovnyk
相关产品推荐
相关产品推荐

