There was a question on the ADUG list last week about how to retrieve “special folder” locations on OS X. By special folder, I mean locations like the user’s Home directory, the Documents directory, Temp directory, etc. I thought I’d write up the solution both because it’s probably something that more people will be wondering and also because it’s a nice little introduction to calling out to the OS X API.
If you want either the path to the Home or the Temp directory, this is ridiculously easy. The IOUtils unit already contains TPath.GetTempPath and TPath.GetHomePath, and these work on both Windows and OS X.
However, if you want another directory, such as the Documents directory, you need to do a little more work.
I’m sure many of you have done this same thing on Windows, using the Windows API SHGetFolderPath, like this:
function GetDocumentDirectory : string; var szBuffer: array [0..MAX_PATH] of Char; begin OleCheck (SHGetFolderPath ( FmxHandleToHWND(Handle), CSIDL_MYDOCUMENTS, 0, 0, szBuffer)); Result := szBuffer; end;
As an aside, the first parameter to the SHGetFolderPath is a HWND. In VCL, you could just pass in the Handle of the form, however in FireMonkey the Window handle is a TFmxHandle, not a HWND. The Fmx.Platform.Win unit contains a FmxHandleToHWND function that, as the name suggests, will take your FireMOnkey handle and give you back a HWND.
So, back to the point, if this is how we do it in FireMonkey on Windows, how do we do it on OS X? Let’s have a look:
function GetDocumentDirectory : string; var FileMgr : NSFileManager; URL : NSURL; Error : NSError; begin FileMgr := TNSFileManager.Create; URL := FileMgr.URLForDirectory(NSDocumentDirectory, NSUserDomainMask, nil, false, Error); if Assigned(Error) then raise Exception.Create(Error.localizedDescription.UTF8String); Result := URL.path.UTF8String; end;
First thing we do is get a reference to a NSFileManager interface. It, and the TNSFileManager class that implements it, can be found in the Macapi.Foundation unit.
Once we have our NSFileManager reference, we can use the URLForDirectory method, which is roughly equivalent to the SHGetFolderPath call in the Windows example. We need to pass in a NSError variable and then check it to make sure everything worked OK. We raise an exception if not.
However, what we get back from URLForDirectory is not a string but a NSURL instance. In order to get a string containing the path, we use, not surprisingly, NSURL.Path. However, this also is not a String, it’s a NSString. How do we convert that to a string? Well, NSString.UTF8String is a quick, easy way to get back a UTF8 encoded string. If you want a different encoding, check out some of the other extraction methods on NSString. Update : Chris Rolliston has written a nice follow-up post digging into converting NSStrings to Delphi Strings in much more detail.
In the example project, once I have the full path of the Documents directory, I use the IOUtils classes to enumerate over the folders and files in that folder and add the names to a listbox:
var DocumentsPath, Filename : String; begin DocumentsPath := GetDocumentDirectory; Label1.Text := DocumentsPath; for Filename in TDirectory.GetDirectories(DocumentsPath) do Listbox1.Items.Add(Format('Folder : %s', [Filename])); for Filename in TDirectory.GetFiles(DocumentsPath) do Listbox1.Items.Add(Format('File : %s', [Filename])); end;
Here’s the resulting app on Windows:
and on OSX:
You can download the sample project from my delphi-samples repository on github.