How can I load an image from Assets.car (compiled version of xcassets) within an NSBundle?
Asked Answered
I

1

11

In a nutshell :

How can I load images from a compiled Assets.car within an NSBundle?

Full Version:

I'm in the process of converting a suite of apps to use CocoaPods. Each app relies on a shared pod called Core.

Core includes code files, xib files, and several xcasset files.

Here's the relevant line from the Podspec for Core that creates the resource bundle:

  s.resource_bundles = {'CoreResources' => ['Core/Resources/*']}

The Podspec passes pod spec lint, and main project that relies on it correctly builds.

However, none of the images from any xcasset files within Core are showing.

I am (naively) trying to load the images using a category on UIImage like this:

@implementation UIImage (Bundle)

+ (UIImage *)imageNamed:(NSString *)name bundle:(NSBundle *)bundle
{
    if (!bundle)
        return [UIImage imageNamed:name];

    UIImage *image = [UIImage imageNamed:[self imageName:name forBundle:bundle]];
    return image;
}

+ (NSString *)imageName:(NSString *)name forBundle:(NSBundle *)bundle
{
    NSString *bundleName = [[bundle bundlePath] lastPathComponent];
    name = [bundleName stringByAppendingPathComponent:name];
    return name;
}
@end

Previously, Core was a submodule, and this solution worked fine. However, upon inspection of my previous bundle file (separate from main bundle), I noticed that all of the images were simply being copied into the bundle... i.e.

Image.png, [email protected], etc were all in the bundle.

Upon inspection of the CocoaPods-generated bundle, it contains an

Assets.car

which I understand to be a combined, compiled version of all the xcasset files within said Core subdirectory.

How can I load images from this compiled Assets.car within this Core resources bundle?

As a hack, I suppose I could...

The Podspec syntax reference gives this as an example:

spec.resource = "Resources/HockeySDK.bundle"

This seems to suggest that it's possible to create the bundle manually within Xcode and have CocoaPods simply copy it.

This is more of a hack than a solution, though.

I believe CocoaPods (v 0.29+) can handle this entirely...?

Isodiametric answered 18/2, 2014 at 15:21 Comment(0)
J
3

I was in the same situation as you, and I ended up going with the "hack" you mentioned, but it is automated during pod install so it's a lot more maintainable.

In my podspec I have

# Pre-build resource bundle so it can be copied later
  s.pre_install do |pod, target_definition|
    Dir.chdir(pod.root) do
      command = "xcodebuild -project MyProject.xcodeproj -target MyProjectBundle CONFIGURATION_BUILD_DIR=Resources 2>&1 > /dev/null"
      unless system(command)
        raise ::Pod::Informative, "Failed to generate MyProject resources bundle"
      end
    end
  end

Then later on in the podspec:

  s.resource = 'Resources/MyProjectBundle.bundle'

The trick here is to build the bundle before pod install so that the .bundle is available, and can then just be linked as if you had it in the source all along. That way I can easily added new resources/images/xibs in the bundle target, and they will be compiled and linked. Works like a charm.

The I have a category on NSBundle+MyResources that allows easy access to the bundle resources:

+ (NSBundle *)myProjectResources
{
    static dispatch_once_t onceToken;
    static NSBundle *bundle = nil;
    dispatch_once(&onceToken, ^{
        // This bundle name must be the same as the product name for the resources bundle target
        NSURL *url = [[NSBundle bundleForClass:[SomeClassInMyProject class]] URLForResource:@"MyProject" withExtension:@"bundle"];
        if (!url) {
            url = [[NSBundle mainBundle] URLForResource:@"MyProject" withExtension:@"bundle"];
        }
        bundle = [NSBundle bundleWithURL:url];
    });
    return bundle;
}

So if you want to load e.g. a core data model:

NSURL *modelURL = [[NSBundle myProjectResources] URLForResource:@"MyModel" withExtension:@"momd"];

I've made some convenience methods to access images as well:

+ (UIImage *)bundleImageNamed:(NSString *)name
{
    UIImage *imageFromMainBundle = [UIImage imageNamed:name];
    if (imageFromMainBundle) {
        return imageFromMainBundle;
    }

    NSString *imageName = [NSString stringWithFormat:@"MyProject.bundle/%@", name];
    UIImage *imageFromBundle = [UIImage imageNamed:imageName];
    if (!imageFromBundle) {
        NSLog(@"Image not found: %@", name);
    }
    return imageFromBundle;
}

Haven't yet had this fail on me.

Jessalin answered 27/2, 2014 at 12:9 Comment(4)
+1: Yep, I did something very similar. You can actually have CocoaPods create the resource bundle entirely for you -- without needing the pre-install hooks. I'll try to write this up as another possible solution to this sometime soon.Isodiametric
@Isodiametric Awesome, would love to see it.Jessalin
Would love to see modern non-hack versionPlasterwork
@FullDecent My latest version doesn't use the pre-install step anymore, and has a small change in the s.resource_bundle config. The rest remains the same.Jessalin

© 2022 - 2024 — McMap. All rights reserved.