Possible to write swift println logs into file too? - swift

Is it an easy way to write logs into a text file too? I need a crash log to analyse when something went wrong. But I already use println al around in the code.

Use String.writeToFile(<#path: String#>, atomically: <#Bool#>, encoding: <#NSStringEncoding#>, error: <#NSErrorPointer#>)
You could add this:
#if DEBUG
func println(s:String) {
var error:NSError? = nil
let path = "/Users/<me>/dump.txt"
var dump = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: nil)!
"\(dump)\n\(s)".writeToFile(path, atomically:true, encoding:NSUTF8StringEncoding, error:&error)
}
#endif
See the #if DEBUG answer on SO how to use this compiler flag.

For swift 3, change Thomas Killan's code like this
func println(s:String) {
let path = "/Users/<me>/dump.txt"
var dump = ""
if FileManager.default.fileExists(atPath: path) {
dump = try! String(contentsOfFile: path, encoding: String.Encoding.utf8)
}
do {
// Write to the file
try "\(dump)\n\(s)".write(toFile: path, atomically: true, encoding: String.Encoding.utf8)
} catch let error as NSError {
print("Failed writing to log file: \(path), Error: " + error.localizedDescription)
}
}

Unfortunately, using a println()-based solution will not result in output being captured by the Apple System Log (ASL).
The ASL is the logging facility provided by the Mac OS and iOS that is used by NSLog() (and on a Mac is visible through the Console application). Because NSLog() uses ASL, log entries recorded by NSLog() will be visible through the device console. Messages logged through println() will not be captured in ASL, and as a result, provide no opportunity to go back to the console for diagnostic purposes after something has happened.
The CleanroomLogger open-source project provides an extensible Swift API that you can use to do what you want. You would just implement a LogRecorder and specify it in the configuration in addition to the ASLLogRecorder.

I modified a little your function to be global and add to save a log per day.
public func debugPrint(s:String)
{
var paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
let dateString = formatter.string(from: Date())
let fileName = "\(dateString).log"
let logFilePath = (documentsDirectory as NSString).appendingPathComponent(fileName)
var dump = ""
if FileManager.default.fileExists(atPath: logFilePath) {
dump = try! String(contentsOfFile: logFilePath, encoding: String.Encoding.utf8)
}
do {
// Write to the file
try "\(dump)\n\(Date()):\(s)".write(toFile: logFilePath, atomically: true, encoding: String.Encoding.utf8)
} catch let error as NSError {
print("Failed writing to log file: \(logFilePath), Error: " + error.localizedDescription)
}
}

Related

How to get the file content?

I have Obj-C code that looks like this
NSURL * url = [[NSBundle mainBundle] URLForResource:#"MyFile" withExtension:#"txt"];
NSError * err = nil;
NSString * string = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&err];
This code is working properly. I need to convert it to Swift one, so I did it like this
func getContentOfUrl(by fileName: String) -> String? {
var result: String?
guard let pathToFile = Bundle.main.path(forResource: fileName, ofType: "txt")
else {
return nil
}
do {
result = try String(contentsOf: URL(string: pathToFile)!, encoding: String.Encoding.utf8)
}
catch let err{
}
return result
}
And I get an error in the catch block
Attempt to get content of URL FAILED, error: Error Domain=NSCocoaErrorDomain Code=256 "The file “MyFile.txt” couldn’t be opened." UserInfo={NSURL=/private/var/containers/Bundle/Application/63FB01-F11-411-B93-6578DEF57B/MyApp.app/MyFile.txt}
what am I doing wrong?
This works for me.
func getFile(named: String) -> String? {
guard let url = Bundle.main.url(forResource: named, withExtension: "txt") else { return nil }
return try? String(contentsOf: url, encoding: .utf8)
}
If it doesn't you need to make sure that the resource file actually exists.
You can test little functions like this in a playground. Add the file you want to use to the Resources folder in the playground. I did this with a file name "MyText.txt" and this worked fine.
Apart from the fact that you should use a method that returns a URL instead of a file path as already mentioned is that you are using the wrong init method when creating the URL yourself. URL(string:) expects a string in a url format (file://...) but you have a local path so instead you should use URL(fileURLWithPath:). So this will make your existing code work
result = try String(contentsOf: URL(fileURLWithPath: pathToFile), encoding: .utf8)
Or use the URL directly
func content(of fileName: String) -> String? {
Bundle.main.url(forResource: fileName, withExtension: "txt")
.flatMap { try? String.init(contentsOf: $0, encoding: .utf8) }
}

Deleting contents of text file

I'm reading and writing to a text file. There is an instance where I want to delete the contents of the file (but not the file) as a kind of reset. How can I do this?
if ((dirs) != nil) {
var dir = dirs![0]; //documents directory
let path = dir.stringByAppendingPathComponent("UserInfo.txt");
println(path)
let text = "Rondom Text"
//writing
text.writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: nil)
//reading
let text2 = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: nil)
println(text2)
Simply write an empty string to the file like this:
let text = ""
text.writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: nil)
If you set up an NSFileHandle, you can use -truncateFileAtOffset:, passing 0 for the offset.
Or, as pointed out in comments, you can simply write an empty string or data to the file.
Or you can use some kind of data structure / database that does not require you to manually truncate files :)
Swift 3.x
let text = ""
do {
try text.write(toFile: fileBundlePath, atomically: false, encoding: .utf8)
} catch {
print(error)
}
Swift 4.x
let text = ""
do {
try text.write(to: filePath, atomically: false, encoding: .utf8)
} catch {
print(error)
}

