Resource-only NSBundle: is this kosher? - objective-c

In my iOS app, I'm downloading content from the web into my /Library/Caches directory. I'd like to represent this directory as an NSBundle for better compatibility with some of the external APIs we're using. (That way, we can simply change [[NSBundle mainBundle] pathForResource...] to [myBundle pathForResource...] whenever it appears.)
The following seems to work fine:
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString* cachesDirectory = [paths objectAtIndex:0];
NSBundle* bundle = [NSBundle bundleWithPath:cachesDirectory];
Better yet, the bundle reflects any changes I make to the /Library/Caches directory. However, I'm concerned because the caches directory is technically not a bundle per Apple's docs. That is to say:
It's not a "directory with a standardized hierarchical structure that holds executable code and the resources used by that code", since there's no code.
It's neither an Application, Framework, nor a Plug-In bundle.
It's most like an Application bundle, but it doesn't contain the required Info.plist or executable.
I could find no mention anywhere of this sort of dynamically-created, resource-only bundle. Is this okay to do?

The /Library/Caches directory will lack some of the standard files which are required in a bundle, like a Contents/ directory or a Contents/Info.plist file, so it may not behave properly when treated as one. Proceed with caution.

Yes, it's absolutely okay to have a resources only bundle. Some of the verbage that you quote pre-exists iOS. In OS X you can dynamically load executable code, that's specifically excluded in iOS.
Localization is an example of resource only bundles.
Edit:
The Bundle Programming Guide says:
Although document formats can leverage the bundle structure to
organize their contents, documents are generally not considered
bundles in the purest sense. A document that is implemented as a
directory and treated as an opaque type is considered to be a document
package, regardless of its internal format. For more information about
document packages, see “Document Packages.”
which says:
There are several ways to access the contents of a document package.
Because a document package is a directory, you can access the
document's contents using any appropriate file-system routines. If you
use a bundle structure for your document package, you can also use the
NSBundle or CFBundleRef routines. Use of a bundle structure is
especially appropriate for documents that store multiple
localizations.
also note that Apple has been telegraphing that it is minimizing the use of "path"/NSString APIs in favor of URL APIs, though existing path APIs will no doubt continue for many more major OS releases.

Related

confused about resource files

