You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 02:17:54