How do I solve the strange "filehandle" problem? [duplicate] - swift

I know there are a few questions pertaining to this, but they're in Objective-C.
How can I access a .txt file included in my app using Swift on an actual iPhone? I want to be able to read and write from it. Here are my project files if you want to take a look. I'm happy to add details if necessary.

Simply by searching in the app bundle for the resource
var filePath = NSBundle.mainBundle().URLForResource("file", withExtension: "txt")
However you can't write to it because it is in the app resources directory and you have to create it in the document directory to write to it
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)
}
now you can access it from fileURL
EDIT - 28 August 2018
This is how to do it in Swift 4.2
var filePath = Bundle.main.url(forResource: "file", withExtension: "txt")
To create it in the document directory
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")
}
}

Swift 3, based on Karim’s answer.
Reading
You can read files included in an app’s bundle through the bundle’s resource:
let fileURL = Bundle.main.url(forResource:"filename", withExtension: "txt")
Writing
However, you can’t write there. You will need to create a copy, preferably in the Documents directory:
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
}
Example usage:
let stuffFileURL = try makeWritableCopy(named: "Stuff.txt", ofResourceFile: "Stuff.txt")
try "New contents".write(to: stuffFileURL, atomically: true, encoding: String.Encoding.utf8)

Just a quick update for using this code with Swift 4:
Bundle.main.url(forResource:"YourFile", withExtension: "FileExtension")
And the following has been updated to account for writing the file out:
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)
}
}
This particular example is not the most flexible, but with a little bit of work you can easily pass in your own file names, extensions and data values.

🎁 Property Wrapper - Fetch and convert to correct data type
This simple wrapper helps you to load any file from any bundle in a cleanest way:
#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)
}
}
Usage:
#BundleFile(name: "avatar", type: "jpg", decoder: { UIImage(data: $0)! } )
var avatar: UIImage
You can define any decoder to match your needs

Get File From Bundle in Swift 5.1
//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")

Bundles are read only. You can use NSBundle.mainBundle().pathForResource to access the file as read-only, but for read-write access you need to copy your document to Documents folder or tmp folder.

Bundles can be written. You can use Bundle.main.path to overwrite file by adding it into Copy Bundles Resource.

I have to use a file from another bundle. So, following code worked for me. Needful when you work with a frameworks.
let bundle = Bundle(for: ViewController.self)
let fileName = bundle.path(forResource: "fileName", ofType: "json")

Related

Unable to save multiple text files in same folder (MacOS Swift Application)

I am trying to save multiple text files in the same folder. So far I can save one text file into a generated folder in my document directory, but I cannot save more than one text file in that same folder.
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
let docURL = URL(string: documentsDirectory)!
let dataPath = docURL.appendingPathComponent("User_Notes")
// this is the folder name
if !FileManager.default.fileExists(atPath: dataPath.path) {
do {
try
FileManager.default.createDirectory(atPath: dataPath.path, withIntermediateDirectories: true, attributes: nil)
// try stringToWrite.write(to: &dataPath)
try stringToWrite.write(toFile: "\(dataPath)/\(userNotes)).txt", atomically: true, encoding: String.Encoding.utf8)
} catch {
print(error.localizedDescription)
}
}
if error != nil {
print("Error saving user data.")
}
}

Write a bundle as a file

I want to write a bundle I've dragged and dropped into my project folder into a temporary file.
Previously I used fileManager.copyItem but it deleted the original.
Original code
func copyPackagedBundleToDocuments(withDestinationName dest: String) {
if let bundlePath = Bundle.main.path(forResource: embeddedBundle, ofType: nil) {
let fileManager = FileManager.default
let destPath = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!
// applicationSupportDirectory is not created by default in the sandbox, and therefore we need to make sure that it exists!
let urls = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)
if let applicationSupportURL = urls.last {
do {
try fileManager.createDirectory(at: applicationSupportURL, withIntermediateDirectories: true, attributes: nil)
} catch {
print(error)
}
}
let fullDestPath = NSURL(fileURLWithPath: destPath + "/" + dest)
do {
try fileManager.copyItem(atPath: bundlePath, toPath: fullDestPath.path!)
} catch {
print(error)
}
}
}
This tells me the paths etc. are fine, since it does copy the file.
Now I want an atomic copy, so want to use Data(contentsOfFile)
So I rewrote everything:
func copyPackagedBundleToDocuments(withDestinationName dest: String) {
if let bundlePath = Bundle.main.path(forResource: embeddedBundle, ofType: nil) {
let fileManager = FileManager.default
let destPath = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!
// applicationSupportDirectory is not created by default in the sandbox, and therefore we need to make sure that it exists!
let urls = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)
if let applicationSupportURL = urls.last {
do {
try fileManager.createDirectory(at: applicationSupportURL, withIntermediateDirectories: true, attributes: nil)
} catch {
print(error)
}
}
let urlPath = URL(fileURLWithPath: destPath + "/" + dest)
let dta = try? Data(contentsOf: URL(fileURLWithPath: bundlePath) )
do {
try dta.write(to: urlPath , options: .atomic)
} catch {
print ("error \(error)")
}
}
}
However the data (dta) here is nil!
I think this is because my bundle location (as follows) ends in a backslash /:
file:///Users/user/Library/Developer/CoreSimulator/Devices/37A85B0B-F2B7-4A0C-BAA7-E05A831FFAE0/data/Containers/Bundle/Application/AD665103-E256-4F6C-8248-B62D8FF9FDC8/LocalBundle.app/Bundle.bundle/
and I guess that copyItem can treat the bundle as a single file, but writing the bundle to data is somehow not possible (the path is correct, since the code using copyItem works).
For clarity the following
if let bundleURL = Bundle.main.url(forResource: embeddedBundle, withExtension: nil) {
print ( try? Data(contentsOf: bundleURL ) )
}
prints nil to the console, validating the url for the bundle.
How can I write my bundle to a file, atomically?