I'm very much a noob/hobbyist programmer, putting together a few simple Mac apps.
I'm confused about resource files.
I have some .png images sitting in a folder in my (XCode 4.4) project.
I also have a .plist (containing a dictionary) sitting in my Supporting Files folder.
To access the .plist, I've added a few lines of code to dig into the Bundle and get the file I'm after (pretty standard, I believe).
To use the .png files, I simply refer to them by name, and when I run from within Xcode everything does what I'm expecting.
But when I export as an Application, the images are still available and work fine, without me going into the Bundle for them.
So my question is - what determines which resource files I should go into the Bundle for, and which resource files I can assume will just be available by virtue of their being in my Supporting Files folder?
Many Thanks for reading this, and for any help you can give me.
I'm guessing that it depends on the class you are using and when you are only referring to them by name its just a convenience thing and XCode does the work nonetheless.
For example for a UIImage you can typeUIImage *image = [UIImage imageNamed:#"something"];
But XCode will do the NSSearchPathForDirectoriesInDomains or the [NSBoundle mainBoundle] thingy at build time.
You can only do the quick name referencing method on a few frequently-used classes and it's just for convenience.
This is my theory anyway, it requires confirmation.

Unzipping downloaded files in iOS

Using ASIHTTPRequest, I downloaded a zip file containing a folder with several audio files. I tried to unzip the file with SSZipArchive and ZipArchive, which are both based on minizip.
When I compile the code, I get this error: Undefined symbols for architecture i386: "_OBJC_CLASS_$_ZipArchive", referenced from: objc-class-ref in AppDelegate.o.
How do I unzip this file in iOS?
I've used ZipArchive with success in the past. It's pretty ligthweight and simple to use, supports password protection, multiple files inside a ZIP, as well as compress & decompress.
The basic usage is:
NSString *filepath = [[NSBundle mainBundle] pathForResource:#"ZipFileName" ofType:#"zip"];
ZipArchive *zipArchive = [[ZipArchive alloc] init];
[zipArchive UnzipOpenFile:filepath Password:#"xxxxxx"];
[zipArchive UnzipFileTo:{pathToDirectory} overWrite:YES];
[zipArchive UnzipCloseFile];
[zipArchive release];
more examples about this package here
I have also tried SSZipArchive in some projects.
Below line would unzip your zip file.
[SSZipArchive unzipFileAtPath:path toDestination:destination];
To start, in iOS 7, Apple introduced the ability to natively zip / unzip files. You also have the option so send the zip files through Mail, Messages, Airdrop and "Open in". The key is: The zip file has to be supported by iOS Some are, and some are not. The first step: Find out if your file is supported. Do this by a simple check of your newly saved file. Try to open it. If it is stuck of "Waiting..." it is probably not supported. Try a different library or use a workaround if this is the case.
Now, doing this programmatically Currently requires the use of a third party library to save zip files and extract them. This is not ideal, since a lot of people / companies avoid using them. That being said, the answer marked correct, ZipArchive is a great third party tool, especially since it now supports Swift. I would recommend using it until Apple introduces a native library.
On iOS6 a ZIP file seems to be handled like a folder. I had success by simply doing the following:
[[NSFileManager defaultManager] copyItemAtPath:[NSURL URLForCachesDirectoryWithAppendedPath:#"ZIP_FILE.zip/Contents/Test.m4a"].path
toPath:[NSURL URLForCachesDirectoryWithAppendedPath:#"Test.m4a"].path
error:nil];
There might be a chance you can even make a directory listing to unzip files with unknown content. Don't know about password protected ZIP files though.

Can .strings resource files be added at runtime?

I know that in Mac apps one can add .strings files to the project folder to add localizations.
Is there any way that additional localizations can be added to an app (iOS or Mac OS) without loading them from the resources bundle at compile time. Say, downloading an additional localization and storing it in /Documents on iOS?
Yes, but it's not what you think.
You can take a strings file and load it into an NSString, and then transform it into a dictionary using -[NSString propertyListFromStringsFileFormat].
This will provide you with a way to store your custom-translated strings in-memory.
As for actually using that, you'll have to define custom translation functions. IE, you can't use NSLocalizedString() and friends any more. Fortunately, genstrings (the utility used for generating strings files) lets you specify custom function names:
genstrings -s "JPLocalizedString" ...
This means that in code, you can define:
NSString* JPLocalizedString(NSString *key, NSString *comment) {
return [myLoadedStrings objectForKey:key];
}
As well as JPLocalizedStringFromTable(), JPLocalizedStringFromTableInBundle(), JPLocalizedStringWithDefaultValue(). genstrings will pick up all of those. (In other words, just because NSLocalizedString is a macro doesn't mean your version has to be)
If you do this and use these JPLocalizedString variants, then genstrings will still generate your strings files for you (providing you use the -s flag).
Once these functions are called, you can use whatever lookup mechanism you want, defaulting back to the NSLocalizedString versions if you can't find anything.
If you want to implement an alternate version of NSLocalizedString() inside your application, there are number of good strategies for that. That would allow you to modify the values used inside your application itself. But those won't work when it comes to Push Notifications.
Warning however : for iOS Push Notifications that use Localized Strings, there is no legal runtime method modify the built in strings resources that are used to translate the localized arguments sent from your server into the device's native language.
So if you want to use Localized Strings as part of Push Notifications, you must ship them in the app when you submit to the store. They can't be modified later by loading something from the server.
You should be able to achieve this by overriding NSBundle’s various pathForResource:… methods in a category. (I would assume they all go through -pathForResource:ofType:inDirectory:forLocalization:, but this isn’t documented so you can’t rely on it even if it turns out to be the case now, so you should override all of them.)
I also suggest filing an enhancement request for a clean way of overriding the lookup mechanism.
I managed to do it and I created a small repo to show how I have done it:
https://github.com/multitudes/MyLocalisationTestApp
Create a custom bundle in the documents directory.
Looking at the Apple documentation for Text: https://developer.apple.com/documentation/swiftui/text
The SwiftUI text view initializer accepts a bundle parameter.
1 - I created a custom bundle in the Documents directory.
2 - I placed programmatically the language folders the strings files inside it.
3 - When using it I pass the custom bundle and the tableName (the name of the .strings file) to the Text initialiser together with the key.
Like in SwiftUI it will be: Text(localizedStringKey, tableName: tableName, bundle: bundle)

Writing to files in bundle?

If you scroll down to the section 'Writing to Files and URLs' at this link, would the path varaible have to be a file on disk? Is it possible to write to a file in the bundle or must it always be deployed first?
You can write files to the application bundle as much as you'd like. Just get the path of the file through NSBundle's pathForResource:ofType: method and you can write to that file. If you want just the directory of the bundle, use resourcePath.
You don't want to do this, though. There are various reasons, but you'll break code signing, which is a big one. You should use the established conventions instead (such as writing to Library:Application Support:Your App).
EDIT: For a (possibly) more convincing reason of not to do this... When I was first learning Cocoa programming, I saved to the bundle because I didn't want to bother with the Library. It got really annoying, though, because every time you make a change to your program, you lose all of your saved data/settings for that program (assuming you're not using NSUserDefaults for preferences). I kept having to move it over from the old version to the new one. By using the Library, you don't have to worry about this.
The bundle is on disk; it's just the ".app" directory. You should not write to files in this directory, however. While currently possible on Mac, it will break code signing. On iPhone, you already can't write into your own bundle, and we should expect to see this limitation in the future on Mac. You should write your application files into various directories under ~/Library or ~/Documents as appropriate. See the File System Programming Guide for guidance.

Accessing data files in Objective-C or cocoa

I am fairly new to a objective-c or in whole mac/iphone development. My question is how can I acces a data files(text files or flat files) in objective-c? For example I have a sample.txt file, how can I open this files and access its data? Do I need to use a class? And I heard about dictionary, is this term related to my problems?
Please kindly redirect me to a good site.
Thanks alot.
sasayins.
You can use regular fopen and fread to access the contents of a file. Alternatively, you can use NSString if your file contains only text or NSData for non-text data.
NSString *myString = [NSString stringWithContentsOfFile:#"/path/to/file"];
NSData *myData = [NSData dataWithContentsOfFile:#"/path/to/file"];
Edit
#"/path/to/file" a constant “Objective-C” style string. It is different to a regular C string (i.e. without the # prepended) because it behaves like an object; you can send it messages, and it is able to be stored in NSArrays etc. From a Mac Programmer's point of view, these Objective-C strings can be treated just like NSString objects.
The Mac OS X filesystem layout typically looks like this:
/System contains system files similar to C:\windows\
/Library contains libraries, similar to C:\windows\system32\
/Users similar to Windows' C:\Documents and Settings\
/Applications Mac's version of C:\Program Files\
/Developer Where Xcode, SDKs, and other developer tools live.
If your username on your Mac is "smith", then your Home directory is /Users/smith. If you have a file in your Documents folder of your Home directory called data.txt, then you can use the following code to access it (but I wouldn't recommend hard-coding paths like this)
NSString *myString = [NSString stringWithContentsOfFile:#"/Users/smith/Documents/data.txt"];
There are various functions available for reliably obtaining your home directory and other directories of particular interest. The NSString documentation explains the various methods available for manipulating strings containing paths.