This question already has answers here:
Packaging a Bundle with a static library
(3 answers)
Closed 9 years ago.
I have created a subproject as a static library and added a bundle to it, so I can wrap the project and resources that will be used by all programs using that library.
I have followed the instructions here.
http://www.galloway.me.uk/tutorials/ios-library-with-resources/
It is working fine, except when I have to work with resources like images and other files.
I thought that by importing a library and its resources to another project I could access them easily but this is not the case.
Every time I have to access a resource file on library's bundle I have to use this:
NSBundle *bundle = [NSBundle bundleWithURL:[[NSBundle mainBundle] URLForResource:#"MyLibraryBundle" withExtension:#"bundle"]];
path = [bundle pathForResource:#"readme" ofType:"txt"];
but if the resource is on the main program I have to use [NSBundle mainBundle] instead.
This is creating a very complex logic because I may have more than one subproject and I will have to create zillions of variations.
Is there a way to make a way the app can find the resource whatever it is using a simple syntax or am I missing something?
Factor your resource accesses into a separate class. For example, something like this might work for you:
#interface CSResourceManager
+ (NSString *)pathForResource:(NSString *)resource ofType:(NSString *)type;
#end
#implementation CSResourceManager
+ (NSArray *)resourceBundles
{
NSBundle *mainBundle = [NSBundle mainBundle];
NSURL *libraryBundleURL = [mainBundle URLForResource:#"MyLibraryBundle" withExtension:#"bundle"];
NSBundle *libraryBundle = [NSBundle bundleWithURL:libraryBundleURL];
// Add more bundles here and/or cache as necessary
return #[ libraryBundle, mainBundle ];
}
+ (NSString *)pathForResource:(NSString *)resource ofType:(NSString *)type
{
NSString *path = nil;
for (NSBundle *bundle in [self resourceBundles]) {
if ((path = [[self resourceBundle] pathForResource:resource ofType:type])) {
break;
}
}
return path;
}
#end
If locating the resource bundle every time is too slow, you may want to cache it, i.e. using a static variable.
To use this, you would now just have to write:
NSString *path = [CSResourceManager pathForResource:#"readme" ofType:#"txt"];
In addition to making your code easier to write, using a class like this will also make it easier to maintain (for example, if you want to change the name of the resource bundle, add additional resource storage locations or fallbacks, etc.).
Update #1: see modified code above to support a "search path" of sorts for locating resources.
Update #2: additional ideas for managing resources:
An alternative to using bundles at all would be to have your subprojects share a naming convention for resource files, essentially flattening the namespace. For example, prefix each resource file with the name of the library it's associated with.
Related
At first i'm trying to access local file in my app folder:
NSData *data = [NSData dataWithContentsOfFile:
[#"countries.json" stringByExpandingTildeInPath]];
result is always NULL
then i tried to check is file exists:
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* foofile = [documentsPath stringByAppendingPathComponent:#"countries.json"];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:foofile];
it doesn't exist, and i use following code to access my json file:
NSString *data1 = [[NSBundle mainBundle] pathForResource:#"countries" ofType:#"json"];
NSData *data2 = [[NSData alloc] initWithContentsOfFile:data1];
it getting the file but when i try to parse it:
NSDictionary *dict1 =[data2 yajl_JSON];
i getting an error:
[NSConcreteData yajl_JSON]: unrecognized selector sent to instance 0x6838d60
My questions is next:
Is there any chance to convert NSData to NSConcreteData?
Am i using the right approach to access data?
Api documentation - http://gabriel.github.com/yajl-objc/
Screenshot of my Xcode Build Phases:
According to the YAJL documentation the method you are trying to invoke does in fact exist.
That leaves only one option; you have not fully linked against the YAJL framework.
Make sure it shows up within the list of linked frameworks/libraries of your App target just like CFNetwork.framework shows up in my example.
Since the method you are trying to invoke is in fact part of a category on NSData, make sure you include -ObjC in your Other Linker Flags.
From Apple's Technical Q&A.
This flag causes the linker to load every object file in the library
that defines an Objective-C class or category. While this option will
typically result in a larger executable (due to additional object code
loaded into the application), it will allow the successful creation of
effective Objective-C static libraries that contain categories on
existing classes.
I have a file called "0.ballpoint" that all I want to do is store some coordinates with (don't really want to use Core Data because it seem a little excessive). I placed it in my project but when I try doing this:
if ( access("0.ballpoint", F_OK) != -1) {
printf("file exists\n");
}
else {
printf("doesn't exist\n");
}
It says it "doesn't exist". Do I need to put the full path name? And if I do what do I do when I place it on the actual iPhone/iPod Touch?
CoreData is not meant for File access. There are other file API's available for iPhone.
Please go through NSFileManager and NSStream documentation.
For example, if your intention is to check if file exists at certain path,you may use
(add your file to resources)
NSString *filePath = [[NSBundle mainBundle] pathForResource: #"0" ofType: #"ballpoint"];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:filePath];
Prefer not to use low level stuff (C File handles) unless you have a valid reason to do so. Paths are to be carefully chosen (as you cant make assumptions on certain path, which might cease to exist in future).
Having said that, if you prefer to work with C file handling ,
NSString *filePath = [[NSBundle mainBundle] pathForResource: #"0" ofType: #"ballpoint"];
FILE *fileHandle = fopen([filePath cStringUsingEncoding:NSASCIIStringEncoding],"r");
it needs to become a resource to your Application that way the file can be pulled anytime during the use of your application.
Im not too well educated in Ipod Dev, im more of a windows phone 7 developer but i know it cant access it if it is still on your computer so you need to import it into your application some how and call it with the full file path name.
i want to find the path of the file which is not in my bundle..
NSString *path = [[NSBundle allBundle] pathForResource:nm ofType:#"jpg" inDirectory:nil];
but it gives warning that NSArray may not respond..
is there any way to find the path of file any other folder.
allBundle returns an NSArray of all the bundles. You mean to use the method mainBundle.
If you can't find your resource then this is most likely a problem with the name of the resource you're looking for (have you checked the value of nm is correct?) Also check in your Target under the "Copy resources" section that your .jpg file is listed there. If it's not then you should drag it there to ensure that the file is copied into your bundle when you build it.
If you really want to search through all the available bundles for your resource, then you can loop through the array of bundles that allBundle returns as follows:
NSString* pathForResource = nil;
for (NSBundle currentBundle in [NSBundle allBundle])
{
pathForResource = [currentBundle pathForResource:nm ofType:#"jpg"];
if (pathForResource)
{
break; // Found resource, no longer need to search through bundles.
}
}
Note: You also don't need to use the "inDirectory:" part of the method (see the docs).
I have a project that uses a static library (SL). In that SL, there are a couple of strings I'd like to localize and the project includes all of the localization files. The localization works just fine when storing all text translations in the same file. The thing is that I'd like to separate the SL strings from the other strings. I have tried to put two different *.strings files (Localizable.strings and Localizable2.strings) in the language folder of interest but that did not work. I have also tried to use two *.strings file with the same name (Localizable.strings) but with different paths. It didn't work either. It seems that only one localization file is supported, right? Could anyone suggest a good way of doing this? I'm using SDK 3.2 beta 2.
It is not possible to bundle it in a static lib, but you can create new bundle like "MyStaticLibraryName.bundle", put inside all localizations and use the code below instead "NSLocalizedString()". All you need to do: add a static library and resource bundle.
NSString *MyLocalizedString(NSString* key, NSString* comment) {
static NSBundle* bundle = nil;
if (!bundle) {
NSString* path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"MyStaticLibraryName.bundle"];
bundle = [[NSBundle bundleWithPath:path] retain];
}
return [bundle localizedStringForKey:key value:key table:nil];
}
Putting files with the same name intro one project never works, because in the resulting app they end up all in the same location. (Xcode doesn't preserve your directory structure.)
But you can put part of your localization into Localizable2.strings and then use:
NSLocalizedStringFromTable(#"key", #"Localizable2", #"")
Make the localizable string for the static library, then place that string file in a folder "YourLibraryResource".
Rename the folder "YourLibraryResource.bundle".
Now you include this bundle also in the project along with the library. Then use the code given by abuharsky.
I've built a static library that makes heavy use of the Core Data framework. I can successfully use the library in my external project, but ONLY if I include the .xcdatamodel file in the main project. That is less than ideal, since the point of the library was to hide implementation details to the maximum possible.
In a separate question, I was informed that I cannot bundle resources with a library (which makes complete sense to me now).
So is there a way to programatically allow the model to be 'discovered' without having to include the model in the main project?
Sascha's answer got me on the right track. Merging a compiled .mom file from a static library into the .mom file from a host project was relatively simple. Here's a trivial example:
Create a new XCode Static Library
project called MyStaticLibrary
Create an .xcdatamodel file in MyStaticLibrary called MyStaticLibraryModels.xcdatamodel, add some Entitys, then generate the headers and implementations. When you build the MyStaticLibrary target, you'll generate a libMyStaticLibrary.a binary file, but it won't include the compiled .mom file. For that we have to create a bundle.
Create a new build target of type Loadable Bundle, found under MacOS X > Cocoa, let's call the new Target MyStaticLibraryModels.
Drag MyStaticLibraryModels.xcdatamodel into the Compile Sources build phase of the MyStaticLibraryModels Target. When you build the MyStaticLibraryModels Target, you will generate a file called MyStaticLibraryModels.bundle and it will contain the compiled NSManagedObjectModel file, MyStaticLibraryModels.mom.
After building both the MyStaticLibrary and MyStaticLibraryModels Targets, drag libMyStaticLibrary.a (along with any associated Model header files) and MyStaticLibraryModels.bundle into your host project, MyAwesomeApp.
MyAwesomeApp uses CoreData, has it's own .xcdatamodel file which will get compiled into a .mom file during its own build process. We want to merge this .mom file with the one we imported in MyStaticLibraryModels.bundle. Somewhere in the MyAwesomeApp project, there is a method that returns MyAwesomeApps NSManagedObjectModel. The Apple generated template for this method looks like this:
...
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel_ != nil) {
return managedObjectModel_;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"MyAwesomeApp" withExtension:#"momd"];
managedObjectModel_ = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return managedObjectModel_;
}
We will alter this to merge and return BOTH of our NSManagedObjectModels, MyAwesomApps and MyStaticLibraryModels, as a single, combined NSManagedObjectModel like so:
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel_ != nil) {
return managedObjectModel_;
}
NSMutableArray *allManagedObjectModels = [[NSMutableArray alloc] init];
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"MyAwesomeApp" withExtension:#"momd"];
NSManagedObjectModel *projectManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
[allManagedObjectModels addObject:projectManagedObjectModel];
[projectManagedObjectModel release];
NSString *staticLibraryBundlePath = [[NSBundle mainBundle] pathForResource:#"MyStaticLibraryModels" ofType:#"bundle"];
NSURL *staticLibraryMOMURL = [[NSBundle bundleWithPath:staticLibraryBundlePath] URLForResource:#"MyStaticLibraryModels" withExtension:#"mom"];
NSManagedObjectModel *staticLibraryMOM = [[NSManagedObjectModel alloc] initWithContentsOfURL:staticLibraryMOMURL];
[allManagedObjectModels addObject:staticLibraryMOM];
[staticLibraryMOM release];
managedObjectModel_ = [NSManagedObjectModel modelByMergingModels:allManagedObjectModels];
[allManagedObjectModels release];
return managedObjectModel_;
}
This will return the merged NSManagedObjectModel with the Entitys from both MyAwesomeApp and MyStaticLibrary.
I also created my own static library that uses Core Data. Besides the static library I have a another bundle target in the project where I have a Copy Bundle Resources item, that copies some images and things like that into the bundle and a Compile Sources build phase, where I am compiling the xcdatamodel.
The final bundle will contain all the necessary files. In your main project that relies on the static library you have to include that bundle as well. Your main project will now have access to the mom file that is needed to use core data.
To use core data with the mom from the bundle you have to create a merged managed object model in your code (it might be the main project has some core data model as well):
- (NSManagedObjectModel *) mergedManagedObjectModel
{
if (!mergedManagedObjectModel)
{
NSMutableSet *allBundles = [[[NSMutableSet alloc] init] autorelease];
[allBundles addObjectsFromArray: [NSBundle allBundles]];
[allBundles addObjectsFromArray: [NSBundle allFrameworks]];
mergedManagedObjectModel = [[NSManagedObjectModel mergedModelFromBundles: [allBundles allObjects]] retain];
}
return mergedManagedObjectModel;
}
By just including the bundle you will not have to give out the xcdatamodel, only the compiled mom file needs to be included.
i have some library with coredata too.
i have found this template for manage a framework with embed ressources
it's really simple to use on a new project ( more difficult to apply on existing )
but for framewoks build, it's really cool :-)
https://github.com/kstenerud/iOS-Universal-Framework
Sascha Konietzke's solution works well, but there is one important caveat that needs to be provided for it to work. The bundle containing the model needs to be loaded first, otherwise it will not be included in the array and merged in the MOM.
In his case he has probably already accessed resources from the bundle therefore the bundle was already loaded prior to this code being executed.
Prairiedogg's answer is a little outdated, here's a tutorial on doing this in Xcode 5: http://bharathnagarajrao.wordpress.com/2014/02/14/working-with-core-data-in-a-static-library/
Note that instead of using xcdatamodel/mom file you can also create your model in code (especially if you have a simple model) and this way you won't need to create an additional bundle for resources. Here is a simple example with one table that contains two attributes:
- (NSManagedObjectModel *)coreDataModel
{
NSManagedObjectModel *model = [NSManagedObjectModel new];
NSEntityDescription *eventEntity = [NSEntityDescription new];
eventEntity.name = #"EventEntity";
eventEntity.managedObjectClassName = #"EventEntity";
NSAttributeDescription *dateAttribute = [NSAttributeDescription new];
dateAttribute.name = #"date";
dateAttribute.attributeType = NSDateAttributeType;
dateAttribute.optional = NO;
NSAttributeDescription *typeAttribute = [NSAttributeDescription new];
typeAttribute.name = #"type";
typeAttribute.attributeType = NSStringAttributeType;
typeAttribute.optional = NO;
eventEntity.properties = #[dateAttribute, typeAttribute];
model.entities = #[eventEntity];
return model;
}
Here is a tutorial about creating model from code: https://www.cocoanetics.com/2012/04/creating-a-coredata-model-in-code/
Also based on this approach I created a small and easy to use library that might fit your needs called LSMiniDB so you can check it also.
Also in my case I had warnings such as "warning: dynamic accessors failed to find #property implementation..." on the console while using properties of NSManagedObject subclasses. I was able to fix that by moving those properties to a class interface/implementation instead of having them in a category in a separate file (currently xcode by default is generating this code splited into separate files ClassName+CoreDataClass and ClassName+CoreDataProperties with a class and a category for each subclass).
No, the limitation on using non-Apple frameworks in an iPhone app really changes the dependency game relative to OS X. Most iPhone "frameworks" (e.g. Google's toolbox for Mac, Core Plot, etc.) actually recommend that you include the source in your main application project rather than linking a product (i.e. a static library). I think the community consensus is that, on iPhone, it's OK to expect consumers of your framework to have to do a little "manual" work to use your library. In your case, this is including the xcdatamodel file in the main project. As with most of Objective-C, tell your users not to make use of the implementation details and leave it at that.
Swift 2 version for Sascha's answer:
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
var allBundles = NSMutableSet()
allBundles.addObjectsFromArray(NSBundle.allBundles())
allBundles.addObjectsFromArray(NSBundle.allFrameworks())
let model = NSManagedObjectModel.mergedModelFromBundles(allBundles.allObjects as? [NSBundle])
return model!
}()