How to get the path of user's special folder in macOS? - objective-c

Is there any API to get the user's special folder, such as the Downloads or Documents folder in macOS?

As per my exprience I am generally use to get Home directory path as per below example.
1st Way :
NSArray * paths = NSSearchPathForDirectoriesInDomains (NSDesktopDirectory, NSUserDomainMask, YES);
NSString * desktopPath = [paths objectAtIndex:0];
For Desktop : NSDesktopDirectory
For Document : NSDocumentDirectory
For Downloads : NSDownloadsDirectory
2nd Way:
Not the best way but we can get path by this method also.
Use Localization for different languages.
$Home Directory of Current User:
[NSURL fileURLWithPath:NSHomeDirectory()];
$Home/Desktop
[NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingPathComponent:#"Desktop"]];
$Home/Documents :
[NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"]];
$Home/Downloads :
[NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingPathComponent:#"Downloads"]];
Hope this will help you to find special folder path.

As already mentioned by others, a more modern approach is to retrieve a URL by using NSFileManager:
NSURL *desktopUrl = [NSFileManager.defaultManager URLsForDirectory:NSDesktopDirectory inDomains:NSUserDomainMask].firstObject;

desktop for Swift 5.x:
var DesktopDir : String = {
let paths = NSSearchPathForDirectoriesInDomains(.desktopDirectory, .userDomainMask, true)
return paths[0]
}()

Related

URL Containing forward slash doesn't work with NSWorkSpace

There exists a file name with forward slash(/) in the system.
For example: URL -> ~/Documents/FolderName/TestFilename/myFile.dmg.
Last Path Component is -> "TestFilename/myFile.dmg"
File Name is -> "TestFilename/myFile.dmg"
Now when i use the below code in my application to reveal in finder kind of stuff with the following one. It fails to does revealing in finder.
NSURL *fileURL = [NSURL URLWithString:#"/Documents/FolderName/TestFilename/myFile.dmg"];
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:#[fileURL]];
Now how do get this resolved for such case and make it revealing in finder for such files. I do have tried with "CFURLCreateStringByAddingPercentEscapes", it doesn't seems to be work. .
This can be achieved if you can segregate file name and file location in your code.
Below is the sample code:
NSString *location = #"Users/Desktop";
NSString *fileName = #"TestFilename/myFile.dmg";
if ([fileName rangeOfString:#"/"].location != NSNotFound)
{
fileName = [fileName stringByReplacingOccurrencesOfString:#"/" withString:#":"];
}
[[NSWorkspace sharedWorkspace] selectFile:[location stringByAppendingPathComponent:fileName] inFileViewerRootedAtPath:location];

Cannot Find File with NSFileManager

Im trying to find a file with NSFileManager. The file exists but my path is never correct no matter how its phrased. The code I'm using is below. Why is NSFileManager not finding the file?
NSString *myFile = #"file1658.pdf";
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:myFile]) {
NSLog(#"good");
}
MyFile is not a full path (e.g., "/Users/Joe/Documents/file1658.pdf"), so NSFileManager is looking for it in the current working directory. You can see what that is with -[NSFileManager currentDirectoryPath].
You need to either include the full path to the file as part of myFile, or set the proper working directory with -[NSFileManager changeCurrentDirectoryPath:].
// If myFile is in "/Users/joe/Documents"...
[fileManager changeCurrentDirectoryPath:#"/Users/joe/Documents"];
if ([fileManager fileExistsAtPath:myFile]) {
NSLog(#"good");
}
Apple tries hard to get you to put your files in sensible places. The file my exist, but you will have to either give the full path, e.g. /Users/user2759189/file1658.pdf or specify what folder it is in by other means.
App bundle
If the file is in your app bundle (for example, you have added it using "Add files to [project]" in XCode) you can get its path by something like:
NSString *myFile = [[NSBundle mainBundle] pathForResource:#"file1658" ofType:#"pdf"];
Search paths
You can look for user documents in a civilised manner by using the foundation function NSSearchPathForDirectoriesInDomains and some built in constants. If the file is in the user's Documents folder, for example, you can use something like:
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *myFile = [documentsPath stringByAppendingPathComponent:#"file1658.pdf"];

Checking if file exists when file on an external device

A playlist file .m3u contains entries available on an external device (a USB key in this case) such as:
/Volumes/KINGSTON/folder/mytitle.mp3
I'd like to check if the file exists:
NSURL *url = [NSURL URLWithString:#"/Volumes/KINGSTON/folder/mytitle.mp3"];
NSFileManager *manager = [NSFileManager defaultManager];
NSLog(#"%d",[manager fileExistsAtPath:[url absoluteString]]); //returns 0. I expect 1
I also tried:
NSURL *u = [[NSURL alloc]initWithScheme:#"/Volumes" host:#"/KINGSTON" path:#"/folder/mytitle.mp3"];
NSLog(#"%d",[manager fileExistsAtPath:[u absoluteString]]); //0
What did I do wrong?
Thanks,
Roland
NSURL *url = [NSURL URLWithString:#"/Volumes/KINGSTON/folder/mytitle.mp3"];
That string does not describe a URL. It's a pathname. Use fileURLWithPath:.
NSLog(#"%d",[manager fileExistsAtPath:[url absoluteString]]);
absoluteString does not return a path; it returns a string describing a URL. Use path.
Or, better yet, use checkResourceIsReachableAndReturnError:.
I also tried:
NSURL *u = [[NSURL alloc]initWithScheme:#"/Volumes" host:#"/KINGSTON" path:#"/folder/mytitle.mp3"];
/Volumes isn't a scheme, /KINGSTON isn't a host, and /folder/mytitle.mp3 is a path but does not refer to anything that exists.
The scheme for a file URL is file:, and the host is generally either localhost or the empty string. The path of a file URL is the complete absolute path to the file.
In your first example, you need to use +[NSURL fileURLWithPath:]. In your second example, I see what you're trying to do, but you're simply going about it the wrong way.
I assume there's a reason you're bothering with NSURL when you have the path you could pass directly to -fileExistsAtPath:?

Where can a sandboxed Mac app save files?

My Mac app is sandboxed and I need to save a file. Where do I save this file? I can't seem to find the specific place where this is allowed without using an open panel. This is how I do it on iOS:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
What is the equivalent for the sandboxed directory on Mac?
That code snippet works on the Mac regardless of whether your application is sandboxed.
In a non-sandboxed Mac app, path will refer to the Documents folder in your home: e.g. /Users/username/Documents.
In a sandboxed app, it refers to a folder within the app sandbox: e.g. /Users/username/Library/Containers/com.yourcompany.YourApp/Documents
See the docs for details.
Apple's Sandboxing guide is very useful, found here.
You basically have a folder dedicated for your app, as described by theAmateurProgrammer in reply to my question here.
~/Library/Container/com.yourcompany.yourappname/
Here is what I have so far, I will improve it later:
//Create App directory if not exists:
NSFileManager* fileManager = [[NSFileManager alloc] init];
NSString* bundleID = [[NSBundle mainBundle] bundleIdentifier];
NSArray* urlPaths = [fileManager URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask];
NSURL* appDirectory = [[urlPaths objectAtIndex:0] URLByAppendingPathComponent:bundleID isDirectory:YES];
//TODO: handle the error
if (![fileManager fileExistsAtPath:[appDirectory path]]) {
[fileManager createDirectoryAtURL:appDirectory withIntermediateDirectories:NO attributes:nil error:nil];
}
Converting #Mazyod's answer into Swift (5.1):
var appPath: URL? {
//Create App directory if not exists:
let fileManager = FileManager()
let urlPaths = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)
if let bundleID = Bundle.main.bundleIdentifier, let appDirectory = urlPaths.first?.appendingPathComponent(bundleID,isDirectory: true) {
var objCTrue: ObjCBool = true
let path = appDirectory.path
if !fileManager.fileExists(atPath: path,isDirectory: &objCTrue) {
do {
try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
} catch {
return nil
}
}
return appDirectory
}
return nil
}
However, the directory has changed and I am not sure that the additonal repetition of the bundle ID is needed as the path is
"/Users/**user name**/Library/Containers/**bundleID**/Data/Library/Application Support/**bundleID**".
But it seems to work.
Is is even easier. For sandboxed apps on macOS the function NSHomeDirectory gives you the path where you have read and write access and can save all your files. It will be a path like this
/Users/username/Library/Containers/com.yourcompany.YourApp

Embedding XML to iOS application

I'm making my first game in iOS and I need to load some levels that are stored as XML. Level files are stored locally and everything works fine from emulator. However, since XML is loaded in the runtime when i try to test my game on an actual device it can't find the XML files since they are not actually part of the app.
I'm coming from Flash background so I might have a wrong idea how this is done on iOS but I need to somehow bundle those XML files with the app, or embed it in the code somehow? Is this possible?
Thanks a lot :)
Well The code to look up your app bundle for the specified file is
NSString *path = [[NSBundle mainBundle] pathForResource:#"fileName.xml" ofType:nil]
NSURL *xmlURL = [NSURL fileURLWithPath:path];
[[NSXMLParser alloc] initWithContentsOfURL: xmlURL]
Hope this helps you...
You could add the XML file to your project and run time read it up with something like this
NSArray *myPathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *myPath = [myPathList objectAtIndex:0];
NSError **err;
myPath = [myPath stringByAppendingPathComponent:fileName];
if([[NSFileManager defaultManager] fileExistsAtPath:myPath])
text = [NSString stringWithContentsOfFile:myPath encoding:NSUTF8StringEncoding error:err];
You could consider using JSON in instead of XML. At least to my knowledge the available XML parsers are not nearly as simply to use as SBJson for instance (https://github.com/stig/json-framework/)
Here is my solution in Swift 3.0. In addition to the code, you'll need to add your xml file as a data set in your Assets.xcassets. Do this by creating a new Data Set, giving it any name, and then dragging the xml file from the finder to your newly created data set. When you reference the file in your code, you'll use the file name of the file, and not the name of the data set.
var parseResults = false
if let path = Bundle.main.path(forResource: fileName, ofType: ".xml") {
let url = URL(fileURLWithPath: path)
if let xmlParser = XMLParser(contentsOf: urlToFile) {
xmlParser.delegate = self
parseResults = xmlParser.parse()
}
}