Bundle.main.paths(forResourcesOfType:inDirectory) 在iOS上不起作用
我正在创建一个多平台的SwiftUI应用。
经过一番初期的摸索之后,我终于创建了一个文件目录并把它们加入到应用的bundle。在启动时,我尝试使用Bundle的 Bundle.main.paths(forResourcesOfType:) 函数来查找这些文件并打开它们。
Mac版本的应用能够把文件找到得很顺利。将同样的代码在iOS版本上运行时,返回的是一个空的文件路径数组。
我可以在Finder中定位到模拟器版本应用的bundle,使用“显示包内容”打开它,bundle中的文件夹清晰可见、里面的文件也写好了。然而,Bundle.main.paths(forResourcesOfType:) 返回一个空列表。
我最初尝试获取bundle的资源路径,将我的文件夹名追加到该路径,并用FileManager来获取文件列表,但失败了,于是改用Bundle方法。
这两种方法在macOS上都能工作,在iOS上都失败。
是不是有什么我没注意到的沙箱问题?
以下是我的代码:
static var scopeTemplates: [ScopeTemplate] = {
let fileManager = FileManager.default
let folderName = "Kaleidoscope_Templates"
do {
if let bundlePath = Bundle.main.resourcePath {
print("bundlePath = \(bundlePath)")
} else {
print("Can't resolve bundle path")
}
// old path-based code.
//let kaleidoscopeTemplateFileNames = Bundle.main.paths(forResourcesOfType: "json", inDirectory: folderName)
//let kaleidoscopeTemplateURLs = kaleidoscopeTemplateFileNames.map { URL(filePath: $0) }
guard let kaleidoscopeTemplateURLs = Bundle.main.urls(forResourcesWithExtension: "json", subdirectory: folderName) else {
fatalError("Can't find folder \(folderName)")
}
let decoder = JSONDecoder()
var scopeTemplates = [ScopeTemplate]()
for aFileURL in kaleidoscopeTemplateURLs {
print("filename = \(aFileURL.path)")
let data = try Data(contentsOf: aFileURL)
let aScopeTemplate = try decoder.decode(ScopeTemplate.self, from: data)
scopeTemplates.append(aScopeTemplate)
}
return scopeTemplates.sorted() { lhv, rhv in
return lhv.index < rhv.index
}
} catch {
fatalError("Error \(error) reading template files")
}
}()
(这一版代码不会崩溃,也不会记录错误,但返回的是空数组。尝试在iOS上用FileManager获取该目录中的文件时会抛出异常,不过该版本在macOS上也能工作。)
另外请注意,iOS与 macOS的 bundle结构看起来完全相同。我在模拟器中用Finder的“前往文件夹...”菜单命令打开bundle中的目录,看到的结构和预期完全一致。
我刚刚创建了一个最小可复现的项目并在Github上发布:
https://github.com/DuncanMC/BundleFiles.git
那段代码是这样的:
static var scopeTemplates: [ScopeTemplate] = {
let fileManager = FileManager.default
let folderName = "Kaleidoscope_Templates"
do {
if let bundlePath = Bundle.main.resourcePath {
print("bundlePath = \(bundlePath)")
} else {
print("Can't resolve bundle path")
}
guard let kaleidoscopeTemplateURLs = Bundle.main.urls(forResourcesWithExtension: "json", subdirectory: folderName) else {
fatalError("Can't find folder \(folderName)")
}
print("Directory \(folderName) in bundle resources contains \(kaleidoscopeTemplateURLs.count) files.")
let decoder = JSONDecoder()
var scopeTemplates = [ScopeTemplate]()
for aFileURL in kaleidoscopeTemplateURLs {
print("filename = \(aFileURL.path)")
let data = try Data(contentsOf: aFileURL)
let aScopeTemplate = try decoder.decode(ScopeTemplate.self, from: data)
scopeTemplates.append(aScopeTemplate)
}
return scopeTemplates.sorted() { lhv, rhv in
return lhv.index < rhv.index
}
} catch {
fatalError("Error \(error) reading template files")
}
}()
(Github上的项目包含Xcode工程、充满文件的文件夹等。它被设置为一个SwiftUI多平台项目,与我现在努力解决的真实应用程序一样。)
在iOS模拟器中运行时,它会按预期记录bundle路径:
bundlePath = /Users/duncan/Library/Developer/CoreSimulator/Devices/EABDE8A8-0CAC-4D5F-81EF-FB03DC809EE5/data/Containers/Bundle/Application/8EA76044-8F14-4783-A45B-423A35F1EB4F/BundleFiles.app
然后输出
Directory Kaleidoscope_Templates in bundle resources contains 0 files.
在我的Mac上运行同样的代码时,会加载并解码bundle中的4 个文件,正如应当的那样。
在Mac上运行时,bundle路径以Contents/Resources结尾;而在iOS上,路径是应用程序bundle的顶层目录。(我相信这是预期的行为。)
解决方案
问题解决,我感觉自己像个白痴。我的项目中有一个名为 "Kaleidoscope_templates" 的文件夹,但代码却试图使用一个名为 "Kaleidoscope_Templates"(大写字母T)的文件夹。
Matt增补了一个关于文件夹名大小写不匹配的回答,然后又删除了。这个不匹配其实是不同的,但确实是原因。
我现在想起来,macOS的文件系统不是区分大小写的,但iOS是大小写敏感的。
@matt,如果你能把回答找回来我就能接受。谢谢!我盯着那段愚蠢的代码看得太久了。(我的大脑对大小写不敏感,所以很容易错过这一点,但本该三次检查它。)
再一次感谢!