Rename a file if file already exists - ios

What is the best way in objective-c to rename a file if a file with a same name exists.
Ideally, I would like to name untitled.png to untitled-1.png if a file named untitled.png already exists.
I have included my solution below as an answer, but I think there should be a better way (built-in function) to do it.
My solution is not thread-safe, and susceptible to race conditions.

The following function returns the next available filename:
//returns next available unique filename (with suffix appended)
- (NSString*) getNextAvailableFileName:(NSString*)filePath suffix:(NSString*)suffix
{
NSFileManager* fm = [NSFileManager defaultManager];
if ([fm fileExistsAtPath:[NSString stringWithFormat:#"%#.%#",filePath,suffix]]) {
int maxIterations = 999;
for (int numDuplicates = 1; numDuplicates < maxIterations; numDuplicates++)
{
NSString* testPath = [NSString stringWithFormat:#"%#-%d.%#",filePath,numDuplicates,suffix];
if (![fm fileExistsAtPath:testPath])
{
return testPath;
}
}
}
return [NSString stringWithFormat:#"%#.%#",filePath,suffix];
}
An example calling function is as follows:
//See" http://stackoverflow.com/questions/1269214/how-to-save-an-image-that-is-returned-by-uiimagepickerview-controller
// Get the image from the result
UIImage* image = [info valueForKey:#"UIImagePickerControllerOriginalImage"];
// Get the data for the image as a PNG
NSData* imageData = UIImagePNGRepresentation(image);
// Give a name to the file
NSString* imageName = #"image";
NSString* suffix = #"png";
// Now we get the full path to the file
NSString* filePath = [currentDirectory stringByAppendingPathComponent:imageName];
//If file already exists, append unique suffix to file name
NSString* fullPathToFile = [self getNextAvailableFileName:filePath suffix:suffix];
// and then we write it out
[imageData writeToFile:fullPathToFile atomically:NO];

Related

Get file name of copied document off UIPasteBoard

I am trying to add a feature to an existing iOS app so that documents "copied" within Mail (say a PDF) can be pasted into my app.
After a PDF document is viewed in Mail and "Copy" chosen from the Action menu, within my app's code, when I inspect:
[[UIPasteboard generalPasteboard] items]
I can see the document as per:
{
"com.adobe.pdf" = {length = 875113, bytes = 0x25504446 2d312e35 0d0a25b5 b5b5b50d ... 300d0a25 25454f46 };
}
)
And further retrieve it via:
po [[UIPasteboard generalPasteboard] valueForPasteboardType:#"com.adobe.pdf"]
<OS_dispatch_data: data[0x2820e5c00] = { leaf, size = 875113, buf = 0x1125e0000 }>
However, is there any way to get the document's name? In the Files.app when I paste the same document it gets pasted as the original file name.
Should I be using the pasteboard for this, or if there another API which can get access to the copied document with the file name?
Even if this question is a little older I happened to search for the same and finally found the solution. So for future reference:
Starting with iOS 11 UIPasteboard contains an array itemProviders which gives you all the information about the objects that are available. The same is used for Drag/Drop.
So with the following logic you can save the items to a certain path:
NSArray *items = [[UIPasteboard generalPasteboard] itemProviders];
for (NSInteger i = 0; i < itemProvider.count; i++) {
NSItemProvider *itemProvider = itemProvider[i];
NSString *identifier = itemProvider.registeredTypeIdentifiers[0];
[itemProvider loadDataRepresentationForTypeIdentifier:identifier completionHandler:^(NSData * _Nullable data, NSError * _Nullable error) {
NSMutableDictionary *result;
if (!error) {
NSString *ext = (__bridge NSString *)(UTTypeCopyPreferredTagWithClass((__bridge CFStringRef _Nonnull)(identifier), kUTTagClassFilenameExtension));
if ([ext isEqualToString:#"jpeg"]) {
ext = #"jpg";
}
NSString *mime = (__bridge NSString *)(UTTypeCopyPreferredTagWithClass((__bridge CFStringRef _Nonnull)(identifier), kUTTagClassMIMEType));
if (!mime) {
mime = #"application/*";
}
NSString *filename = itemProvider.suggestedName;
NSString *path = [folderPath stringByAppendingPathComponent:filename];
[data writeToFile:path options:NSDataWritingAtomic error:&error];
}
}
}

Copying a realm-file seems to cause realm Migration-block call - why?

After copying a .realm-file from folder-location1 to folder-location2 (and also changing its name from location1.realm to location2.realm using NSFileManager) - it seems that after doing so, the first call to this new file causes a migration-block call! (i.e. setSchemaVersion). I wonder WHY ???
Prior calls to the "location1/location1.realm" File did not cause a migration-block call - however calls to "location2/location2.realm" do - and the files are identical in structure (at least in Realm-Browser there is no obvious difference) !!
Here is my code :
ApplicatonSuppPathHandler *ApplicationSupportPathHandler = [[ApplicatonSuppPathHandler alloc] init];
// creation of location where this directory shall be placed on the iPhone
// typically /library/Application Support/<bundleID_name>/RLMGeneralDatabasesFolderName/...
// = name of directory that the .realm-File finally should be placed in
NSString *RLMLocation1DirectoryName = folderName;
// if it does not exist already, create the RLMLocation_xyz-directory (.../library/Application Support/<bundleID_name>/RLMDatabasesFolderName)
if([ApplicationSupportPathHandler getURLToApplicationDirectoryWithSubDirectory:RLMLocation1DirectoryName] == nil) {
[ApplicationSupportPathHandler createSubDirectoryAtLocationToApplicationDirectory:RLMLocation1DirectoryName];
}
// get the name of entire directory just created
NSURL *RLMLocation1Directory = [ApplicationSupportPathHandler getURLToApplicationDirectoryWithSubDirectory:RLMLocation1DirectoryName];
// name of entire path-name (including filename !! ...needed for copy function below...)
NSString *RLMLocation1Path = [[RLMLocation1Directory path] stringByAppendingPathComponent:fileName];
// HERE IS WHERE THE MIGRATION BLOCK IS CALLED - WHY ?????
// *******************************************************
RLMRealm *realm_Location1 = [RLMRealm realmWithPath:RLMLocation1Path]; // pointing to realm file at path
// the rest does work after the migration-block call...
[realm_Location1 beginWriteTransaction];
[realm_Location1 deleteAllObjects];
[realm_Location1 addObject:RLMTopoRes];
[realm_Location1 commitWriteTransaction];
Below is the implementation of the used ApplicationSuppPathHandler class-methods:
#implementation ApplicatonSuppPathHandler
- (NSURL*)getURLToApplicationDirectoryWithSubDirectory:(NSString*)SubDirectoryName {
NSString* appBundleID = [[NSBundle mainBundle] bundleIdentifier];
NSFileManager*fm = [NSFileManager defaultManager];
NSURL* dirPath = nil;
// Find the application support directory in the home directory.
NSArray* appSupportDir = [fm URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask];
if ([appSupportDir count] > 0)
{
// Append the bundle ID and the location-Foldername to the URL for the Application Support directory
dirPath = [[[appSupportDir objectAtIndex:0] URLByAppendingPathComponent:appBundleID] URLByAppendingPathComponent:SubDirectoryName];
BOOL isDir;
BOOL exists = [fm fileExistsAtPath:[dirPath path] isDirectory:&isDir];
if (exists) {
/* file exists */
if (isDir) {
/* path exists */
return dirPath;
}
else {
NSLog(#"Directory does not exist");
return nil;
}
}
else {
/* file does not exist */
return nil;
}
}
return dirPath;
}
- (void)createSubDirectoryAtLocationToApplicationDirectory:(NSString*)SubDirectoryName {
NSString* appBundleID = [[NSBundle mainBundle] bundleIdentifier];
NSFileManager*fm = [NSFileManager defaultManager];
NSURL* dirPath = nil;
// Find the application support directory in the home directory.
NSArray* appSupportDir = [fm URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask];
if ([appSupportDir count] > 0)
{
// Append the bundle ID and the location-Foldername to the URL for the Application Support directory
dirPath = [[[appSupportDir objectAtIndex:0] URLByAppendingPathComponent:appBundleID] URLByAppendingPathComponent:SubDirectoryName];
// If the directory does not exist, this method creates it.
// This method call works in OS X 10.7 and later only.
NSError* theError = nil;
if (![fm createDirectoryAtURL:dirPath withIntermediateDirectories:YES attributes:nil error:&theError]) {
// Handle the error.
NSLog(#"%#", theError.localizedDescription);
}
else {
// Mark the directory as excluded from iCloud backups
if (![dirPath setResourceValue:#YES
forKey:NSURLIsExcludedFromBackupKey
error:&theError]) {
NSLog(#"Error excluding %# from iCloud backup %#", [dirPath lastPathComponent], theError.localizedDescription);
}
else {
// NSLog(#"Location Directory excluded from iClud backups");
}
}
}
}
There was previously a bug in Realm where new realm files would have their schema version set to 0 instead of the current schema version, which would trigger a migration in your case.
A fix for this was pushed to the master branch of Realm last week: https://github.com/realm/realm-cocoa/pull/1142
Building Realm from master should address the issue you're having. Realm v0.88.0 will be released shortly with this fix.
...hhm - seems that another method messed up !
The below code shows this method that loads data from a realm-file into a realm-wrapper-object.
Coming back to the original problem (location2/location2.realm file creating migraton-block):
Unfortunately, I was not aware that calling this "loadData_..."-method needed to be called with caution ! If it is called twice - once to location1/location1.realm and then a second time (to location2/location2.realm) - that is when things get messed up !
I tried to create a "wrapper" that loads and saves Realm-objects. But it seems more difficult with Realm than originally expected. Thanks for any comment on this !!
- (void) loadData_at_TopoNr_from_LocationRLM :(NSNumber *)TopoNr :(NSString *)folderName :(NSString *)fileName {
// realm-object calling "get_TopoResultRLM_FilePath" must be of same object-type, otherwise block-migration call !
RLMRealm *realm = [RLMRealm realmWithPath:[self get_TopoResultRLM_FilePath :folderName :fileName]];
RLMResults *resTopoResult = [RLMTopoResult allObjectsInRealm:realm];
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:#"TopoNrRLM == %d", [TopoNr intValue]];
RLMResults *resultTopoResult = [resTopoResult objectsWithPredicate:predicate1];
if ([resultTopoResult count] <= 1) {
if ([resultTopoResult count] == 1) {
// found one object
// data loading
self.TopoNrRLM = [resultTopoResult.firstObject TopoNrRLM];
self.nameAnamneserRLM = [resultTopoResult.firstObject nameAnamneserRLM];
self.anamneseDateRLM = [resultTopoResult.firstObject anamneseDateRLM];
self.NumberOfCriteriaRLM = [resultTopoResult.firstObject NumberOfCriteriaRLM];
self.BestMatchMethodRLM = [resultTopoResult.firstObject BestMatchMethodRLM];
self.RealmFrameworkVersionRLM = [resultTopoResult.firstObject RealmFrameworkVersionRLM];
self.BoundaryConditionVerionRLM = [resultTopoResult.firstObject BoundaryConditionVerionRLM];
self.CriteriaRLM = [resultTopoResult.firstObject CriteriaRLM];
self.imageNameRLM = [resultTopoResult.firstObject imageNameRLM];
self.imageDataRLM = [resultTopoResult.firstObject imageDataRLM];
}
else { ....
Here is the property definition of the wrapper-object-class :
// GUIData-RLM
#property (nonatomic) RLMGUIData *GUIDataRLM;
// Location-RLM
#property (nonatomic) RLMTopoResult *LocationRLM;
Here is the object creation and call of that loadData_at...-method :
RLMTopoResult *LocationRealm = [[RLMTopoResult alloc] init];
[self setLocationRLM:LocationRealm];
[LocationRealm loadData_at_TopoNr_from_LocationRLM :[NSNumber numberWithInt:[GUIDataRealm TopoNrRLM]] :[GUIDataRealm locationFolderNameRLM] :[NSString stringWithFormat:#"%#%s", [GUIDataRealm locationFolderNameRLM], ".realm"]];

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.

PDF file size from document directory showing zero in iOS

I am retrieving file size of PDF file that located in document directory with following codes.
- (NSString *)sizeOfFile:(NSString *)filePath {
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
NSInteger fileSize = [[fileAttributes objectForKey:NSFileSize] integerValue];
NSString *fileSizeString = [NSByteCountFormatter stringFromByteCount:fileSize countStyle:NSByteCountFormatterCountStyleFile];
return fileSizeString;
}
NSURL *url = [self.arrayOfBooks objectAtIndex:indexPath.row];
NSLog(#"%#",[self sizeOfFile:url.absoluteString]); // Showing zero KB.
I am sure there is file in arrayBooks because it can show in cellForRowAtIndexPath.
But i don't know why file size is showing zero.
Please help me.
The most likely cause of your issue is due to an invalid path being sent to sizeOfFile:.
Your code:
NSURL *url = [self.arrayOfBooks objectAtIndex:indexPath.row];
NSLog(#"%#",[self sizeOfFile:url.absoluteString]); // Showing zero KB.
should be:
NSURL *url = [self.arrayOfBooks objectAtIndex:indexPath.row];
NSLog(#"%#",[self sizeOfFile:[url path]]);
You want to call the path method to get a file path. absoluteString returns a file URL, not a path.
Also, don't use property syntax to call methods.

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