如何在Swift中访问应用程序包中包含的文件?

54

我知道有一些关于这个问题的问题,但它们都是关于Objective-C的。

在实际的 iPhone 上,如何使用 Swift 访问包含在我的应用程序中的.txt文件?我想能够从中读取和写入。这里是我的项目文件,如果您想看一下。如有必要,我可以补充细节。


2
“我想能够从中读取和写入。” 但是不行。当安装在设备上时,应用程序包是只读的。您可以读取应用程序包中的文件,但无法向其写入。 - rob mayoff
8个回答

69

仅通过在应用程序包中搜索资源即可

var filePath = NSBundle.mainBundle().URLForResource("file", withExtension: "txt")

但是你不能向它写入内容,因为它位于应用程序资源目录中,你必须在文档目录中创建它才能向其写入内容。

var documentsDirectory: NSURL?
var fileURL: NSURL?

documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last!
fileURL = documentsDirectory!.URLByAppendingPathComponent("file.txt")

if (fileURL!.checkResourceIsReachableAndReturnError(nil)) {
    print("file exist")
}else{
    print("file doesnt exist")
    NSData().writeToURL(fileURL!,atomically:true)
}

现在你可以从fileURL访问它。

编辑 - 2018年8月28日

这是在Swift 4.2中如何实现的。

var filePath = Bundle.main.url(forResource: "file", withExtension: "txt")

将其创建在文档目录中

if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last {
   let fileURL = documentsDirectory.appendingPathComponent("file.txt")
   do {
       if try fileURL.checkResourceIsReachable() {
           print("file exist")
       } else {
           print("file doesnt exist")
           do {
            try Data().write(to: fileURL)
           } catch {
               print("an error happened while creating the file")
           }
       }
   } catch {
       print("an error happened while checking for the file")
   }
}

你所说的“它”,是指复制到文档目录中的可写文件吗? - atirit
现在您可以从fileURL访问它,它指的是已创建的文件,而不是包含的文件,并且它是可写的。 - Karim H
该文件没有被复制,而是一个新的空文件。 - Karim H
你能更具体地说明如何在文档目录中创建目录吗?这个目录的名称是什么?是Document还是Documents?谢谢。 - rony
更具体一些?答案是关于如何访问文件而不是如何创建目录的。 - Karim H

18

Swift 3,基于Karim的答案

读取文件

您可以通过应用程序包中的资源来读取文件:

let fileURL = Bundle.main.url(forResource:"filename", withExtension: "txt")

写作

然而,你不能在那里直接写作。你需要创建一份副本,最好放在文档目录中:

func makeWritableCopy(named destFileName: String, ofResourceFile originalFileName: String) throws -> URL {
    // Get Documents directory in app bundle
    guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last else {
        fatalError("No document directory found in application bundle.")
    }

    // Get URL for dest file (in Documents directory)
    let writableFileURL = documentsDirectory.appendingPathComponent(destFileName)

    // If dest file doesn’t exist yet
    if (try? writableFileURL.checkResourceIsReachable()) == nil {
        // Get original (unwritable) file’s URL
        guard let originalFileURL = Bundle.main.url(forResource: originalFileName, withExtension: nil) else {
            fatalError("Cannot find original file “\(originalFileName)” in application bundle’s resources.")
        }

        // Get original file’s contents
        let originalContents = try Data(contentsOf: originalFileURL)

        // Write original file’s contents to dest file
        try originalContents.write(to: writableFileURL, options: .atomic)
        print("Made a writable copy of file “\(originalFileName)” in “\(documentsDirectory)\\\(destFileName)”.")

    } else { // Dest file already exists
        // Print dest file contents
        let contents = try String(contentsOf: writableFileURL, encoding: String.Encoding.utf8)
        print("File “\(destFileName)” already exists in “\(documentsDirectory)”.\nContents:\n\(contents)")
    }

    // Return dest file URL
    return writableFileURL
}

示例用法:

let stuffFileURL = try makeWritableCopy(named: "Stuff.txt", ofResourceFile: "Stuff.txt")
try "New contents".write(to: stuffFileURL, atomically: true, encoding: String.Encoding.utf8)

12

关于在Swift 4中使用此代码的快速更新:

Bundle.main.url(forResource:"YourFile", withExtension: "FileExtension")

以下内容已更新,以便将文件写出:

var myData: Data!

func checkFile() {
    if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last {
        let fileURL = documentsDirectory.appendingPathComponent("YourFile.extension")
        do {
            let fileExists = try fileURL.checkResourceIsReachable()
            if fileExists {
                print("File exists")
            } else {
                print("File does not exist, create it")
                writeFile(fileURL: fileURL)
            }
        } catch {
            print(error.localizedDescription)
        }
    }
}

func writeFile(fileURL: URL) {
    do {
        try myData.write(to: fileURL)
    } catch {
        print(error.localizedDescription)
    }
}

这个例子并不是最灵活的,但稍微改动一下,你就可以轻松地传入自己的文件名、扩展名和数据值。


9

属性包装器 - 获取并转换为正确的数据类型

这个简单的包装器可以帮助你以最干净的方式从任何捆绑包中加载任何文件:

@propertyWrapper struct BundleFile<DataType> {
    let name: String
    let type: String
    let fileManager: FileManager = .default
    let bundle: Bundle = .main
    let decoder: (Data) -> DataType

    var wrappedValue: DataType {
        guard let path = bundle.path(forResource: name, ofType: type) else { fatalError("Resource not found: \(name).\(type)") }
        guard let data = fileManager.contents(atPath: path) else { fatalError("Can not load file at: \(path)") }
        return decoder(data)
    }
}

使用方法:

@BundleFile(name: "avatar", type: "jpg", decoder: { UIImage(data: $0)! } )
var avatar: UIImage

您可以定义任何解码器来满足您的需求。


8

在 Swift 5.1 中从 Bundle 中获取文件

//For Video File
let stringPath = Bundle.main.path(forResource: "(Your video file name)", ofType: "mov")

let urlVideo = Bundle.main.url(forResource: "Your video file name", withExtension: "mov")

2

捆绑包可以编写。您可以使用Bundle.main.path将文件添加到Copy Bundles Resource中以覆盖文件。

Project -> Target -> Build Pharse -> Copy Bundles Resource

最初的回答已翻译完毕,如有需要请参考上述内容。


2

Bundle是只读的。您可以使用NSBundle.mainBundle().pathForResource以只读方式访问文件,但如果需要读写访问权限,则需要将文档复制到Documents文件夹或tmp文件夹中。


0

我必须使用另一个包中的文件。所以,以下代码适用于我。当你使用框架时需要这样做。

let bundle = Bundle(for: ViewController.self)
let fileName = bundle.path(forResource: "fileName", ofType: "json")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接