Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I include a nib in an OS X static library?

I've seen several posts addressing this topic in regards to iOS, but the one or two mentions of OS X just say to build a framework instead of a static library. (I can't find the post that had decent framework instructions.)

I've created my project as a static library, and coded the whole thing up accordingly. Now, I simply want to put my framework in a demo app and it's complaining about a missing nib. As a stopgap, I've copied the nib into the parent project, but I want to properly compartmentalize the whole thing.

What's the best approach?

Edit:

For context: I've created a nib to initialize an NSWindowController in a Mac app.

like image 873
Moshe Avatar asked Oct 25 '13 07:10

Moshe


4 Answers

No it isn't possible, because a static library is not the same as a "bundle".

A static library is a single file that contains classes, code and variables that were linked together by the library creator. It does not "contain" other files, it is essentially a database of compiled code. Although it would be possible to put the data for the xibs in there, Xcode would have no way of knowing that it was in there, as it looks for them as individual files on the filesystem.

Option 1: (not good)

Just keep the xib with the static library with a README file including instructions. Kind of dumb way but it's fast and not horrible since you already have everything.

Option 2: (fairly good)

You may create a "Framework" which is essentially a bundle of code, resources, settings etc which may be reused by multiple projects

Apple Doc for Framework: https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html

https://developer.apple.com/library/mac/documentation/macosx/conceptual/BPFrameworks/Tasks/CreatingFrameworks.html

Random guide for OS X Frameworks: (it's with older version of Xcode but it's the same concept) http://www.intridea.com/blog/2010/12/28/a-visual-guide-to-creating-an-embeddable-framework-for-mac-osx

[UPDATE 1] Note that it won't work with iOS, since frameworks in app bundles aren't allowed by the App Store rules. (Dynamically loaded code isn't anyway). [Credit to @nielsbot who remind me of this in comments.]

[UPDATE 2] Read more about iOS 8 they changed a lot of things in this regard. (Cannot talk on that because of the Apple's NDA)

like image 161
Mojtaba Avatar answered Oct 04 '22 10:10

Mojtaba


Ok I took the time to put together a sample of how you can do this. It's built on @TomSwift's answer and this: http://www.cocoanetics.com/2012/02/xcode-build-rules/

I uploaded a sample project here: https://github.com/nielsbot/StaticLibraryXIB

You add a custom rule for XIB files to your static library target. It inserts a class method on NSNib that instantiates your XIB whenever it's called. The method is the name of your XIB. So if you have "TestNib.xib", you would call +[NSNib TestNib].

In more detail:

  1. Add a custom compiler for XIB files (This is really just a bit of shell script we want to run for each XIB file in the project)

    a) In your static lib build rules, enter "XIB" in the search box and click the "Copy to target" button to create a XIB file rule in your project.

    b) leave Process as "Interface Builder files", set Using to "Custom script:" Here's the custom script:

    /usr/bin/ibtool --output-format human-readable-text --compile "$TARGET_TEMP_DIR/$INPUT_FILE_BASE.nib" "$INPUT_FILE_DIR/$INPUT_FILE_NAME"
    Hexdump=`hexdump -v -e '1 1 "0x%02x, "' "$TARGET_TEMP_DIR/${INPUT_FILE_BASE}.nib"`
    Datalength=`stat -f "%z" ${TARGET_TEMP_DIR}/${INPUT_FILE_BASE}.nib`
    sed -e "s/HEX_DUMP/$Hexdump/g" -e "s/NIB_NAME/${INPUT_FILE_BASE}/g" -e "s/HEX_LENGTH/$Datalength/g" "${SRCROOT}/NibTest/CompiledNibTemplate.m" > "$DERIVED_FILE_DIR/$INPUT_FILE_BASE.nib.m"
    

    This will transform your XIB files into .m files that will be automatically compiled into your project. The template is CompiledNibTemplate.m, which looks like this:

    #import <Cocoa/Cocoa.h>
    
    @implementation NSNib (NIB_NAME)
    
    +(instancetype)NIB_NAME
    {
        NSData * data = [ NSData dataWithBytesNoCopy:(void*)(const unsigned char[]){ HEX_DUMP } length:(NSUInteger){ HEX_LENGTH } freeWhenDone:NO ] ;
        NSNib * result = [ [ NSNib alloc ] initWithNibData:data bundle:nil ] ;
        return result  ;
    }
    
    @end
    
  2. Based on the example above, if your XIB was called "StaticLibraryWindowController.xib", NSNib will now have a new class method StaticLibraryWindowController. )Invoking [ NSNib StaticLibraryWindowController ] will return an NSNib object. You can call -instantiateWithOwner:topLevelObjects: on that like any other nib handle)

like image 36
nielsbot Avatar answered Oct 04 '22 11:10

nielsbot


Simply convert you nib into an objective C class: http://kosmaczewski.net/projects/nib2objc/

Then include that as you would your other source*

*Never done this, but in theory it should work, right?

like image 32
Sam Jarman Avatar answered Oct 04 '22 11:10

Sam Jarman


As others have pointed out, bundling a nib file with a static library in the same manner that you might bundle a nib with a framework just isn't possible. A static library contains compiled code only. It is not a container for a collection of code and other resources.

That said, if you're serious about this you have an option that would have the same effect. Basically you'll be base64 encoding your nib into a string, and reconstituting it at runtime. Here's my recipe:

1) compile your .xib to binary .nib form. Use XCode or ibtool.

2) use a tool to encode your .nib as base64 text. On OSX you can use openssl to do this from the terminal:

openssl base64 -in myNib.nib -out myNib.txt

3) copy/paste the base64 string into one of your source files. Construct a NSString from it:

NSString* base64 = @"\
TklCQXJjaGl2ZQEAAAAJAAAAHgAAADIAAAA2AAAAjAAAAG4AAADuAwAAEQAAAPwG\
AACHgIaChoGPh4eCjoGJj46CnYGCnoGCn4GBoIKEooaKqIGOqYGCqoGQq4iMs4WC\
uIGEuYePwIaBxoWMy4SCz4GG0IGI0YWM1oGE14aL3YeO5IGE5YeC7IGF7YGKVUlG\

...

ZVN0cmluZwCHgQ0AAABVSUZvbnQAiIBOU0FycmF5AIeATlNGb250AI6AVUlQcm94\
eU9iamVjdACIgQMAAABVSUNvbG9yAIeAVUlWaWV3AA==";

4) write code to decode the base64 string into a NSData:

NSData* d = [[NSData alloc] initWithBase64EncodedString: base64 options: NSDataBase64DecodingIgnoreUnknownCharacters];

5) Construct a UINib from the NSData:

UINib* nib = [UINib nibWithData: d  bundle: nil];

6) Use your nib:

NSArray* nibItems = [nib instantiateWithOwner: nil options: 0];

Now, you may end up needing to change a few things in your code. If you're creating view controllers from nibs using either init or initWithNibName:bundle:, then this isn't going to 'just work'. Why? Because those mechanisms look in a bundle (typically the app bundle) for the nib, and your nib won't be there. But you can fix up any view controllers in your code to load from your UINib that we just reconstituted. Here's a link that describes a process for this: http://www.indelible.org/ink/nib-loading/

You may quickly find that you need other resources available to your code in addition to your .nib. E.g. images. You could use the same approach to embed images or any other resource in your static library.

In my opinion, the developer-cost of the workflow required to keep this embedded nib up to date is pretty high. If it were me I'd just create a framework and distribute that.

like image 27
TomSwift Avatar answered Oct 04 '22 11:10

TomSwift