Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to use iOS framework which is internally using .xib file

I am trying to make a universal framework, for iOS by following steps specified in this URL: Universal-framework-iOS

I have a viewController class within, that framework which internally loads a .xib file.

Below is a part of code which shows, how I am initializing that viewController and showing related view:

/*** Part of implementation of SomeViewController class, which is outside the framework ***/

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    self.viewControllerWithinFramework = [[ViewControllerWithinFramework alloc] initWithTitle:@"My custom view"];
}
- (IBAction)showSomeView:(id)sender {
    [self.viewControllerWithinFramework showRelatedView];
} 

/*** Part of implementation of ViewControllerWithinFramework class, which is inside the framework ***/

- (id)initWithTitle:(NSString *)aTitle
{
   self = [super initWithNibName:@"ViewControllerWithinFramework" bundle:nil]; // ViewControllerWithinFramework.xib is within the framework

   if (self)
   {
       _viewControllerTitle = aTitle;
   }

   return self;
}

While creating the framework, I included all .xib files, including ViewControllerWithinFramework.xib within its Copy Bundle Resources build phase.

Now my problem is when I try to integrate that framework within other project, it crashes with below stack trace:

Sample[3616:a0b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not load NIB in bundle: 'NSBundle </Users/miraaj/Library/Application Support/iPhone Simulator/7.0/Applications/78CB9BC5-0FCE-40FC-8BCB-721EBA031296/Sample.app> (loaded)' with name 'ViewControllerWithinFramework''
*** First throw call stack:
(
    0   CoreFoundation                      0x017365e4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x014b98b6 objc_exception_throw + 44
    2   CoreFoundation                      0x017363bb +[NSException raise:format:] + 139
    3   UIKit                               0x004cc65c -[UINib instantiateWithOwner:options:] + 951
    4   UIKit                               0x0033ec95 -[UIViewController _loadViewFromNibNamed:bundle:] + 280
    5   UIKit                               0x0033f43d -[UIViewController loadView] + 302
    6   UIKit                               0x0033f73e -[UIViewController loadViewIfRequired] + 78
    7   UIKit                               0x0033fc44 -[UIViewController view] + 35

Any ideas, how could I resolve this problem?

Note: It works fine if there is no any xib within the framework.

like image 952
Devarshi Avatar asked Mar 19 '14 10:03

Devarshi


5 Answers

The simplest way is to use [NSBundle bundleForClass:[self class]] to get the NSBundle instance of your framework. This won't enable the ability to get the framework's NSBundle instance by its Bundle ID but that isn't usually necessary.

The issue with your code is the initWithNibName:@"Name" bundle:nil gets a file named Name.xib in the given bundle. Since bundle is nil, it looks in the host app's bundle, not your framework.

The corrected code for the OP's issue is this:

/*** Part of implementation of ViewControllerWithinFramework class, which is inside the framework ***/

- (id)initWithTitle:(NSString *)aTitle
{
   NSBundle *bundle = [NSBundle bundleForClass:[self class]];
   self = [super initWithNibName:@"ViewControllerWithinFramework" bundle:bundle];

   // ... 

   return self;
}

The only thing changed is giving the correct bundle instance.

like image 136
Jay Whitsitt Avatar answered Oct 13 '22 12:10

Jay Whitsitt


As an another option you can directly put your xib file into your framework project and can get your nib with calling

Swift 3 and Swift 4

let bundleIdentifier = "YOUR_FRAMEWORK_BUNDLE_ID"
let bundle = Bundle(identifier: bundleIdentifier)
let view = bundle?.loadNibNamed("YOUR_XIB_FILE_NAME", owner: nil, options: nil)?.first as! UIView

Objective-C

NSString *bundleIdentifier = @"YOUR_FRAMEWORK_BUNDLE_ID";
NSBundle *bundle = [NSBundle bundleWithIdentifier:bundleIdentifier];
UIView *view =  [bundle loadNibNamed:@"YOUR_XIB_FILE_NAME" owner:nil options:nil];
like image 38
abdullahselek Avatar answered Oct 13 '22 12:10

abdullahselek


If you're using Universal-framework-iOS all resources (including Nibs and images), will be copied inside a separate bundle (folder) such as MyApp.app/Myframework.bundle/MyNib.nib.

You need to specify this bundle by passing a NSBundle object instead of nil. Your can get your bundle object as follows:

NSString *path = [[NSBundle mainBundle] pathForResource:@"Myframework" ofType:@"bundle"];
NSBundle *resourcesBundle = [NSBundle bundleWithPath:path];

As for images you can just prepend Myframework.bundle/ to their names:

[UIImage imageNamed:@"Myframework.bundle/MyImage"

This also works in Interface Builder.

Finally your users to install/update a framework is a pain, specially with resources, so better try to use CocoaPods.

like image 27
Rivera Avatar answered Oct 13 '22 13:10

Rivera


Unfortunately because iOS does not have an exposed concept of dynamic fragment loading some of NSBundle's most useful functionality is a little hard to get to. What you want to do is register the framework bundle with NSBundle, and from then on you can find the bundle by it's identifier - and the system should be able to correctly find nibs, etc. within that bundle. Remember, a framework is just a kind of bundle.

To make NSBundle "see" your bundle and it's meta information (from it's Info.plist), you have to get it to attempt to load the bundle. It will log an error because there will be no CFPlugin class assigned as a principal class, but it will work.

So for example:

NSArray *bundz = [[NSBundle bundleForClass:[self class]] URLsForResourcesWithExtension:@"framework" subdirectory:nil];
for (NSURL *bundleURL in bundz){
    // This should force it to attempt to load. Don't worry if it says it can't find a class.
    NSBundle *child = [NSBundle bundleWithURL:bundleURL];
    [child load];
}

Once that is done, you can find your framework bundle using bundleWithIdentifier:, where the identifier is the CFBundleIdentifier in your framework's Info.plist. If you need to use UINib directly to load your view controller nib directly at that point, it should be easy to locate the bundle using bundleWithIdentifier: and give that value to nibWithNibName:bundle: .

like image 2
quellish Avatar answered Oct 13 '22 11:10

quellish


Frameworks that come with XIBs usually come with bundles too - so you probably should not pass nil in the framework part.

Right click the framework -> Show in finder Open it up and see what's the bundle name in the resources folder (For example - Facebook uses FBUserSettingsViewResources.bundle) and use it.

In general - static libraries do not include xib or resource files. Frameworks is basically a wrapper to a static library, headers and some resources (usually inside a bundle)

like image 1
Liviu R Avatar answered Oct 13 '22 13:10

Liviu R