How to first download audio files from URLs and then play all

I have got multiple audio files on server.
I want to download all audio files first, and then when all are downloaded, i need to play them one after another.
What will be the best approach to achieve this?
Thanks!
if let audioUrl = URL(string: "http://freetone.org/ring/stan/iPhone_5-Alarm.mp3") {
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// lets create your destination file url
let destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
print(destinationUrl)
// to check if it exists before downloading it
if FileManager.default.fileExists(atPath: destinationUrl.path) {
print("The file already exists at path")
// if the file doesn't exist
} else {
// you can use NSURLSession.sharedSession to download the data asynchronously
URLSession.shared.downloadTask(with: audioUrl, completionHandler: { (location, response, error) -> Void in
guard let location = location, error == nil else { return }
do {
// after downloading your file you need to move it to your destination url
try FileManager.default.moveItem(at: location, to: destinationUrl)
print("File moved to documents folder")
} catch let error as NSError {
print(error.localizedDescription)
}
}).resume()
}
}

Trying to clear document folder Swift

I use following method to add file (download) to document directory:
static func downloadFileWithLink(linkString : String){
// Create destination URL
let documentsUrl:URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL!
let destinationFileUrl = documentsUrl.appendingPathComponent("downloadedFile")
//Create URL to the source file you want to download
let fileURL = URL(string: linkString)
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL!)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
} catch (let writeError) {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: %#", error?.localizedDescription);
}
}
task.resume()
}
It simply download file to document folder in sandbox. I looked for method that delete files in documents folder, and tried following:
static func deleteFiledInDocDirectory(){
let fileManager = FileManager.default
let tempFolderPath = NSTemporaryDirectory()
do {
let filePaths = try fileManager.contentsOfDirectory(atPath: tempFolderPath)
for filePath in filePaths {
try fileManager.removeItem(atPath: tempFolderPath + filePath)
}
} catch {
print("Could not clear temp folder: \(error)")
}
}
However, when i inspect sandbox, downloaded file is still here. How to delete it?
In deleteFiledInDocDirectory() you're using NSTemporaryDirectory instead of the documents directory that you originally saved the file to.
Change tempFolderPath to be set using following:
guard let tempFolderPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
return // documents directory not found for some reason
}
The full method:
static func deleteFiledInDocDirectory(){
guard let tempFolderPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first?.absoluteString else {
return // documents directory not found for some reason
}
let fileManager = FileManager.default
do {
let filePaths = try fileManager.contentsOfDirectory(atPath: tempFolderPath)
for filePath in filePaths {
try fileManager.removeItem(atPath: tempFolderPath + filePath)
}
} catch {
print("Could not clear temp folder: \(error)")
}
}

How can I read a file in a swift playground

