SwiftUI 统计资源文件夹PDF总数并实现All选项下按子目录分组展示
SwiftUI 资源目录PDF统计与分组展示实现
核心逻辑校准
当前实现的核心偏差在于:All是页面交互层面的虚拟选项,并非Bundle中真实存在的资源子目录,直接拼接路径查找会返回空结果;同时PDF总数统计逻辑需要明确遍历范围,不能直接依赖递归扫描根目录(受Bundle资源打包规则影响容易出现漏扫)。
功能落地实现
1. 产品维度PDF总数统计
通过遍历ProductModel中维护的真实应用子目录列表(排除虚拟的"All"选项),逐个累加每个目录下的PDF数量即可:
/// 统计指定产品下所有应用子目录的PDF总数量 /// - Parameter product: 产品信息模型 /// - Returns: PDF总个数 func totalPDFCount(for product: ProductModel) -> Int { // 过滤非实际目录的虚拟选项 let realDirectories = product.application.filter { $0 != "All" } return realDirectories.reduce(0) { sum, dir in let dirPath = "\(product.headerTitle)/\(dir)" let pdfList = Bundle.main.urls(forResourcesWithExtension: "pdf", subdirectory: dirPath) ?? [] return sum + pdfList.count } }
说明:采用遍历已知目录的方式比递归扫描产品根目录更稳定,因为ProductModel中维护的目录列表和实际资源结构是一一对应的,不会因为Xcode打包时的目录扁平化规则出现文件漏读。
2. 选中"All"选项时按子目录分组展示
对二级视图做分支判断:当选中项为"All"时,预先整理所有真实子目录的PDF映射关系,再通过SwiftUI原生的List + Section实现带组头的分组列表;选中具体应用场景时,保留原有单目录的渲染逻辑即可。
改造后的完整视图代码:
struct ProductTab5NoteRow: View { @ObservedObject var application: selectedApplication @State private var pdfURL: URL? var product: ProductModel // 预整理全量PDF的目录映射关系:[子目录名: 对应PDF URL数组] private var allPDFGroupedByDirectory: [String: [URL]] { let realDirectories = product.application.filter { $0 != "All" } var groupMap: [String: [URL]] = [:] realDirectories.forEach { dir in let dirPath = "\(product.headerTitle)/\(dir)" let pdfs = Bundle.main.urls(forResourcesWithExtension: "pdf", subdirectory: dirPath) ?? [] groupMap[dir] = pdfs } return groupMap } var body: some View { List { if application.selectedApplication == "All" { // 按字母序遍历分组,用Section渲染组头 ForEach(allPDFGroupedByDirectory.keys.sorted(), id: \.self) { dirName in Section(header: Text(dirName)) { if let pdfs = allPDFGroupedByDirectory[dirName], !pdfs.isEmpty { ForEach(pdfs, id: \.self) { pdf in Button { pdfURL = pdf } label: { // 用deletingPathExtension去掉后缀,比手写dropLast(4)更鲁棒 Text(pdf.deletingPathExtension().lastPathComponent) } } } else { Text("当前分类下暂无PDF文件") .font(.caption) .foregroundColor(.secondary) } } } } else { // 原有单目录渲染逻辑 let targetPath = "\(product.headerTitle)/\(application.selectedApplication)" let pdfs = Bundle.main.urls(forResourcesWithExtension: "pdf", subdirectory: targetPath) ?? [] ForEach(pdfs, id: \.self) { pdf in Button { pdfURL = pdf } label: { VStack(alignment: .leading, spacing: 4) { Text(pdf.deletingPathExtension().lastPathComponent) Text(application.selectedApplication) .font(.caption) .foregroundColor(.secondary) } } } } } // 接入PDF预览逻辑,PDFPreviewView为项目内已实现的预览组件 .sheet(item: $pdfURL) { url in PDFPreviewView(fileURL: url) } } }
实现注意事项
- 始终将虚拟交互选项"All"和真实资源目录做隔离,不要将其拼接到Bundle的查找路径中
- 替换原有
dropLast(4)的文件名处理逻辑,采用deletingPathExtension()可以兼容带多后缀的文件名 - 可将PDF统计、分组获取的逻辑抽到独立的资源管理工具类中,避免视图层重复编写查找逻辑
内容的提问来源于stack exchange,提问作者spoax
相关产品推荐
相关产品推荐

