Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
136 lines (103 sloc) 9.29 KB

Table of contents


Introduction

This example demonstrate:

  • How to create a static library project correctly that is easy to integrate then in your applications
  • how to make such static library project generate a resource bundle to provide ressources to your app.

This shows that you can have a library that provide both functional code and resources like XIB files, PNG files, xcdatamodels, and even Localized strings, without the need for you to copy each and every single resource in your application project.

There are a lot of advantages in this practice:

  • Your library can provide UIViewControllers that are based on XIB files if necessary, and the XIB files will be provided by the library and not need to be copied in the application for this to work
  • If your XIB files (used and provided by the library and its resource bundle) use themselves some images in an UIImageView, the image will be loaded from the same bundle than the XIB, namely the resource bundle provided by the library too, and not the application
  • If your library needs to provide some localized strings, there is no need to merge your own Localizable.strings of your app with the strings needed for the library: the library will have its own Localizable.strings and will be able to pick localized strings from it instead of the one from the application itself.

Once you have implemented a simple method in your library main class, that returns an NSBundle object representing the resource bundle generated by the library (see demo project), you can use all the standard methods of NSBundle like pathForResource:ofType: and localizedStringForKey:value:table: to retrieve those resources.

For images, the +[UIImage imageNamed:inBundle:] method is unfortunately a private method, but the demo project provides a workaround (see the UIImage+Bundle.h category in the demo project).

Example usages

You can imagine using this pattern for example to create a library that will provide some standard widgets / custom views / reusable control (or even View Controllers) that you reuse a lot in your various applications.

Even if those views will be embedded in a static library, you can design their interface using XIB files, use PNG images when needed to design those custom views, provide localized strings so your reusable control support multiple languages natively, and so on… As long as you use the methods of NSBundle in your library code to make sure to retrieve the XIB, PNG, Localized strings and other resources from the resource bundle (and not the resources of your future host application), you will have a standalone reusable control!

All you will need to do to use them in your applications then is to link the application with the static library of course, and add the resource bundle that your project generated with the static library (thanks to a second, dedicated target) to the resources of your app.

How to generate a clean an reusable static library

Note: this is not exactly the process I presented in my CocoaHeads presentation #12 in Rennes (17 Jan 2013) since I found this much better solution a bit later. Contrary to what I told in my CocoaHeads presentation:

  • There will be no need to fill the HEADER_SEARCH_PATH Build Setting in your application project
  • We will use the "Copy Files" phase (and not the "Copy Headers" phase) as explained by Apple here -- so we won't have to use a workaround involving the Installation Path of the lib to make it work in "Archive" mode.

Create a library project

  • Create a new project of type "Static Library", and add this project to your workspace
    • If you don't have a workspace already, simply create a new workspace and add both your application project and this library project to it
  • Add your code (.h and .m files) to this project
  • Go in the "Build Phase" tab of your library project. You should have a "Copy Files" phase already provided by the project template. If this is not the case:
    • Click on the "Add Phase" button at the bottom of this screen and choose "Copy Files"
    • Choose "Products Directory" in the "Destination" dropdown menu
    • Type include/${PRODUCT_NAME} in the "Subpath" field
  • Add all your public headers to this phase (by drag & dropping the .h files from the Project Navigator, or with the "+" button)
    • You should only add public headers here: if you have a header that is not intended to be imported by applications that use your library, don't add it there. This way the application won't be able to #import your private headers that declare any private API.
    • Don't forget to add headers to this phase each time you create a new .h file (that is intended to be public) in your library project!
  • Build the library project for "iOS Device" (not the simulator) once, to check that it builds correctly and to make Xcode know where it will be generated

Integrate the library in your application project

  • Select your application project in your workspace, and go to the "Build Phases" tab
  • Open the "Link Binary With Libraries" section and add your static library (libSomething.a) there (using the "+" button)
  • In your Project Navigator on the left of Xcode, select the new file reference (libSomething.a) that has been added to your application's xcodeproj and in the File Inspector on the right, select "Relative to Build Products" in the "Location" dropdown. (This way you will ensure Xcode will detect implicit dependencies between your app and the library)
  • In the "Build Settings" of your application's project, add the -ObjC flag to the Other Linker Flags setting

You can now use #import "YourLib/Header.h" or #import <YourLib/Header.h> in your application's code and start calling your library methods.

How to generate a valid resource bundle

Create the Resource Bundle target

Since Xcode 4 does not provide a template to generate a Resource Bundle for iOS, you will use the template for OSX bundles and then need to adjust the Build Settings to adapt it:

  • In your project that generates your static library, in addition to the "Static Library" target, add a New Target using the "Bundle" template of the OSX "Framework & Library" template section
  • Go to the Build Settings of this newly generated template, and delete the following key (i.e. select their line and hit the delete key, to remove the custom value propose and so that they use the default values instead):
    • Base SDK
    • Architectures
    • OSX Deployment Target
    • Installation Directory
    • Prefix Header
    • COMBINE_HIDPI_IMAGES
  • Set the Skip Install Build Settings to YES if not already
  • Remove the ".pch" file (we are building a resource bundle, we won't have any code in this target)
  • In the Info.plist file, remove useless keys like "pluginXXX" and "Executable File"

Finally, you may reorganize the remaining files if needed (for example I often remove the InfoPlist.strings file and move the Info.plist file at some other place and remove the Supporting Files folder, just to keep stuff as clean as possible)

Once this Resource Bundle target is configured:

  • Add it as an explicit dependency to your static library, so that every project (typically your host application projet) that is linked and dependent to your static library will ensure first that the bundle has been compiled first.
  • Add your resource files (PNG, XIB, Localized.string, PLIST data files, xcdatamodel, …) to your bundle target so that those resources will be embedded in the bundle

Integrate the resource bundle in your application project

  • Build the bundle target once, for "iOS Device" (not for the simulator). The file should then change from red (inexistant) to black (file exists) in the "Products" group in your Project Navigator on the left.
  • Select the generated bundle in the "Products" group in your Project Navigator, and right-click to choose "Show in Finder"
  • Once the generated bundle has been highlighted in the Finder, drag & drop it on Xcode, on the Project Navigator, under your application's Xcode project (for example right below the ".a" static library you should have around here too). This way the resource bundle will be copied and embedded inside the generated application during the compilation.

Note that since modifying a resource does not seem to modify the "modification date" of the bundle that contains the resource, Xcode may sometimes not detect that it needs to rebuild your resource bundle when a resource has been added to it or modified. In that case, the easiest solution is to clean and rebuild your resource bundle to be sure it contains the latest resources.

Something went wrong with that request. Please try again.