How does swift turn a text file into a string or array?

I have a plaintext file that simply has a list of words. I’d like to bring this list in as an array but I haven’t had any luck. I can also bring it in as a string and convert it to an array, but I can’t get that to work either.
let location = "/Users/user/Desktop/list.txt"
var content = NSString(contentsOfFile: location, encoding: NSUTF32StringEncoding, error: nil)
println(content)
My output is always “nil". Same thing if I try to do an array instead:
let content2 = NSArray(contentsOfFile: location)
println(content2)
i use this helper class for file working:
class File {
var path=""
init(path: String,name:String){
self.path=path+"/"+name
}
init(path: String){
self.path=path
}
func read()->String {
return NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: nil)!
}
func write(data: String) {
data.writeToFile(path, atomically: true, encoding: NSUTF8StringEncoding, error: nil)
}
func getLineArray()->[String] {
return read().componentsSeparatedByString("\n")
}
}
using:
let address = "/Users/sajadgarshasbi/Desktop/myTestFile/sample.txt"
if NSFileManager.defaultManager().fileExistsAtPath(address) {
let f = File(path: address)
println(f.read())
}else{
println("File Not Found")
}
Does that file exist? What does
NSFileManager().fileExistsAtPath(location)
return?
Also, are you sure it's UTF-32 encoded? The most likely encoding is probably UTF-8.
change:
var content = NSString(contentsOfFile: location, encoding: NSUTF32StringEncoding, error: nil)
to:
var content = NSString(contentsOfFile: location, encoding: NSUTF8StringEncoding, error: &err)
EDIT: removed nil from suggested fix per Martin R's request
If you are using playground it will always return nil because playground is sandboxed. You can place your file inside the documents directory but it is located somewhere else at your computer and probably looks like something like the String below.
"/var/folders/f9/yrxcqv_10m57prx9lwts4qy80000gq/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.MyPlayground-iOS-NSDate-85BEA777-C527-4A79-9B4E-53427CF98B7D/Documents/".
You can open this folder using your finder Main Menu > Go > Go To Folder... (command-shift-G) and paste that directory path there.
You can locate your playground documents folder using the code below:
let documentDirectoryPath = (NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first as NSURL).path!
Just place your file there and then you can open it as a String:
let fileName = documentDirectoryPath.stringByAppendingPathComponent("test.txt")
var error:NSError?
if "This is a sample sentence.".writeToFile(fileName, atomically: true, encoding: NSUTF8StringEncoding, error: &error) {
println("file successfully saved") // "file successfully saved"
} else {
if let error = error {
println(error.description)
}
}
if let myLoadedString = String(contentsOfFile: fileName, encoding: NSUTF8StringEncoding, error: &error) {
println(myLoadedString) // "This is a sample sentence."
} else {
if let error = error {
println(error.description)
}
}

Reading a short text file to a string in swift

I need to read the contents of a short text file in my Swift program. I did this:
var err: NSError?
let bundle = NSBundle.mainBundle()
let path = bundle.pathForResource("cards", ofType: "ini")
let content = String(contentsOfFile: path!, encoding: NSUTF8StringEncoding, error: nil)
My problem is that I can't use the error reporting. If I change that last line to this:
let content = String(contentsOfFile: path!, encoding: NSUTF8StringEncoding, error: err)
The compiler complains "Extra argument 'contentsOfFile' in call". That makes zero sense to me, maybe someone else can figure it out?
Following the new error handling introduced into iOS 9/Swift 2, a solution for this that works for me is:
let fileLocation = NSBundle.mainBundle().pathForResource("filename", ofType: "txt")!
let text : String
do
{
text = try String(contentsOfFile: fileLocation)
}
catch
{
text = ""
}
text will contain the file contents or be empty in the case of an error.
At a first glance I'd say that you have to pass the err variable by reference:
let content = String(contentsOfFile: path!, encoding: NSUTF8StringEncoding, error: &err)
I was able to resolve the error by using it in this way :
let content = String.stringWithContentsOfFile(path!, encoding: NSUTF8StringEncoding, error: nil)
or if wanted to use error reporting then
var err: NSError?
let content = String.stringWithContentsOfFile(path!, encoding: NSUTF8StringEncoding, error: &err)

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()