I am using UIDocumentPickerViewController to allow the user to select files that will be "attached" and available within the App. The concept is allow the user to send detail via email with the selected file attachments.
As each file is attached, I copy the file from the tmp Inbox (where fileManager puts the imported file) to a directory I create within the App document directory called "fileAttachments".
I list the files in a UITableView and the user can select each entry and preview the content within a QLPreviewController view using the path stored in the file object fileOJ.filePath.
It all works swimmingly well, until a reload of the project down to my test iPad, then all the files seem to disappear. My list of the files is still fine, but there is no file at the path location.
Any help with just what is happenning would be greatly appreciated.
- (IBAction)selectFilesAction:(UIBarButtonItem *)sender {
NSArray *UTIs = [NSArray arrayWithObjects:#"public.data", nil];
[self openFilePicker:UTIs];
}
- (void)openFilePicker:(NSArray *)UTIs {
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:UTIs inMode:UIDocumentPickerModeImport];
documentPicker.delegate = self;
documentPicker.allowsMultipleSelection = FALSE;
documentPicker.popoverPresentationController.barButtonItem = self.selectFilesButton;
[self presentViewController:documentPicker animated:TRUE completion:nil];
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray <NSURL *>*)urls {
NSLog(#"picked URLs %#", urls);
// selecting multiple documents is cool, but requires iOS 11
for (NSURL *documentURL in urls) {
//get file details
NSDictionary *attr = [documentURL resourceValuesForKeys:#[NSURLFileSizeKey,NSURLCreationDateKey] error:nil];
NSLog(#"object: %#", attr);
NSNumber *fileSize = [attr valueForKey:NSURLFileSizeKey];
NSDate *dateFileCreated = [attr valueForKey:NSURLCreationDateKey];
NSDateFormatter *storageDateFormat = [[NSDateFormatter alloc] init];
[storageDateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSString *createdDateString = [storageDateFormat stringFromDate:dateFileCreated];
MMfile *fileObj = [[MMfile alloc]init];
fileObj.fileName = documentURL.lastPathComponent;
fileObj.meetingID = _meetingID;
fileObj.fileSize = fileSize;
fileObj.fileCreateDate = createdDateString;
//move file to new directory
fileObj.filePath = [self movefile:documentURL.lastPathComponent sourceFilePath:documentURL.path directory:#"fileAttachments"];
//save file details
[self.meetingModel saveFile:fileObj];
//refresh array and reload table
self.fileArray = [self.meetingModel getFiles:self.meetingID];
[self.tableView reloadData];
}
}
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
NSLog(#"cancelled");
}
-(NSString *)movefile:(NSString *)filename sourceFilePath:(NSString *)sourcePath directory:(NSString *)directoryName{
// Move file from tmp Inbox to the destination directory
BOOL isDir;
NSError *error;
NSFileManager *fileManager= [NSFileManager defaultManager];
//get directory path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString* directoryPath;
if (paths>0) {
NSString *documentsDirectory = [paths objectAtIndex:0];
directoryPath = [NSString stringWithFormat:#"%#/%#",documentsDirectory,directoryName];
}
if(![fileManager fileExistsAtPath:directoryPath isDirectory:&isDir])
if(![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:NO attributes:nil error:NULL])
NSLog(#"Error: Create folder failed %#", directoryPath);
NSString *destinationPath = [NSString stringWithFormat:#"%#/%#",directoryPath,filename];;
BOOL success = [fileManager moveItemAtPath:sourcePath toPath:destinationPath error:&error];
if (success) {
NSLog(#"moved file");
}else{
NSLog(#"error %#",error.description);
}
return destinationPath;
}
Found the issue. When the project is rebuilt and downloaded to the iPad the AppID changes, and as the documents path includes the AppID, so the documents path changes. Key is not to save the file path, only the file name and rebuild the path each instance. After having found the issue, I now see other similar posts I didn't find earlier. Also see Document directory path change when rebuild application
I am currently using a dispatch_group to get notify when all concurrent tasks are done. I am offloading some heavy tasks on one concurrent queue within the [TWReaderDocument documentFileURL:url withCompletionBlock:] class method.
I have implemented the following code but never received any notification. I don't see what i am potentially doing wrong in the below code:
dispatch_group_t readingGroup = dispatch_group_create();
NSFileManager* manager = [NSFileManager defaultManager];
NSString *docsDir = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Data"];
NSDirectoryEnumerator *dirEnumerator = [manager enumeratorAtURL:[NSURL fileURLWithPath:docsDir]
includingPropertiesForKeys:[NSArray arrayWithObjects:NSURLNameKey,
NSURLIsDirectoryKey,nil]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:nil];
// An array to store the all the enumerated file names in
NSMutableArray *arrayFiles;
// Enumerate the dirEnumerator results, each value is stored in allURLs
for (NSURL *url in dirEnumerator) {
// Retrieve the file name. From NSURLNameKey, cached during the enumeration.
NSString *fileName;
[url getResourceValue:&fileName forKey:NSURLNameKey error:NULL];
// Retrieve whether a directory. From NSURLIsDirectoryKey, also cached during the enumeration.
NSNumber *isDirectory;
[url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL];
if (![isDirectory boolValue]) {
dispatch_group_enter(readingGroup);
TWReaderDocument* doc = [TWReaderDocument documentFileURL:url withCompletionBlock:^(BOOL success) {
dispatch_group_leave(readingGroup);
}];
[arrayFiles addObject:doc];
}
else if ([[[fileName componentsSeparatedByString:#"_" ] objectAtIndex:0] isEqualToString:#"XXXXXX"]) {
TreeItem* treeItem = [[TreeItem alloc] init];
arrayFiles = [NSMutableArray arrayWithCapacity:10];
treeItem.child = arrayFiles;
treeItem.nodeName = [[fileName componentsSeparatedByString:#"_" ] lastObject];
[self addItem:treeItem];
}
}
dispatch_group_notify(readingGroup, dispatch_get_main_queue(), ^{ // 4
NSLog(#"All concurrent tasks completed");
});
Does the dispatch_group_enter and dispatch_group_leave have to be executed on the same thread?
EDIT
The code snippet of my factory method might help aswell:
+ (TWReaderDocument *)documentFileURL:(NSURL *)url withCompletionBlock:(readingCompletionBlock)completionBlock{
TWReaderDocument * twDoc = [[TWReaderDocument alloc] init];
twDoc.status = ReaderDocCreated;
twDoc.doc = [ReaderDocument withDocumentFilePath:[url path] withURL:url withLoadingCompletionBLock:^(BOOL completed) {
twDoc.status = completed ? ReaderDocReady : ReaderDocFailed;
completionBlock(completed);
}];
return twDoc;
}
TWReaderDocument is a wrapper class that call internally the following methods of a third-party library (it is a PDF reader)
+ (ReaderDocument *)withDocumentFilePath:(NSString *)filePath withURL:(NSURL*)url withLoadingCompletionBLock:(readingCompletionBlock)completionBlock{
ReaderDocument *document = [[ReaderDocument alloc] initWithFilePath:filePath withURL:url withLoadingCompletionBLock:[completionBlock copy]];
return document;
}
- (id)initWithFilePath:(NSString *)fullFilePath withURL:(NSURL*)url withLoadingCompletionBLock:(readingCompletionBlock)completionBlock {
id object = nil; // ReaderDocument object;
if ([ReaderDocument isPDF:fullFilePath] == YES) // File must exist
{
if ((self = [super init])) // Initialize superclass object first
{
_fileName = [ReaderDocument relativeApplicationFilePath:fullFilePath]; // File name
dispatch_async([ReaderDocument concurrentLoadingQueue], ^{
self.guid = [ReaderDocument GUID]; // Create a document GUID
self.password = nil; // Keep copy of any document password
self.bookmarks = [NSMutableIndexSet indexSet]; // Bookmarked pages index set
self.pageNumber = [NSNumber numberWithInteger:1]; // Start on page 1
CFURLRef docURLRef = (__bridge CFURLRef)url;// CFURLRef from NSURL
self.fileURL = url;
CGPDFDocumentRef thePDFDocRef = CGPDFDocumentCreateX(docURLRef, self.password);
BOOL success;
if (thePDFDocRef != NULL) // Get the number of pages in the document
{
NSInteger pageCount = CGPDFDocumentGetNumberOfPages(thePDFDocRef);
self.pageCount = [NSNumber numberWithInteger:pageCount];
CGPDFDocumentRelease(thePDFDocRef); // Cleanup
success = YES;
}
else // Cupertino, we have a problem with the document
{
// NSAssert(NO, #"CGPDFDocumentRef == NULL");
success = NO;
}
NSFileManager *fileManager = [NSFileManager new]; // File manager instance
self.lastOpen = [NSDate dateWithTimeIntervalSinceReferenceDate:0.0]; // Last opened
NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:fullFilePath error:NULL];
self.fileDate = [fileAttributes objectForKey:NSFileModificationDate]; // File date
self.fileSize = [fileAttributes objectForKey:NSFileSize]; // File size (bytes)
completionBlock(success);
});
//[self saveReaderDocument]; // Save the ReaderDocument object
object = self; // Return initialized ReaderDocument object
}
}
return object;
}
It's hard to say what's going on here without knowing more about TWReaderDocument, but I have a suspicion...
First off, no, dispatch_group_enter and dispatch_group_leave do not have to be executed on the same thread. Definitely not.
My best guess based on the info here would be that for some input, [TWReaderDocument documentFileURL:withCompletionBlock:] is returning nil. You might try this instead:
if (![isDirectory boolValue]) {
dispatch_group_enter(readingGroup);
TWReaderDocument* doc = [TWReaderDocument documentFileURL:url withCompletionBlock:^(BOOL success) {
dispatch_group_leave(readingGroup);
}];
// If the doc wasn't created, leave might never be called.
if (nil == doc) {
dispatch_group_leave(readingGroup);
}
[arrayFiles addObject:doc];
}
Give that a try.
EDIT:
It's exactly as I expected. There are cases in which this factory method will not call the completion. For instance:
if ([ReaderDocument isPDF:fullFilePath] == YES) // File must exist
If -isPDF: returns NO the completionBlock will never be called, and the returned value will be nil.
Incidentally, you should never compare something == YES. (anything non-zero is equivalent to YES, but YES is defined as 1. Just do if ([ReaderDocument isPDF:fullFilePath]). It's equivalent, and safer.
Below is the code I have written to change the name of any file stored in an iOS device "Documents" directory.
When renaming the file using this code, the prefix resolves to...
"file:/localhost/private/var/mobile/Applications/.../Documents/"
instead of the correctly formatted...
"file://localhost/private/var/mobile/Applications/.../Documents/"
I am losing a slash, and so the rename method fails!!!
I have rewritten this for both NSString and NSURL formats, and both methods present the identical error. I have tested this using the iOS Simulator and on device, and both tests present the identical error.
I suspect I am overlooking something simple, but cannot figure what it is - any help please?
- (void)alterFileNameOrExtension {
NSError *errorMove = nil;
NSError __autoreleasing *error = nil;
//note below - entity attribute document.dStringURL has a data type NSString
NSString *file = [document.dStringURL lastPathComponent];
NSString *fileName = [file stringByDeletingPathExtension];
NSString *fileExtension = [file pathExtension];
NSString *fileNameNew = nil;
//note below - self.tempFileComponent, self.tempFileName & self.tempFileExtension
//note below - are set in the (IBAction) method textFieldDidEndEditing:
//note below - self.tempFileComponent determines whether the user is attempting to change the file name or the file extension
if (self.tempFileComponent == 1) {
fileNameNew = [[self.tempFileName stringByAppendingPathExtension:fileExtension] stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
} else if (self.tempFileComponent == 2) {
fileNameNew = [fileName stringByAppendingPathExtension:self.tempFileExtension];
} else {
NSLog(#"%# - %# - attempt to determine tempFileComponent has (possible undefined) E~R~R~O~R: %#_", self.className, NSStringFromSelector(_cmd), error.localizedDescription);
}
NSString *filePath = [document.dStringURL stringByDeletingLastPathComponent];
NSString *filePathNew = [filePath stringByAppendingPathComponent:fileNameNew];
BOOL move = [[NSFileManager defaultManager] moveItemAtPath:document.dStringURL toPath:filePathNew error:&errorMove];
if (!move) {
//handle error
} else {
//complete process
}
}
#
With assistance from #Mario, I have adjusted my code and include the working version below for the benefit of others...
#
NSError *errorMove = nil;
NSError __autoreleasing *error = nil;
NSString *file = [document.dStringURL lastPathComponent];
NSString *fileName = [file stringByDeletingPathExtension];
NSString *fileExtension = [file pathExtension];
NSString *fileNameNew = nil;
if (self.tempFileComponent == 1) {
fileNameNew = [self.tempFileName stringByAppendingPathExtension:fileExtension];
} else if (self.tempFileComponent == 2) {
fileNameNew = [fileName stringByAppendingPathExtension:self.tempFileExtension];
} else {
NSLog(#"%# - %# - attempt to determine tempFileComponent has (possible undefined) E~R~R~O~R: %#_", self.className, NSStringFromSelector(_cmd), error.localizedDescription);
}
NSURL *fileURLOld = [NSURL URLWithString:document.dStringURL];
NSURL *fileURLPrefix = [fileURLOld URLByDeletingLastPathComponent];
NSURL *fileURLNew = [fileURLPrefix URLByAppendingPathComponent:fileNameNew];
BOOL move = [[NSFileManager defaultManager] moveItemAtURL:fileURLOld toURL:fileURLNew error:&errorMove];
if (!move) {
//handle error
} else {
//complete process
}
The NSString path manipulation methods like stringByAppendingPathComponent expects file paths not URLs. It will remove double slashes as this is not a valid file path (although it is a valid URL).
I moved some code that will be used multiple times into a class.
I'm not getting errors, but I'm also not getting results. It seems to skip over my class completely.
Ideally, this class is supposed to do NSURL conns and XMLParser stuff to chew up the data feed from our hosting API. I already have this working but wanted to congeal and somewhat normalize/centralize some of the main logic of my code.
The one function 'bdCheckIfFileExistsAndisValid' is supposed to take a string but return BOOL and it isn't being called at all.
Neither is 'bdParsePlaylistXML' that is supposed to take a string and return an array.
I put breakpoints everywhere in my class and none are hit.
I'm new so I'm not sure if I did everything right. Here's some code, thanks in advance.
--------------------CUSTOM CLASS:(.h)
#interface bdXMLParser : NSObject {
NSMutableArray *playlist;
//Playlist XML info
BOOL recordTrackName;
BOOL recordTrackDescription;
BOOL recordTrackThumbnailAbsoluteLocation;
BOOL recordTrackURL;
NSString *TrackName;
NSString *TrackDescription;
NSString *TrackThumbnailAbsoluteLocation;
NSString *TrackURL;
}
-(NSMutableArray*) bdParsePlaylistXML:(NSString *) playlistXMLFileName;
-(BOOL) bdCheckIfFileExistsAndisValid:(NSString *) localFileName;
----------------CUSTOM CLASS (.m):
#import "bdXMLParser.h"
#implementation bdXMLParser
{
NSMutableData *webData;
NSMutableArray *playlist;
NSXMLParser *xmlParserPlaylist;
}
-(NSString*) bdDocumentsDirectory{
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
return documentsPath;
}
-(int) bdCheckFileCreationDate:(NSString *) fileName {
//get XML file path
NSString *localFilePath = [[self bdDocumentsDirectory] stringByAppendingPathComponent:fileName];
//local file check
NSFileManager *filemgr;
filemgr = [NSFileManager defaultManager];
NSDictionary* attrs = [filemgr attributesOfItemAtPath:localFilePath error:nil];
NSDate *fileCreationDate = [attrs objectForKey: NSFileCreationDate];
NSDate *rightNow = [NSDate date];
NSTimeInterval lastDiff = [fileCreationDate timeIntervalSinceNow];
int lastDiffINT = round(lastDiff);
NSLog(#"NSFileCreationDate:%#",fileCreationDate);
NSLog(#"CurrentDate:%#",rightNow);
NSLog(#"lastDiff:%f",lastDiff);
return lastDiffINT;
}
-(BOOL) bdCheckIfFileExistsAndisValid:(NSString *) fileName {
//local file check
NSString* foofile = [[self bdDocumentsDirectory] stringByAppendingPathComponent:fileName];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:foofile];
if ((fileExists == YES) && ([self bdCheckFileCreationDate:foofile] > -86400))//(24 hrs = 86400 seconds)
return YES;
else
return NO;
}
HERE's THE VIEW WHERE I'M TRYING TO USE IT:(menu.h)
#import "bdXMLParser.h"
#interface MenuScreenViewController : UIViewController <NSXMLParserDelegate>
- (IBAction)btnPlayerPlayPause:(id)sender;
(menu.m)
- (IBAction)btnPlayerPlayPause:(id)sender {
//if array exists, don't reload xml, dont reparse xml, just go to the view
if (playlist.count == 0){
//Playlist!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//========================================================================
//1st: check to see if we have a local cached xml data
//if we do, check if it is <24hr old and if so load it
//if not, go get it with connection and overwrite/store it
//init blogs NSMutableArray
playlist = [[NSMutableArray alloc] init];
//local file check
bdXMLParser *myParser;
BOOL fileExistsAndValid = NO;
=HERE!==fileExistsAndValid = [myParser bdCheckIfFileExistsAndisValid:PlaylistName];
//1st
if (fileExistsAndValid)//(<24 hrs old)
{
NSLog (#"File fileExistsAndValid");
=AND HERE!!=playlist = [myParser bdParsePlaylistXML:PlaylistName];
NSLog(#"playlist:%u", playlist.count);
//load first track
[self LoadTrack:0];
}
else{
NSLog (#"File doesn't exist");
//call refresh function
//[self refreshAlbumPhotoXML];
[myParser bdRefreshPlaylistXML];
}
}
}
you forgot initing the class
bdXMLParser *myParser= [[bdXMLParster alloc]init];
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)")
}
}
}
}