Locating the macOS application resources directory: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: Locating the macOS application resources directory
| Old | New | Line | |
|---|---|---|---|
| 1 | + | *(macOS only)* | |
| 2 | + | ||
| 3 | + | Resources are data files that live outside of your application’s executable file. The resources directory is where you should store these files (eg your image files, sound files, icon files and other data files necessary for your application's operation). The contents of this directory may be further divided into subdirectories where you can store localized and non-localized versions of your resource files. | |
| 4 | + | ||
| 5 | + | ## macOS File Storage Overview | |
| 6 | + | ||
| 7 | + | ## Application Resources Directory | |
| 8 | + | ||
| 9 | + | The basic structure of the [[Application Bundle|application bundle]] is: | |
| 10 | + | ||
| 11 | + | MyApp.app/ | |
| 12 | + | Contents/ | |
| 13 | + | Info.plist | |
| 14 | + | MacOS/ | |
| 15 | + | Resources/ | |
| 16 | + | ||
| 17 | + | A more complex application bundle layout can be seen in my [[Add an Apple Help Book to your macOS app|Apple Help Book article]]. | |
| 18 | + | ||
| 19 | + | ## Using a CFBundle object | |
| 20 | + | ||
| 21 | + | CFBundle allows you to use a folder hierarchy called a bundle to organize and locate many types of application resources including images, sounds, localized strings, and executable code. | |
| 22 | + | ||
| 23 | + | Set out below are two ways to find the location of the Resources directory using the Core Foundation bundle object. | |
| 24 | + | ||
| 25 | + | The first way is to retrieve the bundle directory (ie MyApp.app/) and concatenate the standard Contents and Resources directories to create the full Resources directory path like so: | |
| 26 | + | ||
| 27 | + | ``` pascal | |
| 28 | + | ... | |
| 29 | + | ||
| 30 | + | {$modeswitch objectivec1} | |
| 31 | + | ||
| 32 | + | interface | |
| 33 | + | ||
| 34 | + | Uses | |
| 35 | + | MacOSAll; | |
| 36 | + | ||
| 37 | + | ... | |
| 38 | + | ||
| 39 | + | function GetBundlePath(): string; | |
| 40 | + | var | |
| 41 | + | pathRef: CFURLRef; | |
| 42 | + | pathCFStr: CFStringRef; | |
| 43 | + | pathStr: shortstring; | |
| 44 | + | status: Boolean = false; | |
| 45 | + | begin | |
| 46 | + | pathRef := CFBundleCopyBundleURL(CFBundleGetMainBundle()); | |
| 47 | + | pathCFStr := CFURLCopyFileSystemPath(pathRef, kCFURLPOSIXPathStyle); | |
| 48 | + | ||
| 49 | + | status := CFStringGetPascalString(pathCFStr, @pathStr, 255, CFStringGetSystemEncoding()); | |
| 50 | + | ||
| 51 | + | if(status = true) then | |
| 52 | + | Result := pathStr | |
| 53 | + | else | |
| 54 | + | raise Exception.Create('Error in GetBundlePath()'); | |
| 55 | + | end; | |
| 56 | + | ||
| 57 | + | ... | |
| 58 | + | ||
| 59 | + | procedure TForm1.MenuItem1Click(Sender: TObject); | |
| 60 | + | begin | |
| 61 | + | ShowMessage(GetBundlePath + PathDelim + 'Contents' + PathDelim + 'Resources' + PathDelim); | |
| 62 | + | end; | |
| 63 | + | ``` | |
| 64 | + | ||
| 65 | + | The second way is to actually interrogate the application bundle for the full path to its Resources directory without the need to concatenate any directories as shown here: | |
| 66 | + | ||
| 67 | + | ``` pascal | |
| 68 | + | ... | |
| 69 | + | ||
| 70 | + | {$modeswitch objectivec1} | |
| 71 | + | ||
| 72 | + | interface | |
| 73 | + | ||
| 74 | + | Uses | |
| 75 | + | ... | |
| 76 | + | MacOSAll, | |
| 77 | + | CocoaAll; | |
| 78 | + | ||
| 79 | + | ... | |
| 80 | + | ||
| 81 | + | function GetResourcesPath(): string; | |
| 82 | + | var | |
| 83 | + | pathStr: shortstring; | |
| 84 | + | status: Boolean = false; | |
| 85 | + | begin | |
| 86 | + | status := CFStringGetPascalString(CFStringRef(NSBundle.mainBundle.resourcePath), @pathStr, 255, CFStringGetSystemEncoding()); | |
| 87 | + | ||
| 88 | + | if(status = true) then | |
| 89 | + | Result := pathStr + PathDelim | |
| 90 | + | else | |
| 91 | + | raise Exception.Create('Error in GetResourcesPath()'); | |
| 92 | + | end; | |
| 93 | + | ||
| 94 | + | ... | |
| 95 | + | ||
| 96 | + | procedure TForm1.MenuItem1Click(Sender: TObject); | |
| 97 | + | begin | |
| 98 | + | ShowMessage(GetResourcesPath); | |
| 99 | + | end; | |
| 100 | + | ``` | |
| 101 | + | ||
| 102 | + | If you just want to retrieve the directory in which your application bundle is located, it is a simple matter of extracting it from the `GetBundlePath()` function above like this: | |
| 103 | + | ||
| 104 | + | ``` pascal | |
| 105 | + | ... | |
| 106 | + | ||
| 107 | + | Uses | |
| 108 | + | SysUtils, | |
| 109 | + | ... | |
| 110 | + | ||
| 111 | + | ... | |
| 112 | + | ||
| 113 | + | procedure TForm1.MenuItem1Click(Sender: TObject); | |
| 114 | + | begin | |
| 115 | + | ShowMessage(ExtractFilePath(GetBundlePath)); | |
| 116 | + | end; | |
| 117 | + | ``` | |
| 118 | + | ||
| 119 | + | ## Using an NSBundle object | |
| 120 | + | ||
| 121 | + | Apple uses bundles to represent applications, frameworks, plug-ins, and many other specific types of content. Bundles organize their contained resources into well-defined subdirectories, and bundle structures vary depending on the platform and the type of the bundle. By using a bundle object, you can access a bundle's resources without knowing the structure of the bundle. The bundle object provides a single interface for locating items, taking into account the bundle structure, user preferences, available localizations, and other relevant factors. Any executable can use a bundle object to locate resources, either inside an application’s bundle or in a known bundle located elsewhere. You don't use a bundle object to locate files in a container directory or in other parts of the file system. | |
| 122 | + | ||
| 123 | + | The unit below demonstrates how to find the application bundle directory, the bundle resources directory and various other bundle paths using the Foundation NSBundle object. | |
| 124 | + | ||
| 125 | + | ``` pascal | |
| 126 | + | unit Unit1; | |
| 127 | + | ||
| 128 | + | {$mode objfpc}{$H+} | |
| 129 | + | {$modeswitch objectivec1} | |
| 130 | + | ||
| 131 | + | interface | |
| 132 | + | ||
| 133 | + | uses | |
| 134 | + | Classes, | |
| 135 | + | Forms, | |
| 136 | + | Dialogs, // Needed for ShowMessage | |
| 137 | + | StdCtrls, // Needed for Buttons | |
| 138 | + | SysUtils, // Needed for pathdelimiter | |
| 139 | + | CocoaAll, // Needed for NSBundle | |
| 140 | + | CocoaUtils; // Needed for NSStringToString | |
| 141 | + | ||
| 142 | + | type | |
| 143 | + | ||
| 144 | + | { TForm1 } | |
| 145 | + | ||
| 146 | + | TForm1 = class(TForm) | |
| 147 | + | Button1: TButton; | |
| 148 | + | Button2: TButton; | |
| 149 | + | Button3: TButton; | |
| 150 | + | Button4: TButton; | |
| 151 | + | Button5: TButton; | |
| 152 | + | Button6: TButton; | |
| 153 | + | Button7: TButton; | |
| 154 | + | procedure Button1Click(Sender: TObject); | |
| 155 | + | procedure Button2Click(Sender: TObject); | |
| 156 | + | procedure Button3Click(Sender: TObject); | |
| 157 | + | procedure Button4Click(Sender: TObject); | |
| 158 | + | procedure Button5Click(Sender: TObject); | |
| 159 | + | procedure Button6Click(Sender: TObject); | |
| 160 | + | procedure Button7Click(Sender: TObject); | |
| 161 | + | private | |
| 162 | + | ||
| 163 | + | public | |
| 164 | + | ||
| 165 | + | end; | |
| 166 | + | ||
| 167 | + | var | |
| 168 | + | Form1: TForm1; | |
| 169 | + | ||
| 170 | + | implementation | |
| 171 | + | ||
| 172 | + | {$R *.lfm} | |
| 173 | + | ||
| 174 | + | Function GetMainBundlePath: string; | |
| 175 | + | begin | |
| 176 | + | Result := NSStringToString(NSBundle.mainBundle.bundlePath); | |
| 177 | + | end; | |
| 178 | + | ||
| 179 | + | function GetExecutablePath : string; | |
| 180 | + | begin | |
| 181 | + | Result := NSStringToString(NSBundle.mainBundle.executablePath); | |
| 182 | + | end; | |
| 183 | + | ||
| 184 | + | function GetResourcesPath : string; | |
| 185 | + | begin | |
| 186 | + | Result := NSStringToString(NSBundle.mainBundle.resourcePath) + PathDelim; | |
| 187 | + | end; | |
| 188 | + | ||
| 189 | + | Function GetSoundResourcePath(Sound: NSString): string; | |
| 190 | + | begin | |
| 191 | + | Result := NSStringToString(NSBundle.mainBundle.pathForSoundResource(Sound)); | |
| 192 | + | end; | |
| 193 | + | ||
| 194 | + | Function GetBundleIdentifier : string; | |
| 195 | + | begin | |
| 196 | + | Result := NSStringToString(NSBundle.mainBundle.bundleIdentifier); | |
| 197 | + | end; | |
| 198 | + | ||
| 199 | + | Function GetSharedSupportPath : string; | |
| 200 | + | begin | |
| 201 | + | Result := NSStringToString(NSBundle.mainBundle.sharedSupportPath); | |
| 202 | + | end; | |
| 203 | + | ||
| 204 | + | Function GetPathForImageResource(Image: NSString) : string; | |
| 205 | + | begin | |
| 206 | + | Result := NSStringToString(NSBundle.mainBundle.pathForImageResource(Image)); | |
| 207 | + | end; | |
| 208 | + | ||
| 209 | + | { TForm1 } | |
| 210 | + | ||
| 211 | + | procedure TForm1.Button1Click(Sender: TObject); | |
| 212 | + | begin | |
| 213 | + | ShowMessage(GetExecutablePath); | |
| 214 | + | end; | |
| 215 | + | ||
| 216 | + | procedure TForm1.Button2Click(Sender: TObject); | |
| 217 | + | begin | |
| 218 | + | ShowMessage(GetResourcesPath); | |
| 219 | + | end; | |
| 220 | + | ||
| 221 | + | procedure TForm1.Button3Click(Sender: TObject); | |
| 222 | + | begin | |
| 223 | + | ShowMessage(GetMainBundlePath); | |
| 224 | + | end; | |
| 225 | + | ||
| 226 | + | procedure TForm1.Button4Click(Sender: TObject); | |
| 227 | + | begin | |
| 228 | + | // Find path to the Glass.aiff sound file in the resources directory | |
| 229 | + | ShowMessage(GetSoundResourcePath(NSStr('Glass'))); | |
| 230 | + | end; | |
| 231 | + | ||
| 232 | + | procedure TForm1.Button5Click(Sender: TObject); | |
| 233 | + | begin | |
| 234 | + | ShowMessage(GetBundleIdentifier); | |
| 235 | + | end; | |
| 236 | + | ||
| 237 | + | procedure TForm1.Button6Click(Sender: TObject); | |
| 238 | + | begin | |
| 239 | + | ShowMessage(GetSharedSupportPath); | |
| 240 | + | end; | |
| 241 | + | ||
| 242 | + | procedure TForm1.Button7Click(Sender: TObject); | |
| 243 | + | begin | |
| 244 | + | // Find path to world1.png in resources directory | |
| 245 | + | ShowMessage(GetPathForImageResource(NSStr('world1'))); | |
| 246 | + | end; | |
| 247 | + | ||
| 248 | + | end. | |
| 249 | + | ``` | |
| 250 | + | ||
| 251 | + | You can also simplify the above by omitting the CocoaUtils unit (losing access to NSStringToString) and use the UTF8String function. For example: | |
| 252 | + | ||
| 253 | + | ``` pascal | |
| 254 | + | function GetExecutablePath : string; | |
| 255 | + | begin | |
| 256 | + | //Result := NSStringToString(NSBundle.mainBundle.executablePath); | |
| 257 | + | Result := NSBundle.mainBundle.executablePath.UTF8String; | |
| 258 | + | end; | |
| 259 | + | ``` | |
| 260 | + | ||
| 261 | + | ## See also | |
| 262 | + | ||
| 263 | + | - [[Locating macOS significant directories]] | |
| 264 | + | - [[Locating macOS app support, preferences folders]] | |
| 265 | + | - [[Locating the macOS tmp directory]] | |
| 266 | + | ||
| 267 | + | ## External links | |
| 268 | + | ||
| 269 | + | - [Apple: Bundle Programming Guide](https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html). | |
| 270 | + | - [Apple: Core Foundation CFBundle Overview](https://developer.apple.com/documentation/corefoundation/cfbundle-s0a). | |
| 271 | + | - [Apple: NSBundle class](https://developer.apple.com/documentation/foundation/nsbundle). | |
| 272 | + | ||
| 273 | + | --- | |
| 274 | + | *From the Free Pascal wiki, page "Locating the macOS application resources directory", as it stood on 2021-03-26 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* | |
| 275 | + | Category: Code Snippets | |
| 276 | + | Category: macOS |
