60

I saw a custom asset bundle in an iOS project I evaluated, so at least I know it's possible.

My issue is that I'm using a CATiledLayer with about 22,000 tiles for a given image and it takes a very long time to compile (half an hour clean build, 5-10 minutes for regular build). So, I want to take all of the images and make a custom bundle to make it portable and hopefully not recompile into the app bundle each time.

How do I go about this? I checked the docs, but didn't see an explanation on how to actually create the bundle.

michael
  • 2,332
  • 2
  • 21
  • 27

7 Answers7

108

Answer is stupidly simple

Make a folder in finder, add files to it, rename it to bundlename.bundle

drag into Xcode - success!

to access, use the form of PathToMainBundle+"/bundlename.bundle"

michael
  • 2,332
  • 2
  • 21
  • 27
  • 4
    Doesn't work for me. I can't access the files in the assets bundle because the bundle is not loaded, and when I load the bundle I get an error saying that the bundle couldn't be loaded because its executable couldn't be located. – LandedGently Jul 08 '11 at 23:02
  • 1
    @LandedGently, I just had the same problem and it turns out it wasn't because the bundle was not loaded. It was because I had capital letters for the `ofType` part of `pathForResource:ofType` (`ofType:@"PNG"`) which worked on the Simulator but not on the actual device. From this I'd say that you need *not* call the load method on non-executable bundles in order to pull resources. – Hari Honor Nov 19 '11 at 12:37
  • Just for reference, none of my custom bundles say they are loaded, and I never bother trying to manually load them. Regardless, they function as intended. – michael Nov 21 '11 at 18:34
  • How can we use this bundle approach for a UIImageView in a interface builder (considering localized files). My question is posted here http://stackoverflow.com/questions/33242187/localised-xib-for-english-and-french-but-with-common-resources – NNikN Oct 20 '15 at 17:10
  • 1
    But that's not a bundle. It's just a folder with "bundle" in its name. – matt Oct 14 '16 at 21:04
  • @matt What is your definition of, and expectation from a bundle? It functions like a bundle, it's treated by the Xcode and the OS as a bundle. iOS does not support bundles which load code. – michael Oct 20 '16 at 19:20
  • 2
    @michael, agreed, but then nothing is happening here that would not have happened if this were "just a folder". To put it another way, what is _your_ definition of, and expectation from, a bundle? If all you wanted to do was drag a folder of stuff into Xcode for inclusion in the app, you could have done that without the renaming with the `.bundle` suffix. – matt Oct 20 '16 at 19:42
  • @matt Well, when I first needed this years ago, the use case was zipping up a .bundle and delivering content by downloading and unzipping such a file. Each one conformed to the same format and implemented info.plist -- infoDictionary worked as expected. With that, the NSBundle class was initialized with the path of the bundle and resource loading was coded around the NSBundle interface. A bundle is really just a folder with the extension anyway... In earlier versions of iOS, NSBundle could only load a ".bundle" path; it didn't work with arbitrary folders. – michael Oct 20 '16 at 21:15
  • "In earlier versions of iOS, NSBundle could only load a ".bundle" path; it didn't work with arbitrary folders" I'm sorry, that's just not true. I've been using iOS since iOS 3 and I've been reading from folders since day one. And you are equivocating on the word "load"; nothing in your code above "loads" a "bundle" as in the actual bundle-loading mechanism. You're just reading from a folder. The "bundle" suffix in the name is totally irrelevant. – matt Oct 20 '16 at 21:23
  • @matt My understanding on the initialization of NSBundle may be based on a mistake I made working in iOS 3.2. You're right; the NSBundle doesn't load in the context of that class because that would involve loading code. On iOS, there may be no technical advantage to using a folder ending in ".bundle" vs a regular folder. Still, it's a relatively common convention that remains to this day, as seen in its support in Cocoapods. It's a helpful way to package things up into discrete bundles and avoid conflicting filenames. On iOS, it turns out all you need to do is end your folder in ".bundle" – michael Oct 20 '16 at 21:38
  • 2
    @michael My goal is not to give you a hard time. I went searching for whether there was a way to _really_ load a _real_ bundle in iOS and was misled for a long time by your question and your answer (and other answers here). My comment is intended to warn off others. Your code does not do what you describe it as doing (in my view). It just references a folder, in a totally normal way. – matt Oct 20 '16 at 22:31
43

How to create a bundle

  1. Create a folder in finder.
  2. Add files to the folder
  3. Rename the folder so that its extension is .bundle (e.g. "New folder" -> "BundleName.bundle")

