Retrieve file creation or modification date - ios

I'm using this piece of code to try to retrieve the last modified date of a file:
NSError *error = nil;
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath: myFilePath error:&error];
if (attributes != nil) {
NSDate *date = (NSDate*)[attributes objectForKey: NSFileModificationDate];
NSLog(#"Date modiifed: %#", [date description]);
}
else {
NSLog(#"Not found");
}
This works well for files in the main bundle but not if the file is located in a subdirectory of the app's document folder, with myFilePath like this:
/Users/User/Library/Application Support/iPhone Simulator/6.0/Applications/The App ID Number/Documents/mySubdirectory/My Saved File
It keeps returning "not found".
I know the file is there, as I can view it with finder. I also tried removing the spaces in the file name but this had no effect.
The error log says no such file or directory, so it looks like something must've gone wrong when I tried to copy the file to the document directory.
Weird thing is, iterating through the document sub directory with contentsOfDirectoryAtPath shows the file as being present.
I've tried hard-coding the path and retrieving it programmatically, with:
*myFolder = [documentsDirectory stringByAppendingPathComponent:#"myFolder"];
*myFilePath = [myFolder stringByAppendingPathComponent:theFileName];
Can anyone see where I'm going wrong?

Swift 3 solution:
func fileModificationDate(url: URL) -> Date? {
do {
let attr = try FileManager.default.attributesOfItem(atPath: url.path)
return attr[FileAttributeKey.modificationDate] as? Date
} catch {
return nil
}
}

Try this. I had same problem and solved with something like next:
NSURL *fileUrl = [NSURL fileURLWithPath:myFilePath];
NSDate *fileDate;
[fileUrl getResourceValue:&fileDate forKey:NSURLContentModificationDateKey error:&error];
if (!error)
{
//here you should be able to read valid date from fileDate variable
}
hope it helped ;)

Here is a Swift like solution of #zvjerka24 answer:
func lastModified(path: String) -> NSDate? {
let fileUrl = NSURL(fileURLWithPath: path)
var modified: AnyObject?
do {
try fileUrl.getResourceValue(&modified, forKey: NSURLContentModificationDateKey)
return modified as? NSDate
} catch let error as NSError {
print("\(#function) Error: \(error)")
return nil
}
}

If you get the error:
"CFURLCopyResourcePropertyForKey failed because it was passed this URL which has no scheme"
You can try to solve this by appending "file:///" to your NSString file path before converting it to NSURL, it worked in my case.

Can also do:
NSURL* file = ...
NSError* error;`
NSDate *creationDate = [[NSFileManager defaultManager] attributesOfItemAtPath:file.path error:&error].fileCreationDate;

For any file in macOS system we can easily get modification date by using any of below mentioned options:
Way 1:
NSString *path = #"path to file";
NSError *err = nil;
NSDictionary *dic2 = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&err];
NSLog(#"File modification Date:%#", dic2[NSFileModificationDate]);
Way 2:
MDItemRef itemRef = MDItemCreate(kCFAllocatorDefault, (__bridge CFStringRef)path);
NSArray *attributeNames = (__bridge NSArray *)MDItemCopyAttributeNames(itemRef);
NSDictionary *attributes = (__bridge NSDictionary *) MDItemCopyAttributes(itemRef, (__bridge CFArrayRef) attributeNames);
CFDateRef modifDate = MDItemCopyAttribute(itemRef, kMDItemContentModificationDate);
NSDate* modificationDate = (__bridge NSDate*) modifDate;
NSLog(#"Modification Date%#", modificationDate);
You can also print various other attributes provided by MDItem :
NSLog(#"All attributes%#", attributes);

Related

NSDirectoryEnumerator iterated element can't compare suffix

I am using NSDirectoryEnmerator to find all file with a suffix of png and jpg with following code:
NSDirectoryEnumerator *directoryEnumerator = [[NSFileManager defaultManager] enumeratorAtURL:containerURL includingPropertiesForKeys:[NSArray array] options:0 errorHandler:^BOOL(NSURL *url, NSError *error) {
// handle error
return NO;
}];
NSString *fileOrDirectory = nil;
while ((fileOrDirectory = [directoryEnumerator nextObject])) {
if([fileOrDirectory hasSuffix:#".jpg"] || [fileOrDirectory hasSuffix:#".png"]){
NSLog(#" find a image file %#", fileOrDirectory );
}
}
But there is an error said that NSURL don't have a method hasSuffix
What happened and how to make this work? what does the type of the iterated elements exactly? the above code was frequently suggested by posts and was presumed to be a NSString but it can't work
The enumeratorAtURL method works with NSURL objects rather than strings (which the exception reason clearly reveals), you can simply compare the pathExtension:
if ([fileOrDirectory.pathExtension isEqualToString:#"jpg"] ||
[fileOrDirectory.pathExtension isEqualToString:#"png"]) { ...

Creating custom directory in iOS Swift 2.2

I am trying to create a custom directory using following snippet, that I essentially translated from a working Obj-C code for the same app.
class func pathToConfigFolder() -> String {
let urls: [String] = NSSearchPathForDirectoriesInDomains(.LibraryDirectory, .UserDomainMask, true)
let libraryPath: String = urls.last!
let configFolder: String = NSURL(fileURLWithPath: libraryPath).URLByAppendingPathComponent(".conf").absoluteString
var directory: ObjCBool = ObjCBool(true)
if !NSFileManager.defaultManager().fileExistsAtPath(configFolder, isDirectory: &directory) {
do {
try NSFileManager.defaultManager().createDirectoryAtPath(configFolder, withIntermediateDirectories: false, attributes: nil)
} catch let error {
print(" Error thrown... \(error)")
}
}
return configFolder
}
But the code fails with following error.
Error thrown... Error Domain=NSCocoaErrorDomain Code=4 "The operation couldn’t be completed. (Cocoa error 4.)" UserInfo=0x17526f4c0 {NSFilePath=file:///var/mobile/Containers/Data/Application/44D90AEB-DD7A-4C8C-9AD0-2665147BAAEC/Library/conf, NSUnderlyingError=0x174054c40 "The operation couldn’t be completed. No such file or directory"}
I have tried both on device and simulator running iOS 9.
EDIT
Objective-C Code
+ (NSString*) pathToConfigFolder
{
NSArray *urls = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
NSUserDomainMask, YES);
NSString *libraryPath = [urls lastObject];
NSString *configFolder = [libraryPath stringByAppendingPathComponent:#".conf"];
NSError *error = nil;
if ( ! [[NSFileManager defaultManager] fileExistsAtPath:configFolder]) {
[[NSFileManager defaultManager] createDirectoryAtPath:configFolder
withIntermediateDirectories:NO
attributes:nil
error:&error];
}
return configFolder;
}
Your problem is here:
let libraryPath: String = urls.last!
let configFolder: String = NSURL(fileURLWithPath: libraryPath).URLByAppendingPathComponent(".conf").absoluteString
The absoluteString method returns the entire URL as a string, so your configFolder is file:///var/mobile/Containers/Data/Application/44D90AEB-DD7A-4C8C-9AD0-2665147BAAEC/Library/conf. That is, it includes the file:// and is therefore not a valid file path suitable for use with the NSFileManager methods you use it with.
Changing those lines to look like this would fix the problem:
let libraryPath: NSString = urls.last!
let configFolder = libraryPath.stringByAppendingPathComponent(".conf")
I'm using NSString here because that's where stringByAppendingPathComponent is defined.

ZipArchive skipping over files 49-100

I have a function that is supposed to create a batch of files, write them to a directory, zip that directory, and attach it to an email.
When I run it, it tells me that it has written 299 files, but when I open up the zip folder precisely 50 are missing
Here is the code. I thought about trimming it down, but I was afraid of removing something that might help someone spot the problem:
func exportAllData() {
var totalExports = 0
// fetch all the data first
// 1 Describe what you want
let fetchRequest = NSFetchRequest(entityName: "Fish")
let sortDescriptor = NSSortDescriptor(key: "time", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
// 2 Get it!
do {
let allCatches = try managedContext!.executeFetchRequest(fetchRequest) as? [NSManagedObject]
totalExports = allCatches!.count
// Create a folder to store the catches in
// path to where data will be written
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
let applicationDocumentsDirectory:NSURL = urls[urls.count-1]
let destinationURL = applicationDocumentsDirectory.URLByAppendingPathComponent("filesToExport", isDirectory: true)
do { try NSFileManager.defaultManager().createDirectoryAtURL(destinationURL, withIntermediateDirectories: false, attributes: nil)
print("created directory")
var currentFile = 0
// Write each fish to file
for fish in allCatches! {
currentFile+= 1
// convert NSManagedObject into a Dictionary, serialize it to NSData, and write it to file
let keys = fish.entity.propertiesByName.keys.array
let dictionary = fish.dictionaryWithValuesForKeys(keys)
let data = NSKeyedArchiver.archivedDataWithRootObject(dictionary)
let fileName = "catchNumber\(currentFile)"
let fileURL = destinationURL.URLByAppendingPathComponent(fileName)
let success = data.writeToFile(fileURL.path!, atomically: false)
if success { print("successfully exported: "+fileName) } else { print("FAILED to export: "+fileName) }
}
// DEBUG
do {
let contents = try NSFileManager.defaultManager().contentsOfDirectoryAtPath(destinationURL.path!)
print(contents)
} catch {
print(error)
}
// After all the fish have been written to file, zip the file
let zipURL = applicationDocumentsDirectory.URLByAppendingPathComponent("exported.zip")
Main.createZipFileAtPath(zipURL.path!, withContentsOfDirectory: destinationURL.path!)
// Load the zip file up as data and send it out in an email
let zipData = NSData(contentsOfURL: zipURL)
let picker = MFMailComposeViewController()
picker.mailComposeDelegate = self
picker.setSubject("Shared Catch Log! (\(totalExports) entries)")
picker.setMessageBody("Open the attachment to this email on an iOS device with Catch Stats installed on it to import all catch into your Catch Log!", isHTML: false)
picker.addAttachmentData(zipData!, mimeType: "applcation/CatchStats", fileName: "CatchStatsLog.csl")
presentViewController(picker, animated: true, completion: nil)
// If failed to created directory
} catch {
print("directory already existed")
}
// If failed to execute fetch request
} catch {
print(error)
self.navigationController?.popViewControllerAnimated(true)
}
}
Now, at the //Debug when I check the contents of the folder I'm about to zip, it has 299 files numbered from 1 to 299 in it.
But when I email the file out and open it up, 50-99 are all missing. Can anybody offer me some advice?
I plucked this code out of ZipArchive's Main.m file. This is what it's doing.
+ (BOOL)createZipFileAtPath:(NSString *)path
withContentsOfDirectory:(NSString *)directoryPath {
return [self createZipFileAtPath:path withContentsOfDirectory:directoryPath keepParentDirectory:NO];
}
+ (BOOL)createZipFileAtPath:(NSString *)path
withContentsOfDirectory:(NSString *)directoryPath
keepParentDirectory:(BOOL)keepParentDirectory {
BOOL success = NO;
NSFileManager *fileManager = nil;
Main *zipArchive = [[Main alloc] initWithPath:path];
if ([zipArchive open]) {
// Use a local file manager (queue/thread compatibility)
fileManager = [[NSFileManager alloc] init];
NSDirectoryEnumerator *directoryEnumerator = [fileManager enumeratorAtPath:directoryPath];
NSString *fileName;
while ((fileName = [directoryEnumerator nextObject])) {
BOOL isDirectory;
NSString *fullFilePath = [directoryPath stringByAppendingPathComponent:fileName];
[fileManager fileExistsAtPath:fullFilePath isDirectory:&isDirectory];
if (!isDirectory) {
if (keepParentDirectory) {
fileName = [[directoryPath lastPathComponent] stringByAppendingPathComponent:fileName];
}
[zipArchive writeFileAtPath:fullFilePath withFileName:fileName];
} else {
if (0 == [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:fullFilePath error:nil].count) {
NSString *temporaryName = [fullFilePath stringByAppendingPathComponent:#".DS_Store"];
[#"" writeToFile:temporaryName atomically:YES encoding:NSUTF8StringEncoding error:nil];
[zipArchive writeFileAtPath:temporaryName withFileName:[fileName stringByAppendingPathComponent:#".DS_Store"]];
[[NSFileManager defaultManager] removeItemAtPath:temporaryName error:nil];
}
}
}
success = [zipArchive close];
}
return success;
}
I found a solution, but it is more of a work around. After a lot of testing, I realized that ZipArchive would fail to zip a folder with an more than 250 files in it. It didn't matter what the files were named or the order they were in.
Instead of serializing each NSDictionary and writing it to file, I created an array of NSDictionaries, and serialized that instead.That way ZipArchive only had to zip one file, and it seems to be working fine now.

Using block statement to detect failed file modification

I have a view controller with a tableview and each tableview cell has an editable UILabel in it. Each cell is also associated with an audio file. Whenever the label is edited, I call an NSObject subclass that handles my files to rename the audio file to whatever the label was changed to. I then return the NSURL absolute string to store in core data. My question is, if this process throws an error - such as "file path already exists", how can I get it to show a UIAlertView in my view controller and not my NSObject subclass. I'm guessing I need some type of block that returns either a NSString or an NSError. I don't have much experience with blocks and any help would be appreciated.
Method in my view controller that calls the NSObject subclass to rename the file
recording.audioURL = [self.managedDocument changeFileName:previousPath withNewComponent:textField.text];
This is the method in my NSObject subclass that renames the audio file
-(NSString*) changeFileName:(NSString*) previousPath withNewComponent:(NSString*)newComponenet
{
NSURL * oldURL = [self.url URLByAppendingPathComponent:#"audioFiles"];
oldURL = [oldURL URLByAppendingPathComponent:[previousPath lastPathComponent]];
NSString * trimmedString = [newComponenet stringByReplacingOccurrencesOfString:#" " withString:#""];
NSURL * newURL = [self.url URLByAppendingPathComponent:#"audioFiles"];
newURL = [newURL URLByAppendingPathComponent:trimmedString];
newURL = [newURL URLByAppendingPathExtension:#"m4a"];
NSFileManager * fileManager = [NSFileManager defaultManager];
NSError * err;
BOOL result = [fileManager moveItemAtURL:oldURL toURL:newURL error:&err];
if(!result)
{
NSLog(#"Error: %#", err);
}
return newURL.absoluteString;
}
The most straightforward approach is to follow the example of the NSFileManager method you're using. Output an NSError* pointer indirectly through a by-reference parameter and make the method's direct return value indicate success or failure:
-(NSString*) changeFileName:(NSString*) previousPath withNewComponent:(NSString*)newComponenet error:(NSError**)error
{
NSURL * oldURL = [self.url URLByAppendingPathComponent:#"audioFiles"];
oldURL = [oldURL URLByAppendingPathComponent:[previousPath lastPathComponent]];
NSString * trimmedString = [newComponenet stringByReplacingOccurrencesOfString:#" " withString:#""];
NSURL * newURL = [self.url URLByAppendingPathComponent:#"audioFiles"];
newURL = [newURL URLByAppendingPathComponent:trimmedString];
newURL = [newURL URLByAppendingPathExtension:#"m4a"];
NSFileManager * fileManager = [NSFileManager defaultManager];
BOOL result = [fileManager moveItemAtURL:oldURL toURL:newURL error:error];
if(!result)
return nil;
return newURL.absoluteString;
}
In the caller, check the return value to determine if it failed and, if so, present the error.

How to NSLog into a file

Is it possible to write every NSLog not only into console, but into a file too? I want to prepare this without replacing NSLog into someExternalFunctionForLogging.
It will be real problem to replace all NSLog. Maybe there is possibility for parsing data from console or catching messages?
Option 1: Use ASL
NSLog outputs log to ASL (Apple's version of syslog) and console, meaning it is already writing to a file in your Mac when you use the iPhone simulator. If you want to read it open the application Console.app, and type the name of your application in the filter field. To do the same in your iPhone device, you would need to use the ASL API and do some coding.
Option 2: write to a file
Let's say you are running on the simulator and you don't want to use the Console.app. You can redirect the error stream to a file of your liking using freopen:
freopen([path cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
See this explanation and sample project for details.
Or you can override NSLog with a custom function using a macro. Example, add this class to your project:
// file Log.h
#define NSLog(args...) _Log(#"DEBUG ", __FILE__,__LINE__,__PRETTY_FUNCTION__,args);
#interface Log : NSObject
void _Log(NSString *prefix, const char *file, int lineNumber, const char *funcName, NSString *format,...);
#end
// file Log.m
#import "Log.h"
#implementation Log
void _Log(NSString *prefix, const char *file, int lineNumber, const char *funcName, NSString *format,...) {
va_list ap;
va_start (ap, format);
format = [format stringByAppendingString:#"\n"];
NSString *msg = [[NSString alloc] initWithFormat:[NSString stringWithFormat:#"%#",format] arguments:ap];
va_end (ap);
fprintf(stderr,"%s%50s:%3d - %s",[prefix UTF8String], funcName, lineNumber, [msg UTF8String]);
[msg release];
}
#end
And import it project wide adding the following to your <application>-Prefix.pch:
#import "Log.h"
Now every call to NSLog will be replaced with your custom function without the need to touch your existing code. However, the function above is only printing to console. To add file output, add this function above _Log:
void append(NSString *msg){
// get path to Documents/somefile.txt
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"logfile.txt"];
// create if needed
if (![[NSFileManager defaultManager] fileExistsAtPath:path]){
fprintf(stderr,"Creating file at %s",[path UTF8String]);
[[NSData data] writeToFile:path atomically:YES];
}
// append
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:path];
[handle truncateFileAtOffset:[handle seekToEndOfFile]];
[handle writeData:[msg dataUsingEncoding:NSUTF8StringEncoding]];
[handle closeFile];
}
and add this line below fprintf in the _Log function:
append(msg);
File writing also works in your iPhone device, but the file will be created in a directory inside it, and you won't be able to access unless you add code to send it back to your mac, or show it on a view inside your app, or use iTunes to add the documents directory.
There is a far easier approach. Here is the method that redirects NSLog output into a file in application’s Documents folder. This can be useful when you want to test your app outside your development studio, unplugged from your mac.
ObjC:
- (void)redirectLogToDocuments
{
NSArray *allPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [allPaths objectAtIndex:0];
NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent:#"yourFile.txt"];
freopen([pathForLog cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
}
Swift:
// 1. Window > Devices and Simulators
// 2. Select the device
// 3. Select your app and click gear icon
// 4. Download container
// 5. Right click and "view contents"
// 6. Find "yourfile.log" under Downloads
//
// redirectLogToDocuments()
func redirectLogToDocuments() {
let allPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = allPaths.first!
let pathForLog = "\(documentsDirectory)/yourfile.log"
freopen(pathForLog.cString(using: String.Encoding.ascii)!, "a+", stdout)
}
After executing this method all output generated by NSLog (ObjC) or print (Swift) will be forwarded to specified file. To get your saved file open Organizer, browse application’s files and save Application Data somewhere in your file system, than simply browse to Documents folder.
I found the simplest solution to the problem: Logging to a file on the iPhone . No need to change any NSLog code or change logger itself, just add these 4 lines to your didFinishLaunchingWithOptions and make sure in your build settings that live release will not have this activated (I added LOG2FILE flag for this).
#ifdef LOG2FILE
#if TARGET_IPHONE_SIMULATOR == 0
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *logPath = [documentsDirectory stringByAppendingPathComponent:#"console.log"];
freopen([logPath cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
#endif
#endif
Translated the answer of JaakL to Swift, posting it here in any case someone else needs it as well
Run this code somewhere in your app, from that moment it stores all NSLog() output to a file, in the documents directory.
let docDirectory: NSString = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0] as NSString
let logpath = docDirectory.stringByAppendingPathComponent("YourFileName.txt")
freopen(logpath.cStringUsingEncoding(NSASCIIStringEncoding)!, "a+", stderr)
Extra: How to find the log-file with Xcode:
You can simply acces the log from Xcode: Windows > Devices > Choose your app > InfoWheelButton > download container.
View the file with finder: click right mouse button on file > show package content > appdata > documents > And there the files are
Swift 4 version
let docDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let logpathe = docDirectory.appendingPathComponent("Logerr.txt")
freopen(logpathe.path.cString(using: .ascii)!, "a+", stderr)
let logpatho = docDirectory.appendingPathComponent("Logout.txt")
freopen(logpatho.path.cString(using: .ascii)!, "a+", stdout)
Output from Swift print() will be in stdout
Ok! firstly, I want to thank Evan-Mulawski.
Here is my solution, maybe it will be helpful for someone:
In AppDelegate I add Function:
void logThis(NSString* Msg, ...)
{
NSArray* findingMachine = [Msg componentsSeparatedByString:#"%"];
NSString* outputString = [NSString stringWithString:[findingMachine objectAtIndex:0]];
va_list argptr;
va_start(argptr, Msg);
for(int i = 1; i < [findingMachine count]; i++) {
if ([[findingMachine objectAtIndex:i] hasPrefix:#"i"]||[[findingMachine objectAtIndex:i] hasPrefix:#"d"]) {
int argument = va_arg(argptr, int); /* next Arg */
outputString = [outputString stringByAppendingFormat:#"%i", argument];
NSRange range;
range.location = 0;
range.length = 1;
NSString* tmpStr = [[findingMachine objectAtIndex:i] stringByReplacingCharactersInRange:range withString:#""];
outputString = [outputString stringByAppendingString:tmpStr];
}
else if ([[findingMachine objectAtIndex:i] hasPrefix:#"#"]) {
id argument = va_arg(argptr, id);
// add argument and next patr of message
outputString = [outputString stringByAppendingFormat:#"%#", argument];
NSRange range;
range.location = 0;
range.length = 1;
NSString* tmpStr = [[findingMachine objectAtIndex:i] stringByReplacingCharactersInRange:range withString:#""];
outputString = [outputString stringByAppendingString:tmpStr];
}
else if ([[findingMachine objectAtIndex:i] hasPrefix:#"."]) {
double argument = va_arg(argptr, double);
// add argument and next patr of message
outputString = [outputString stringByAppendingFormat:#"%f", argument];
NSRange range;
range.location = 0;
range.length = 3;
NSString* tmpStr = [[findingMachine objectAtIndex:i] stringByReplacingCharactersInRange:range withString:#""];
outputString = [outputString stringByAppendingString:tmpStr];
}
else if ([[findingMachine objectAtIndex:i] hasPrefix:#"f"]) {
double argument = va_arg(argptr, double);
// add argument and next patr of message
outputString = [outputString stringByAppendingFormat:#"%f", argument];
NSRange range;
range.location = 0;
range.length = 1;
NSString* tmpStr = [[findingMachine objectAtIndex:i] stringByReplacingCharactersInRange:range withString:#""];
outputString = [outputString stringByAppendingString:tmpStr];
}
else {
outputString = [outputString stringByAppendingString:#"%"];
outputString = [outputString stringByAppendingString:[findingMachine objectAtIndex:i]];
}
}
va_end(argptr);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString * filePath = [[paths objectAtIndex:0]stringByAppendingPathComponent:#"logFile.txt"];
NSError* theError = nil;
NSString * fileString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&theError];
if (theError != nil||[fileString length]==0) {
fileString = [NSString stringWithString:#""];
}
fileString = [fileString stringByAppendingFormat:#"\n%#",outputString];
if(![fileString writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&theError])
{
NSLog(#"Loging problem");
}
NSLog(#"%#",outputString);
}
and, then use "replace for all" NSLog -> logThis.
This code is adapted for my app. It can be expand for different needs.
Thnks for help.
This is what I use and works well:
http://parmanoir.com/Redirecting_NSLog_to_a_file
Hope it helps.
I'll just post it here for the sake of the content
- (BOOL)redirectNSLog {
// Create log file
[#"" writeToFile:#"/NSLog.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil];
id fileHandle = [NSFileHandle fileHandleForWritingAtPath:#"/NSLog.txt"];
if (!fileHandle) return NSLog(#"Opening log failed"), NO;
[fileHandle retain];
// Redirect stderr
int err = dup2([fileHandle fileDescriptor], STDERR_FILENO);
if (!err) return NSLog(#"Couldn't redirect stderr"), NO; return YES;
}
Swift 2.0 :
Add these to Appdelegate didFinishLaunchWithOptions.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
var paths: Array = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentsDirectory: String = paths[0]
let logPath: String = documentsDirectory.stringByAppendingString("/console.log")
if (isatty(STDERR_FILENO) == 0)
{
freopen(logPath, "a+", stderr)
freopen(logPath, "a+", stdin)
freopen(logPath, "a+", stdout)
}
print(logPath)
return true
}
Accessing console.log :
When the log path is printed on Xcode Log Area, select the path, right click, choose Services- Reaveal in Finder and open the file console.log
I worked a little bit with the answer of Alvin George.
To keep the log file sizes under control I implemented (quick and dirty) a "10 generations of log files" solution and add a func to delete them later on
Every time the app starts, it will generate a new log file with an index "0". The exiting file(s) will be renamed with an index higher than before. Index "10" will be deleted.
So, each start gives you a new log file, maximum 10 generations
Might not be the most elegant way to do it, but works for me during the last weeks very good, as I need some longtime logging "off the mac"
// -----------------------------------------------------------------------------------------------------------
// redirectConsoleToFile()
//
// does two things
// 1) redirects "stderr", "stdin" and "stdout" to a logfile
// 2) deals with old/existing files to keep up to 10 generations of the logfiles
// tested with IOS 9.4 and Swift 2.2
func redirectConsoleToFile() {
// Instance of a private filemanager
let myFileManger = NSFileManager.defaultManager()
// the path of the documnts directory of the app
let documentDirectory: String = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first!
// maximum number of logfiles
let maxNumberOfLogFiles: Int = 10
// look if the max number of files already exist
var logFilePath : String = documentDirectory.stringByAppendingString("/Console\(maxNumberOfLogFiles).log")
var FlagOldFileNoProblem: Bool = true
if myFileManger.fileExistsAtPath(logFilePath) == true {
// yes, max number of files reached, so delete the oldest one
do {
try myFileManger.removeItemAtPath(logFilePath)
} catch let error as NSError {
// something went wrong
print("ERROR deleting old logFile \(maxNumberOfLogFiles): \(error.description)")
FlagOldFileNoProblem = false
}
}
// test, if there was a problem with the old file
if FlagOldFileNoProblem == true {
// loop over all possible filenames
for i in 0 ..< maxNumberOfLogFiles {
// look, if an old file exists, if so, rename it with an index higher than before
logFilePath = documentDirectory.stringByAppendingString("/Console\((maxNumberOfLogFiles - 1) - i).log")
if myFileManger.fileExistsAtPath(logFilePath) == true {
// there is an old file
let logFilePathNew = documentDirectory.stringByAppendingString("/WayAndSeeConsole\(maxNumberOfLogFiles - i).log")
do {
// rename it
try myFileManger.moveItemAtPath(logFilePath, toPath: logFilePathNew)
} catch let error as NSError {
// something went wrong
print("ERROR renaming logFile: (i = \(i)), \(error.description)")
FlagOldFileNoProblem = false
}
}
}
}
// test, if there was a problem with the old files
if FlagOldFileNoProblem == true {
// No problem so far, so try to delete the old file
logFilePath = documentDirectory.stringByAppendingString("/Console0.log")
if myFileManger.fileExistsAtPath(logFilePath) == true {
// yes, it exists, so delete it
do {
try myFileManger.removeItemAtPath(logFilePath)
} catch let error as NSError {
// something went wrong
print("ERROR deleting old logFile 0: \(error.description)")
}
}
}
// even if there was a problem with the files so far, we redirect
logFilePath = documentDirectory.stringByAppendingString("/Console0.log")
if (isatty(STDIN_FILENO) == 0) {
freopen(logFilePath, "a+", stderr)
freopen(logFilePath, "a+", stdin)
freopen(logFilePath, "a+", stdout)
displayDebugString(DEBUG_Others, StringToAdd: "stderr, stdin, stdout redirected to \"\(logFilePath)\"")
} else {
displayDebugString(DEBUG_Others, StringToAdd: "stderr, stdin, stdout NOT redirected, STDIN_FILENO = \(STDIN_FILENO)")
}
}
// -----------------------------------------------------------------------------------------------------------
// cleanupOldConsoleFiles()
//
// delete all old consolfiles
func cleanupOldConsoleFiles() {
// Instance of a private filemanager
let myFileManger = NSFileManager.defaultManager()
// the path of the documnts directory of the app
let documentDirectory: String = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first!
// maximum number of logfiles
let maxNumberOfLogFiles: Int = 10
// working string
var logFilePath: String = ""
// loop over all possible filenames
for i in 0 ... maxNumberOfLogFiles {
// look, if an old file exists, if so, rename it with an index higher than before
logFilePath = documentDirectory.stringByAppendingString("/Console\(i).log")
if myFileManger.fileExistsAtPath(logFilePath) == true {
// Yes, file exist, so delete it
do {
try myFileManger.removeItemAtPath(logFilePath)
} catch let error as NSError {
// something went wrong
print("ERROR deleting old logFile \"\(i)\": \(error.description)")
}
}
}
}

Resources