I am developing a mac app and I want to put my settings (array of strings) into a file. If that file exists in the same folder as the app it is read and it overwrites default settings.
No problems writing the file but when reading with code:
NSArray* settingsInput = [[NSArray alloc] initWithContentsOfFile:#"./SettingsFile"];
something strange happens. When running the app from XCode (the settings file is in the build/debug folder next to the app) settings are read without a problem. When I archive the app and run it from the desktop, the file cannot be loaded. Even when I copy the app from the build folder to the desktop it does not work.
What could be the reason for this kind of behaviour? How can I read this file?
It may be a better Idea to use the normal prefence system. NSUserDefaults.
There a couple of ways you can do it.
But the idea is to give your app a set of default preference which are registered for you in the correct domain and always with a fresh app.
Using the registerDefaults: from NSUserDefaults.
See Apples documentation NSUserDefaults and its #registerDefaults
But the one I would use is :
Copy a plist file into the supporting files in you Xcode project.
Making sure "Copy files into destination group's folder" is checked. And the "Add to targets is check also"
The plist file should contain your array of strings.
(I created mine. By duplicating another plist in my user preferences. Renaming it. Copying it to the project. Selecting it and editing it to how I needed it. Making sure I use the file menu ->'Save' to save the changes. )
Declare a NSUserDefaults * prefs;
Now in the - (id)init method for the app. you register the contents of the file as your default preferences.
- (id)init
{
self = [super init];
if (self) {
prefs = [NSUserDefaults standardUserDefaults] ;
NSString *registerDefaultsPlistFile= [[NSBundle mainBundle] pathForResource:#"registerDefaults" ofType:#"plist"];
[prefs registerDefaults:[NSDictionary dictionaryWithContentsOfFile: registerDefaultsPlistFile]];
}
return self;
}
You can later make a call to read these preferences.
NSLog(#" arrayOfStrings = %#", [prefs objectForKey:#"arrayOfStrings" ]);
These default preferences are NOT written to file/out unless you make a change to them. By written to file I mean to the applications preference file. Once you do make a change to them then they will be written out into the users preferences and those are what will be used from then on.
You should not rely on the current directory of the app. You should either read from the app bundle (see NSBundle class for get the correct path) or the app's document directory (see NSSearchPathForDirectoriesInDomains(NSDocumentDirectory ...).
The UNIX concept of the current working directory is not commonly used in Mac desktop applications. When launching an app through the Finder it's usually set to the root directory of the boot volume.
You should find another way to determine locations for your settings files. Good spots would be ~/Library/Preferences or ~/Library/Application Support/MyApp. You can get the absolute path to these directories using:
NSString *path = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomain, YES)[0];
Related
In macOS apps, if you need to get the path of your app or any resources in its bundle, you can of course use the mainBundle static method of NSBundle:
NSBundle *bundle = [NSBundle mainBundle];
NSString *appPath = [bundle bundlePath];
NSString *resourceFolderPath = [bundle resourcePath];
... etc ...
However, if the user moves your app's bundle to a different location on disk while the app is running, then this method will not work. All of the above functions will still return the bundle's old paths from before the user moved it.
How can you get the current path of your bundle or the path of any resource inside of it regardless of whether the user has moved it on disk?
So far, I've tried using NSRunningApplication to get its current location like so:
NSRunningApplication *thisApp = [NSRunningApplication runningApplicationWithProcessIdentifier:getpid()];
NSLog(#"bundle URL: %#", [thisApp bundleURL]);
...but that too returns the old path of the bundle!
Moving an application that's already running typically isn't supported. If that's a scenario you really need to support then you can do the following:
When the app is first launched, get the app's current path as you would normally and then create an NSURL bookmark for that path.
Whenever you need to know the app's current path you can resolve the NSURL bookmark you created at launch. The bookmark will resolve to app's current path, even if the app has been moved.
More info on NSURL bookmarks is available here: https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html#//apple_ref/doc/uid/TP40010672-CH3-SW10
While macOS doesn't care if you move documents around as they're open, it does care if you move applications around. This is a long-recognized issue in macOS development.
Instead of trying to fight it, app developers tend to either not care, or ask users if they want to move the application to the Applications directory at launch (since this is the most common move destination for apps). LetsMove is a project that demonstrates how you would do that.
I want to use NSLocalizedString in my app but it always failed. What i do is:
Define 3 Localizations in Project Properties (See screenshot bellow)
Create a new file: Resource Strings File
Check in the app bundle if file.strings is there
Then I use NSLocalizedStrings as follow but it doesn't work!
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
NSString *v1 = NSLocalizedString(#"MyWindow", nil);
//NSString *v1 = [[NSBundle mainBundle] localizedStringForKey:(#"MyWindow") value:#"" table:nil];
[label setStringValue:v1];
}
In my 3 .strings files I define the below key/value:
"MyWindow" = "Ma Fenetre";
Normally, my label should display "Ma Fenetre" and not "MyWindows"
You can download an example project here and tell me where is the issue.
Bellow the content of Resources folder in my app bundle :
DerivedData om$ find test/Build/Products/Debug/test.app/Contents/Resources/
test/Build/Products/Debug/test.app/Contents/Resources/
test/Build/Products/Debug/test.app/Contents/Resources//de.lproj
test/Build/Products/Debug/test.app/Contents/Resources//de.lproj/File.strings
test/Build/Products/Debug/test.app/Contents/Resources//en.lproj
test/Build/Products/Debug/test.app/Contents/Resources//en.lproj/File.strings
test/Build/Products/Debug/test.app/Contents/Resources//fr.lproj
test/Build/Products/Debug/test.app/Contents/Resources//fr.lproj/File.strings
test/Build/Products/Debug/test.app/Contents/Resources//MainMenu.nib
Thanks
Elfoiros
NSLocalizedString uses Localizable.strings file by default. Change your File.strings name and try again.
For every time you make changes in .Strings file you need to clean your project and remove application from device and simulator. This is the only way to develop Localised application.
Have a happy coding.!
As Adam stated, NSLocalizedString uses Localizable.strings for a lookup table. To specify a custom table, use:
NSLocalizedStringFromTable(#"MyWindow", #"File");
Documentation
Need to take care over case sensitive file names: Localizable.strings not localizable.strings. Simply rename in Finder, delete reference in Xcode project and add Localizable string back to the project. Then make sure the appropriate Localization boxes are selected in the file inspector. Don't know if there's a better slicker process.
I had a similar issue as described here but the solution was different. Somehow I managed to mangle how the Localization.strings file was being referenced in the project.
Removing the Localizable.strings file and readding it fixed the problem for me.
I have a problem accessing my files in my app.
I am currently using
//Directly from TileMap example from WWDC2010
NSString *tileDirectory = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:#"Tiles"];
to access my tiles for my MKOverlay. This gives me this directory
/Users/xxxx/Library/Application Support/iPhone Simulator/4.2/Applications/9D62025C-C53B-472C-8309-xxxx/xxxx.app/Tiles
The x's is only for privacy reasons
I have my tiles in a folder called Tiles in the root of my application which is in Xcode in a group called Tiles which is in directly in the Resources group.
When I run my app, I get a simple error saying that it could not find my tiles at the generated directory (the one quote above) If I replace that piece of code and make it:
NSString *tileDirectory = #"/Users/xxxx/Documents/xxxx/Tiles";
Then my app works fine. This is obviously because it finds my tiles in its direct location on my Mac. This is fine for testing, but I need it to work on my iPhone/iPad.
This problem might be occurring due to:
The generated directory is incorrect.
The tile images aren't getting included in the builded .app file.
Either way, I have no clue of what to do to solve it.
How can I solve this problem?
[EDIT]
I changed that piece of code to:
NSString *tileDirectory = [[NSBundle mainBundle] resourcePath];
Now it works in simulator, because all files are in the apps root folder and I don't ask for it to enter another directory called "Tiles".
This runs with no error on the simulator, but when on my iPhone it gives the original error (just a different file path but also ending with /xxxx.app
How can I ensure a directory in my app file such as xxxx.app/Tiles - TileMap does this.
Since it is your files in your app bundle, I think you can use pathForResource:ofType: to get the full pathname of your file.
Here is an example:
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"your_file_name"
ofType:#"the_file_extension"];
Remember that the "folders/groups" you make in xcode, those which are yellowish are not reflected as real folders in your iPhone app. They are just there to structure your XCode project. You can nest as many yellow group as you want and they still only serve the purpose of organizing code in XCode.
EDIT
Make a folder outside of XCode then drag it over, and select "Create folder references for any added folders" instead of "Create groups for any added folders" in the popup.
If your tiles are not in your bundle, either copied from the bundle or downloaded from the internet you can get the directory like this
NSString *documentdir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *tileDirectory = [documentdir stringByAppendingPathComponent:#"xxxx/Tiles"];
NSLog(#"Tile Directory: %#", tileDirectory);
You need to use the URL for the link, such as this:
NSURL *path = [[NSBundle mainBundle] URLForResource:#"imagename" withExtension:#"jpg"];
It will give you a proper URL ref.
You need to add your tiles into your resource bundle. I mean add all those files to your project make sure to copy all files to project directory option checked.
I know how to read/write to local files on iOS using file handles. I can dynamically create and read/write form them.
What I'm trying to do right now is to include a file, about 200 lines long with the app bundle, which I hope to parse and extract configuration settings from. I really do not want to have to create an array of 200 numbers by hand.
How would I go about including a text file with the app, and making it accessible from my application?
Thank you!
You can include a file in the bundle by adding it to your Resources folder in Xcode and making sure that it is included in your target as well. To access a file in the bundle (e.g., a text file called info.txt), you can get the path using:
[[NSBundle mainBundle] pathForResource:#"info" ofType:#"txt"]
Then access it using the normal NSFileManager methods.
EDIT:
With this said, you can't write anything into this file inside the bundle on iOS. Instead, you can copy the file from the bundle to a file system location (say, your app's Application Support folder) on first launch, then access it there elsewhere in your app.
Why don't you create a dictionary with Property List Editor and load it like
NSString *configurationPath = [[NSBundle mainBundle] pathForResource: #"configuration" ofType: #"plist"];
NSDictionary *configuration = [NSDictionary dictionaryWithContentsOfFile: configurationPath];
So you can access its settings with
[configuration objectForKey: #"someSetting"];
If you want to write, I recommend registering that dictionary with NSUserDefaults so you could write to a copy like
[[NSUserDefaults standardUserDefaults] setObject: someSetting forKey: #"someSetting"];
My question is stated above in the title but I'll write it here again
Is it possible to copy a different plist into the application bundle code wise??
This is actually a bit related on my previous question ;) Create a dictionary property list programmatically
But since I can write out the plist (and test if I'm doing something wrong ) I was wondering if I can copy my written file
[testBook writeTofile:#/Users/jack/testbook2.plist" atomically:NO];
to my application bundle in code (so that it can read from here with
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *dataPath = [path stringbyAppendingPathcomponent:#"testbook2.plist"];
Another alternative is good as well for instance if I can read my plist directly from another path instead of the mainBundle
Physically, yes it is possible sometimes.
NO, DON'T DO THAT!!
Okay... copying stuff into your App Bundle is bad, your app bundle is not where you keep settings or data. You should think of your app bundle as readonly. You should do this for several reasons:
While most of the time a user on a Mac OS X system has permission to write to an app sometimes they don't. Think about children who are not using admin accounts, or lab deployments at schools.
It will invalidate codesigning. If you sign your application this will change the signature and break the codesign, causing all sorts of weird issues (losing any firewall permissions the app has, telling the user the app has changed and asking them for keychain permission again, etc). In the future this may also pop up all sorts of warnings since tampering with an app is a big security red flag.
It won't work on iPhone. That may not be an issue, but if you every intend to use this code on an iPhone the apps bundles are sandboxed readonly
The correct place to put this sort of stuff is into a sub folder of the Application Support folder
I don't know exactly what you're doing, but depending on your application, you might want to use the Application Support directory:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
if ([paths count] > 0) {
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"App Name"];
// Create the directory if it doesn't exist, etc.
} else {
NSLog(#"Fail!");
// ...
}