Im trying to read a text file using a Swift playground with the following
let dirs : String[]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true) as? String[]
if (dirs != nil) {
let directories:String[] = dirs!;
let dir = directories[0]; //documents directory
let path = dir.stringByAppendingPathComponent(file);
//read
let content = String.stringWithContentsOfFile(path, encoding: NSUTF8StringEncoding, error: nil)
}
However this fails with no error. It seems the first line stops the playground from outputting anything below
You can also put your file into your playground's resources. To do this: show Project Navigator with CMD + 1. Drag and drop your file into the resources folder. Then read the file:
On Xcode 6.4 and Swift 1.2:
var error: NSError?
let fileURL = NSBundle.mainBundle().URLForResource("Input", withExtension: "txt")
let content = String(contentsOfURL: fileURL!, encoding: NSUTF8StringEncoding, error: &error)
On Xcode 7 and Swift 2:
let fileURL = NSBundle.mainBundle().URLForResource("Input", withExtension: "txt")
let content = try String(contentsOfURL: fileURL!, encoding: NSUTF8StringEncoding)
On Xcode 8 and Swift 3:
let fileURL = Bundle.main.url(forResource: "Input", withExtension: "txt")
let content = try String(contentsOf: fileURL!, encoding: String.Encoding.utf8)
If the file has binary data, you can use NSData(contentsOfURL: fileURL!) or Data(contentsOf: fileURL!) (for Swift 3).
While the answer has been supplied for a quick fix, there is a better solution.
Each time the playground is opened it will be assigned a new container. This means using the normal directory structure you would have to copy the file you want into the new container every time.
Instead, inside the container there is a symbolic link to a Shared Playground Data directory (/Users/UserName/Documents/Shared Playground Data) which remains when reopening the playground, and can be accessed from multiple playgrounds.
You can use XCPlayground to access this shared folder.
import XCPlayground
let path = XCPlaygroundSharedDataDirectoryURL.appendingPathComponent("foo.txt")
The official documentation can be found here: XCPlayground Module Reference
Cool post on how to organize this directory per-playground: Swift, Playgrounds, and XCPlayground
UPDATE: For swift 4.2 use playgroundSharedDataDirectory. Don't need to import anything.
Looks like:
let path = playgroundSharedDataDirectory.appendingPathComponent("file")
1. Access a file that is located in the Resources folder of your Playground
With Swift 3, Bundle has a method called url(forResource:withExtension:). url(forResource:withExtension:) has the following declaration:
func url(forResource name: String?, withExtension ext: String?) -> URL?
Returns the file URL for the resource identified by the specified name and file extension.
You can use url(forResource:withExtension:) in order to read the content of a json file located in the Resources folder of an iOS or Mac Playground:
import Foundation
do {
guard let fileUrl = Bundle.main.url(forResource: "Data", withExtension: "json") else { fatalError() }
let data = try Data(contentsOf: fileUrl)
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
You can use url(forResource:withExtension:) in order to read the content of a text file located in the Resources folder of an iOS or Mac Playground:
import Foundation
do {
guard let fileUrl = Bundle.main.url(forResource: "Text", withExtension: "txt") else { fatalError() }
let text = try String(contentsOf: fileUrl, encoding: String.Encoding.utf8)
print(text)
} catch {
print(error)
}
As an alternative to let image = UIImage(named: "image"), you can use url(forResource:withExtension:) in order to access an image located in the Resources folder of an iOS Playground:
import UIKit
do {
guard let fileUrl = Bundle.main.url(forResource: "Image", withExtension: "png") else { fatalError() }
let data = try Data(contentsOf: fileUrl)
let image = UIImage(data: data)
} catch {
print(error)
}
2. Access a file that is located in the ~/Documents/Shared Playground Data folder of your computer
With Swift 3, PlaygroundSupport module provides a global constant called playgroundSharedDataDirectory. playgroundSharedDataDirectory has the following declaration:
let playgroundSharedDataDirectory: URL
The path to the directory containing data shared between all playgrounds.
You can use playgroundSharedDataDirectory in order to read the content of a json file located in the ~/Documents/Shared Playground Data folder of your computer from an iOS or Mac Playground:
import Foundation
import PlaygroundSupport
do {
let fileUrl = PlaygroundSupport.playgroundSharedDataDirectory.appendingPathComponent("Data.json")
let data = try Data(contentsOf: fileUrl)
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
You can use playgroundSharedDataDirectory in order to read the content of a text file located in the ~/Documents/Shared Playground Data folder of your computer from an iOS or Mac Playground:
import Foundation
import PlaygroundSupport
do {
let fileUrl = PlaygroundSupport.playgroundSharedDataDirectory.appendingPathComponent("Text.txt")
let text = try String(contentsOf: fileUrl, encoding: String.Encoding.utf8)
print(text)
} catch {
print(error)
}
You can use playgroundSharedDataDirectory in order to access an image located in the ~/Documents/Shared Playground Data folder of your computer from an iOS Playground:
import UIKit
import PlaygroundSupport
do {
let fileUrl = PlaygroundSupport.playgroundSharedDataDirectory.appendingPathComponent("Image.png")
let data = try Data(contentsOf: fileUrl)
let image = UIImage(data: data)
} catch {
print(error)
}
Swift 3 (Xcode 8)
The code below works in both iOS and macOS playgrounds. The text file ("MyText.txt" in this example) must be in the Resources directory of the playground. (Note: You may need to open the navigator window to see the directory structure of your playground.)
import Foundation
if let fileURL = Bundle.main.url(forResource:"MyText", withExtension: "txt")
{
do {
let contents = try String(contentsOf: fileURL, encoding: String.Encoding.utf8)
print(contents)
} catch {
print("Error: \(error.localizedDescription)")
}
} else {
print("No such file URL.")
}
This works for me. The only thing I changed was to be explicit about the file name (which is implied in your example) - perhaps you have a typo in the off-screen definition of the "file" variable?
let dirs = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true) as? [String]
let file = "trial.txt" // My change to your code - yours is presumably set off-screen
if let directories = dirs {
let dir = directories[0]; //documents directory
let path = dir.stringByAppendingPathComponent(file);
//read
let content = NSString(contentsOfFile: path, usedEncoding: nil, error: nil)
// works...
}
Update Swift 4.2
As #raistlin points out, this would now be
let dirs = NSSearchPathForDirectoriesInDomains(
FileManager.SearchPathDirectory.documentDirectory,
FileManager.SearchPathDomainMask.userDomainMask,
true)
or, more tersely:
let dirs = NSSearchPathForDirectoriesInDomains(.documentDirectory,
.userDomainMask, true)
Select the .playground file.
Open Utility inspector, In the playground press opt-cmd-1 to open the File Inspector. You should see the playground on the right. If you don't have it selected, press cmd-1 to open the Project Navigator and click on the playground file.
Under 'Resource Path' in Playground Settings choose 'Relative To Playground' and platform as OSX.
On Mavericks with Xcode 6.0.1 you can read using iOS platform too.
import UIKit
let dirs : [String]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true) as? [String]
let myDir = "/Shared Playground Data"
let file = "README.md" // My change to your code - yours is presumably set off-screen
if (dirs != nil) {
let directories:[String] = dirs!;
let dir = directories[0] + myDir; // iOS playground documents directory
let path = dir.stringByAppendingPathComponent(file);
//read
let content = String.stringWithContentsOfFile(path, encoding: NSUTF8StringEncoding, error: nil)
// works...
println(content!)
}
Remember, you need to create a directory called "Shared Playground Data" in your Documents directory. Im my case I used this command: mkdir "/Users/joao_parana/Documents/Shared Playground Data" and put there my file README.md
String.stringWithContentsOfFile is DEPRECATED and doesn't work anymore with Xcode 6.1.1
Create your documentDirectoryUrl
let documentDirectoryUrl = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first! as NSURL
To make sure the file is located there you can use the finder command Go To Folder e copy paste the printed documentDirectoryUrl.path there
println(documentDirectoryUrl.path!)
// should look like this: /Users/userName/Library/Containers/com.apple.dt.playground.stub.OSX.PLAYGROUNDFILENAME-5AF5B25D-D0D1-4B51-A297-00015EE97F13/Data/Documents
Just append the file name to the folder url as a path component
let fileNameUrl = documentDirectoryUrl.URLByAppendingPathComponent("ReadMe.txt")
var fileOpenError:NSError?
Check if the file exists before attempting to open it
if NSFileManager.defaultManager().fileExistsAtPath(fileNameUrl.path!) {
if let fileContent = String(contentsOfURL: fileNameUrl, encoding: NSUTF8StringEncoding, error: &fileOpenError) {
println(fileContent) // prints ReadMe.txt contents if successful
} else {
if let fileOpenError = fileOpenError {
println(fileOpenError) // Error Domain=NSCocoaErrorDomain Code=XXX "The file “ReadMe.txt” couldn’t be opened because...."
}
}
} else {
println("file not found")
}
I was unable to read a file with ease in playground and ended up just creating a command line app in Xcode. This seemed to work for me very well.
The other answers, relying on "playgroundSharedDataDirectory" never works for me, especially if using an iOS playground.
let documentsDirectoryShareURL = PlaygroundSupport.playgroundSharedDataDirectory.absoluteURL
let fileManager = FileManager()
try? fileManager.copyItem(at: URL(fileURLWithPath: "/Users/rufus/Documents/Shared Playground Data/"), to: documentsDirectoryShareURL)
I just do the above now. I can populate my documents/shared folder, and it is just manually automatically copied to the playgrounds documents directory.
My code will not overwrite files that exist there. You could enhance this if you need it to look at file timestamps and then copy if necessary etc.
Swift 5.7.1 - Xcode 14.1
func readFile() -> [String] {
if let fileURL = Bundle.main.url(forResource: "File", withExtension: "txt") {
do {
let content = try String(contentsOf: fileURL)
var x = content.components(separatedBy: "\n")
x.removeAll { data in
data.isEmpty
}
return x
} catch {
print(error)
}
}
return [String]()
}
//Usage:
let input = readFile()