I have the user select a folder from an NSOpenPanel. This returns a filepath like: file://localhost/Folder. Here is my code where it all goes wrong:
NSURL *filePath = [openDlg URL]; //OpenDlg is my NSOpenPanel
NSString *s = [filePath absoluteString];
NSLog(#"%#",s);
NSError *error;
NSArray *b = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:s error:&error];
if (error) {
NSLog(#"%#",error);
}
NSLog(#"%lu",b.count);
Here, no matter what folder I select, this error message is sent: The folder “Folder” doesn’t exist." UserInfo=0x10518b320 {NSFilePath=file://localhost/Folder, NSUserStringVariant=(
Folder
), NSUnderlyingError=0x10515d5e0 "The operation couldn’t be completed. (OSStatus error -43.)"}
What is going on?!? If this isn't the best way to do it how can I access all the files inside of a folder?
Try using this method instead:
- (NSArray *)contentsOfDirectoryAtURL:(NSURL *)url includingPropertiesForKeys:(NSArray *)keys options:(NSDirectoryEnumerationOptions)mask error:(NSError **)error
You can just pass in the NSURL without having to convert it into a NSString. To give you an example of how you would use it, see below:
[[NSFileManager defaultManager] contentsOfDirectoryAtURL:filePathURL
includingPropertiesForKeys:[NSArray arrayWithObjects:NSURLNameKey, nil]
options:NSDirectoryEnumerationSkipsHiddenFiles
error:&error];
I can't see how you setup your NSOpenPanel so I will also include an example of how to set that up below:
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[openPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result){
if (result == NSFileHandlingPanelOKButton) {
NSArray* urls = [openPanel URLs];
NSURL *url = [urls objectAtIndex:0];
if (url != nil) {
// If you want to convert the path to a NSString
self.filePathString = [url path];
// If you want to keep the path as a NSURL
self.filePathURL = url;
}
}
}];
The above method will get the path to the file or folder after the user has pressed the OK button. Give that a try and see if it works. To further elaborate on why I suggested you use NSURL, here is the explanation that the Apple Documentation gives:
The preferred way to specify the location of a file or directory is to use the NSURL class. Although the NSString class has many methods related to path creation, URLs offer a more robust way to locate files and directories. For applications that also work with network resources, URLs also mean that you can use one type of object to manage items located on a local file system or on a network server.
Related
I need to give full read/write permission for a directory where the application write some file to this directory. I read that using sandboxed application it required to Enable Security-Scoped Bookmark and URL Access to access a folder after relaunch the app.
So I am trying to implement it based on the code here with some minor modification What is the correct way to handle stale NSURL bookmarks?
NSOpenPanel* openDlg = [NSOpenPanel openPanel];
[openDlg setCanChooseDirectories:YES];
[openDlg setCanCreateDirectories:YES];
[openDlg setAllowsMultipleSelection:FALSE];
if ( [openDlg runModal] == NSOKButton )
{
NSArray *files = [openDlg URLs];
NSString* dirPath =[[files objectAtIndex:0] path];// absoluteString];
BOOL isDirectory;
NSFileManager* manager = [NSFileManager defaultManager];
NSString *Dir = [dirPath stringByAppendingPathComponent:#"ScreenCaptures"];
if (![manager fileExistsAtPath:Dir isDirectory:&isDirectory] || !isDirectory)
{
NSError *error = nil;
[manager createDirectoryAtPath:Dir
withIntermediateDirectories:NO
attributes:nil
error:&error];
if (error)
NSLog(#"Error creating directory snap path: %#", [error localizedDescription]);
}
NSURL *url = [NSURL URLWithString:[Dir stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSData *bookmark = nil;
NSError *error = nil;
bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:nil
relativeToURL:nil // Make it app-scoped
error:&error];
if (error) {
NSLog(#"Error creating bookmark for URL (%#): %#", url, error);
[NSApp presentError:error];
}
NSLog(#"bookmark: %#", bookmark);
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:bookmark forKey:#"bookmark"];
}
But the above code giving me the error
016-08-20 02:19:53.390 FileAccess[635:85753] modalSession has been exited prematurely - check for a reentrant call to endModalSession:
2016-08-20 02:19:59.979 FileAccess[635:85753] Error creating bookmark for URL (/Users/development/Documents/c/ScreenCaptures): Error Domain=NSCocoaErrorDomain Code=262 "Scoped bookmarks can only be made with file URLs" UserInfo={NSURL=/Users/development/Documents/c/ScreenCaptures, NSDebugDescription=Scoped bookmarks can only be made with file URLs}
2016-08-20 02:20:00.021 FileAccess[635:85753] CFURLCopyResourcePropertyForKey failed because it was passed an URL which has no scheme
2016-08-20 02:20:04.967 FileAccess[635:85753] bookmark: (null)
What could be the problem?, anything wrong on above code.
Your second error message tells you what is wrong - you haven't used a file:// URL.
This can be fixed by creating the URL properly from your path variable, however you will probably be better of sticking with URLs throughout and not doing the URL -> path -> URL transformation. All the operations you've used the path for can be done directly with URLs, just check the documentation for NSFileManager and NSURL. The only one which may be non-obvious is using NSURL's checkResourceIsReachableAndReturnError: rather than NSFileManager's fileExistsAtPath:, however read the documentation for checkResourceIsReachableAndReturnError: carefully and take its advice.
Making these changes should address at least three of the errors you have reported.
HTH
I've made an app that is relying on reading and writing a plist-file. This works well when I'm running the app in the iPhone simulator, but doesn't work at all when I'm testing it on my iPhone. I've also made a pre made text file in .txt format with demo data. The app works when I'm running this file.
All the reading and writing is done in a class that looks like this:
-(void)saveArray:(NSMutableArray *)inputArray
{
albumArray = inputArray;
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentFolder = [path objectAtIndex:0];
NSString *filePath = [documentFolder stringByAppendingFormat:#"albums.plist"];
[albumArray writeToFile:filePath atomically:YES];
}
Update: Changed the string from "stringByAppendingFormat" to "stringByAppendingPathComponent" and it seems to work now. Thanks a lot! You guys made my day made.
Are you sure, that the folders already exist?
Here is a function i'm using to get the path to my file:
- (NSString*) pathToSavedAlbums
{
NSURL *applicationSupportURL = [self applicationDataDirectory];
if (! [[NSFileManager defaultManager] fileExistsAtPath:[applicationSupportURL path]])
{
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:[applicationSupportURL path]
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error)
{
NSLog(#"error creating app support dir: %#", error);
}
}
NSString *path = [[applicationSupportURL path] stringByAppendingPathComponent:#"albums.plist"];
return path;
}
Check the spelling of the plist name as well as the case, device is case sensitive for docs but simulator isn't. Also try deleting the app from the device and reinstalling it ?
writeToFile:atomically: returns a bool, so check that to see if it fails to even write to the path. Check the file path string and ensure this is where you want it to go.
I would like to know how can I get the total amount of archives inside of a directory, for example desktop.
I don't just want to know what's inside of the root of the directory, but also inside of its subfolders.
To just get the archives on the root of desktop I can do the following:
NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
NSUInteger numberOfFileInFolder = [directoryContent count];
But I need to get also the count of its subfolders.
Can somebody help me?
Edit:
Finally I have coded this way:
-(int) numberOfDocumentsInPath: (NSString *) path{
NSFileManager *manager = [[NSFileManager alloc] init];
NSDirectoryEnumerator* totalSubpaths = [manager enumeratorAtPath: path];
NSLog(#"Path %# has %d documents", path, (int)[[totalSubpaths allObjects] count]);
return (int)[[totalSubpaths allObjects] count];
}
Try this:
NSDirectoryEnumerator *subs = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:FolderPath error:nil];
Straight from the docs:
If you need to recurse into subdirectories, use
enumeratorAtURL:includingPropertiesForKeys:options:errorHandler: as
shown in “Using a Directory Enumerator”).
Check also here:
Using a Directory Enumerator
Swift version:
var subs = NSFileManager.defaultManager().subpathsOfDirectoryAtPath(path, error: nil) as! [String]
var filecount = subs.count
println(filecount)
for sub in subs {
//Do stuff with files
}
I've only been leaning Cocoa/Objective C for a few days so apologies that this is probably simple/obvious but it's got me stumped.
I've written this handler for saving 3 floats to a text file. However when I'm running it the files are not being saved. Could anyone suggest if there's an error in my code or if you think there's something else (like file write permissions) preventing the file from being written.
Research has lead me to look into Sandboxing, but that gets confusing very quickly and I'm hoping just running the app from xcode in debug would let me write to my user directory.
Heres the code:
- (IBAction)saveResultsAction:(id)sender {
//Sets up the data to save
NSString *saveLens = [NSString stringWithFormat:#"Screen width is %.02f \n Screen Height is %.02f \n Lens is %.02f:1",
self.myLens.screenWidth,
self.myLens.screenHeight,
self.myLens.lensRatio];
NSSavePanel *save = [NSSavePanel savePanel];
long int result = [save runModal];
if (result == NSOKButton) {
NSURL *selectedFile = [save URL];
NSLog(#"Save URL is %#", selectedFile);
NSString *fileName = [[NSString alloc] initWithFormat:#"%#.txt", selectedFile];
NSLog(#"Appended URL is %#", fileName);
[saveLens writeToFile:fileName
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
}
}
a NSURL object is no POSIX path..
its a URL and getting its description doesnt make it a path
NSString *fileName = [selectedFile.path stringByAppendingPathExtension:#"txt"];
BUT as said, you shouldnt have to append the .txt at all. just use what the panel returns. Else, there would be sandboxd errors because you dont have access rights to the modified filename :)
NSString *fileName = selectedFile.path;
The problem is that you don't need to append the file extension to the URL.The extension is already there.You could directly do this:
if (result == NSOKButton)
{
[saveLens writeToURL: [save URL]
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
}
I see you've already accepted an answer, but it may also be helpful to know how to debug this type of issue using NSError pointers.
Cocoa uses NSError with method calls which generate error conditions, which richly encapsulate errors. (Objective-C also has exceptions, but they're reserved for cases of programmer error, like an array index out of bounds, or a nil parameter that should never be.)
When you have a method which accepts an error pointer, usually it also return a BOOL indicating overall success or failure. Here's how to get more information:
NSError *error = nil;
if (![saveLens writeToFile:fileName
atomically:YES
encoding:NSUTF8StringEncoding
error:&error]) {
NSLog(#"error: %#", error);
}
Or even:
NSError *error = nil;
if (![saveLens writeToFile:fileName
atomically:YES
encoding:NSUTF8StringEncoding
error:&error]) {
[NSApp presentError:error];
}
I seem to have stumbled over a problem regarding saving an xml file from a string (this is done on the iPhone)
The file itself exists and included in the project (hence within the workspace), and all indications I get from the code snippet which follows passes without any errors on the emulator and fail on the iPhone (error 513), but in either case the file is not saved!
{
Hits = config->Hits;
NSString* filenameStr = [m_FileName stringByAppendingFormat: #".xml" ];
NSString* pData = [self getDataString]; // write xml format - checked out ok
NSError *error;
/* option 2 - does not work as well
NSBundle *mainBundle = [NSBundle mainBundle];
NSURL *xmlURL = [NSURL fileURLWithPath:[mainBundle pathForResource: m_FileName ofType: #"xml"]];
if(![pData writeToURL: xmlURL atomically: true encoding:NSUTF8StringEncoding error:&error])
{
NSLog(#"Houston - we have a problem %s#\n",[error localizedFailureReason]);
return false;
}
*/
if(![pData writeToFile: filenameStr atomically: FALSE encoding:NSUTF8StringEncoding error:&error])
{
NSLog(#"Houston - we have a problem %s#\n",[error localizedFailureReason]);
return false;
}
return true;
}
Any help would be appreciated,
-A
You should not write to files included in the application package. On a real iPhone, you may be prevented from doing this because these files are digitally signed.
Even if you can modify a packaged file, it is not a good place to store data. Re-installing the application from an App Store upgrade or an Xcode build will overwrite the file with the original.
Instead, store your XML into the Documents directory. You can get the path like this:
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString* documentsDirectory = [paths objectAtIndex:0];
NSString* leafname = [m_FileName stringByAppendingFormat: #".xml" ];
NSString* filenameStr = [documentsDirectory
stringByAppendingPathComponent:leafname];
If your file needs some initial state that you don't want to generate in your code, have your app check that it is present in the documents directory the first time it is needed and, if it is missing, copy it from the template in the package.
An alternative to storing structured data is to use user defaults. For example:
[[NSUserDefaults standardUserDefaults] setObject:foo forKey:FOO_KEY];