PS: You can at any time right click the folder and hit "Show package content" in order to add, remove or modify any of the files.

How to add the bundle to Xcode

  1. Drag it into Xcode

How to use the bundle

NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"BundleName" ofType:@"bundle"];
NSBundle *bundle = [NSBundle bundleWithPath:bundlePath]; 
NSString *resource = [bundle pathForResource:@"fileName" ofType:@"fileType"];

(Replace BundleName, fileName and fileType with appropriate names)

hfossli
  • 22,616
  • 10
  • 116
  • 130
27

Two other helpful bits of advice:

First, in order to see the contents of the bundle in Xcode you need to set its type in the File Inspector Utility Pane, to "Application Bundle". You still won't be able to copy to and from via Xcode. You'll need to use Terminal but Xcode will update it immediately.

Second, in order to use resources in the bundle here's a helpful snippet...

NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"AquarianHarp" ofType:@"bundle"];
NSString *imageName = [[NSBundle bundleWithPath:bundlePath] pathForResource:@"trebleclef2" ofType:@"png"];
UIImage *myImage = [[UIImage alloc] initWithContentsOfFile:imageName];

As mentioned in my comment above, you needn't actually load the bundle (you can't as it's not executable) and the ofType needs to match the case of your actual file for it to work on the device. It will work either way in simulator so don't be fooled by this red herring!

Finally, you don't need to put your resources in the "Resources" subfolder inside the bundle. It seems you can use an arbitrary layout but there may be unknown performance implications.

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Hari Honor
  • 8,677
  • 8
  • 51
  • 54
  • Also, if you have folders in your bundle, make sure that the folder names do NOT contain spaces. The simulator will work, but the device does not tolerate spaces in folder names. – LandedGently Apr 06 '12 at 21:31
10

Here's how I got this to work: In Xcode create a new file | Resource | Settings Bundle. Then in the Finder select that bundle and choose Show Package Contents, and add whatever image files.

Then in the code reference an image this way:

NSString *imgName = @"bundlename.bundle/my-image.png";
UIImage *myImage = [UIImage imageNamed:imgName]; 
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
LandedGently
  • 693
  • 1
  • 8
  • 16
  • 1
    But there is no meaningful sense in which that is a _bundle_. It's just a folder. You are simply doing what you would normally do to load an image from a folder. – matt Oct 14 '16 at 21:02
5

Here are the steps to create an asset or resource bundle (ex. FrameworkResources.bundle) - it's surprisingly non-obvious. This is especially useful if you are creating static frameworks.

  1. Press File -> New -> Target in Xcode
  2. Select "macOS" tab, search "Bundle"
  3. Tap "Bundle" -> click "Next" -> type product name "MyResources" -> tap "Finish"
  4. Go to "Build Settings" for the newly created Bundle. Change "Base SDK" (SDKROOT) to "iOS'
  5. Go to "Build Phases" for the newly created Bundle. Delete "Compile Sources" and "Link Binary With Libraries" (this will remove the executable within the bundle which can cause all sorts of build and app submission errors)
kgaidis
  • 14,259
  • 4
  • 79
  • 93
0

My notes on bundling and reading files in an Xcode project

Steps:

  1. Create a test.txt file and add the text "testing" to it then put it in a folder named test.bundle
  2. Drag and drop it next to your .app file in Xcode (copy)
  3. print(Bundle.main.resourcePath!+"/temp.bundle/test.txt") Output: /Users/James/Library/Developer/Xcode/DerivedData/GitSyncMac-heiwpdjbtaxzhiclikjotucjguqu/Build/Products/Debug/GitSyncMacApp.app/Contents/Resources/temp.bundle/test.txt

Example:

print(content(Bundle.main.resourcePath!+"/temp.bundle/test.txt")) // testing
static func content(_ path:String)->String?{
    do {
        let content = try String(contentsOfFile:path, encoding:String.Encoding.utf8) as String//encoding: NSUTF8StringEncoding
        return content
    } catch {
        return nil
    }
}
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Sentry.co
  • 5,355
  • 43
  • 38
0

Creating a loadable bundle project is just like creating an application—you just need to pick the appropriate project template. To create a loadable bundle project, perform the following steps:

  1. Launch Xcode.
  2. Choose New Project… from the File menu.
  3. From the template list, select Cocoa Bundle.
  4. Click Next.
  5. Choose the location for the project and click Finish.

Build and run, in Xcode you will see the bundle file in your Products folder of Project Navigator. Right-click the bundle and select "Show in Finder".

DàChún
  • 4,751
  • 1
  • 36
  • 39