Why is my bundle ignoring its info.plist? - objective-c

I am attempting to bundle a set of resources into a bundle, and them access them from code via NSBundle.
So far, I can access the resources from the bundle fine, but I cannot get the NSBundle instance to get any meta-data from the bundle's Info.plist file.
Strangely, my first attempt worked, and correctly got the meta-data, but since then I have been unable to replicate this behaviour and the bundle always returns null for the bundleIdentifier or other items in the infoDictionary.
I have reduced the code to the following test case:
NSString *pathToTestBundle = [[NSBundle mainBundle] pathForResource:#"test"
ofType:#"testBundle"];
NSLog(#"path to Test Bundle: %#", pathToTestBundle);
NSBundle *testBundle = [NSBundle bundleWithPath: pathToTestBundle];
NSLog(#"testBundle: %#", testBundle);
NSString *identifier = [testBundle bundleIdentifier];
NSLog(#"testBundle identifier: %#", identifier);
NSURL *testResourceURL = [testBundle URLForResource:#"vanGroup"
withExtension:#"png"];
NSLog(#"testBundle test resource: %#", testResourceURL);
NSImage *testImage = [[NSImage alloc] initByReferencingURL:testResourceURL];
[imageView setImage:testImage];
NSLog(#"%#", [testBundle infoDictionary]);
Which gives this: (edited for line length)
path to Test Bundle: /.../BundleTester.app/Contents/Resources/test.testBundle
testBundle: NSBundle </../BundleTester.app/Contents/Resources/test.testBundle>
(not yet loaded)
testBundle identifier: (null)
testBundle test resource: file://.../BundleTester.app/Contents/Resources/test.testBundle/Resources/vanGroup.png
BundleTester[45083:707] {
NSBundleInitialPath = "/Users/diggory/Library/Developer/Xcode/DerivedData/BundleTester-hcbsnhudsdfzlyggjbvlkdbrtxrw/Build/Products/Debug/BundleTester.app/Contents/Resources/test.testBundle";
NSBundleResolvedPath = "/Users/diggory/Library/Developer/Xcode/DerivedData/BundleTester-hcbsnhudsdfzlyggjbvlkdbrtxrw/Build/Products/Debug/BundleTester.app/Contents/Resources/test.testBundle";
}
In my Xcode project by test bundle is added as a folder reference and has the following structure:
test.testBundle
Info.plist
Resources
vanGroup.png
The image resource is loaded correctly, but the values in the plist are ignored.
The plist is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.monkeyfood.testbundle</string>
<key>badger</key>
<string>Bodger</string>
</dict>
</plist>
Am I missing something? What am I doing wrong?
Many thanks.

The "Contents" folder is missing.

It seems your bundle object is referring to the Bundle but the Bundle isn't yet loaded which is why you get null when you call bundleIdentifier. From Apple's Documentation on Bundle Loading Code
To load a bundle’s executable code, use NSBundle’s load method. This method returns YES if loading was successful or if the code had already been loaded, and NO otherwise.
You need to call [bundle load] like so:
NSBundle *testBundle = [NSBundle bundleWithPath: pathToTestBundle];
[testBundle load]; // Load the bundle...
More information on Bundle Loading is explained in Apple's Documentation

Related

Copy a file in Cocoa App with / without sandboxing enabled

First Issue: No Sandboxing
I am having an issue with some code to copy a file. With sandboxing turned off completely and this code.
- (IBAction)installWidget:(id)sender
{
// copy widgets to users library
NSError* error = nil;
NSString *testUrl = #"~/Library/Widgets/test.wdgt";
if ([[NSFileManager defaultManager]fileExistsAtPath:testUrl]) {
NSLog(#"yes");
}
NSString *testUrl2 = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: #"test.wdgt"];
if ([[NSFileManager defaultManager]fileExistsAtPath:testUrl2]) {
NSLog(#"yes");
}
[[NSFileManager defaultManager] removeItemAtPath:testUrl error:nil];
[[NSFileManager defaultManager]copyItemAtPath:testUrl2 toPath:testUrl error:&error];
if (error != nil) {
NSLog(#"%#", [error localizedDescription]);
}
}
With sandboxing turned off I get the following error.
The file “test.wdgt” doesn’t exist. I tried zipping it thinking it was a flat file issue when changing the code from test.wdgt to test.zip I got the same error. The file is included in the resources bundle but its not letting me copy it.
I tried moving the app from outside of the build folder, same issue. I also tried cleaning the build folder same issue.
Second Issue: With Sandboxing
I get the following error with Sandboxing enabled. You don't have permission to access the directory Widgets.
Entitlement file looks like this
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.assets.movies.read-write</key>
<true/>
<key>com.apple.security.assets.music.read-write</key>
<true/>
<key>com.apple.security.assets.pictures.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.print</key>
<true/>
<key>com.apple.security.temporary-exception.files.absolute-path.read-write</key>
<array>
<string>/Users/jon/Library/Widgets/</string>
</array>
</dict>
</plist>
I am beating my head against the wall. I tried adding a simple image and then replacing the image name with the widget name and it copied fine as long as sandboxing was disabled. I wrote a widget thats works with my app and I want to distribute it by allowing the user to install the widget by clicking a button in the app or from a menu item. Any help is appreciated.
On the non-sandbox question, you need to expand the tilde(~) before using that in a path for file system operations. One way to do this is by calling -stringByExpandingTildeInPath on the string you have created, but the best practice for getting folders like this would be to use:
NSArray *paths = NSSearchPathForDirectoriesInDomains
( NSLibraryDirectory, NSUserDomainMask, YES);
This will return an array (should only be 1 element) containing the string to the path for the User's Library directory. Then you can add your specific path elements to that by using -stringByAppendingPathComponent:.
Thus, you'd get the full path by taking:
NSString *widgetsPath = [[paths objectAtIndex: 0] stringByAppendingPathComponent:
#"Widgets"];
As for doing in the sandbox, your current code will fail due to the wrong directory, which won't have access (since ~ is being interpreted as a path component and not to replace the User's home directory). However, you obviously can't use the absolute-path exception in shipping code by enumerating every user. Chances are you will need to find another approach to installing this if you are going to be sandboxed. You may be able to just open the widget file and thus get the OS to offer to copy it for you. Otherwise, you're going to have to ask the user's permission in some way, such as by popping up an open window and passing in the path to the user's Widget folder.

How come reading from a plist fails in my iOS application?

I am trying to create an NSMutableDictionary containing a list of animals in my iOS app with this function.
// puts animals into dictionary
- (void) putAnimalsFromPlistToDictionary
{
NSBundle *bundle = [NSBundle mainBundle];
NSString *path = [bundle pathForResource:#"myAnimals" ofType:#"plist"];
myAnimalDictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
// I set a breakpoint here in XCode.
}
I dragged myAnimals.plist (which looks like the below) into my supporting files folder for my application.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<string>elephant</string>
<string>monkey</string>
<string>cat</string>
</array>
</plist>
However, when I reach the breakpoint (as listed in my comment), I see that myAnimalDictionary is nil. Why?
Perhaps it's because you have an array in your .plist but you're trying to instantiate a mutable dictionary instance.

A smart way to retrieve allowed file types from plist

Scenario:
I like to define the allowed file types (content types) in the Info.plist file of my Cocoa application. Therefore, I added them like the following example shows.
# Extract from Info.plist
[...]
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>public.png</string>
<key>CFBundleTypeIconFile</key>
<string>png.icns</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSIsAppleDefaultForType</key>
<true/>
<key>LSItemContentTypes</key>
<array>
<string>public.png</string>
</array>
</dict>
[...]
Further, my application allows to open files using an NSOpenPanel. The panel allows to set the allowed file types through the following selector: setAllowedFileTypes:. The documentation states that UTI can be used.
The file type can be a common file extension, or a UTI.
A custom solution:
I wrote the following helper method to extract the UTI from the Info.plist file.
/**
Returns a collection of uniform type identifiers as defined in the plist file.
#returns A collection of UTI strings.
*/
+ (NSArray*)uniformTypeIdentifiers {
static NSArray* contentTypes = nil;
if (!contentTypes) {
NSArray* documentTypes = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleDocumentTypes"];
NSMutableArray* contentTypesCollection = [NSMutableArray arrayWithCapacity:[documentTypes count]];
for (NSDictionary* documentType in documentTypes) {
[contentTypesCollection addObjectsFromArray:[documentType objectForKey:#"LSItemContentTypes"]];
}
contentTypes = [NSArray arrayWithArray:contentTypesCollection];
contentTypesCollection = nil;
}
return contentTypes;
}
Instead of [NSBundle mainBundle] also CFBundleGetInfoDictionary(CFBundleGetMainBundle()) can be used.
Questions:
Do you know a smarter way to extract the content type information
from the Info.plist file? Is there a Cocoa-build-in function?
How do you deal with the definition of folders that can contained
there, e.g. public.folder?
Note:
Throughout my research, I found this article quite informative: Simplifying Data Handling with Uniform Type Identifiers.
Here is how I read information from a plist (it can be the info.plist or any other plist you have in you project provided you set the correct path)
NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
NSString *fullPath = [NSString stringWithFormat:#"%#/path/to/your/plist/my.plist", resourcePath];
NSData *plistData = [NSData dataWithContentsOfFile:fullPath];
NSDictionary *plistDictionary = [NSPropertyListSerialization propertyListFromData:plistData mutabilityOption:NSPropertyListImmutable format:0 errorDescription:nil];
NSArray *fileTypes = [plistDictionary objectForKey:#"CFBundleDocumentTypes"];

Accessing a plist with a console application - Xcode 4

I've created a plist (File->New File->Resource plist) file that has a dictionary that stores floating point numbers for a console application. I've pretty much created this plist to store a few numbers to be returned by class methods, but could change so hardcoding isn't really that great of an idea. However, when I try to access the plist file, I always get "nil" for the file path and I'm not sure what I'm doing wrong.
<plist version="1.0">
<dict>
<key>minFloatNumber</key>
<real>520.0</real>
</dict>
</plist>
+ (double) minFloatingPointNunber
{
NSString *path = [[NSBundle mainBundle] pathForResource: #"FloatDictionary"
ofType: #"plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile: path];
[path release];
double f = [[dict objectForKey: #"minFloatNumber"] doubleValue];
[dict release];
return f;
}
I've read various articles discussing looking at the project's "Build Phases" and looking at "Copy Bundle Resources," but I don't see any of that anywhere. I'm pretty sure there has to be something wrong with my setup, but not sure what I'm missing, or the steps I need to take to remedy the situation. Thanks in advance for the tips.
The "Console Application" template will be set up to produce a single binary — not included inside a proper bundle, so you can't use the NSBundle APIs to access files. If you want to include a file, you could make an actual application (where you'll be able to use [NSBundle mainBundle]), or you could include the data inside the binary (as a compile-time constant or some such).

cant load files from NSBundle in iphone unit test

I'm using XCode 4.
Can someone please explain to me why when I have written a unit test (under the test target) that it can't find my local file resource? I'm trying to load a plist file from the NSBundle in a unit test but it doesn't work in the unit test target. I've tried throwing it under the Classes area of the Test target.
Here's my code:
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"urlMappings.plist"];
self.urlMappings = [NSDictionary dictionaryWithContentsOfFile:finalPath];
self.urlMappings is empty for some reason even though urlMappings.plist definately exists in my project. Here is the source to urlMappings.plist to prove it is in fact a dictionary.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>%server%view.html</key>
<string>testHtml1.html</string>
</dict>
</plist>
Any ideas? Thankyou
Under the test target, you shouldn't use [NSBundle mainBundle], because it is the main bundle, not the test bundle. you should use [NSBundle bundleForClass:[self class]] replace.
The solution (which doesn't make sense) is it wasn't in the bundle resources of the main target. It has to be in the bundle resources of BOTH targets even though I'm only using it in test.
Anyone know why this is the case?