无法使用内部使用.xib文件的iOS框架

我正在尝试通过以下URL中指定的步骤为iOS创build通用框架: Universal-framework-iOS

我有一个viewController类,内部加载.xib文件的框架。

下面是代码的一部分,它显示了我如何初始化viewController并显示相关视图:

/*** 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; } 

在创build框架时,我将所有的.xib文件包括在内,包括ViewControllerWithinFramework.xib在其Copy Bundle Resources构build阶段。

现在我的问题是,当我尝试将该框架集成在其他项目中,崩溃与下面的堆栈跟踪:

 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 

任何想法,我怎么能解决这个问题?

注意:如果框架内没有任何xib,它会正常工作。

如果您使用Universal-framework-iOS所有资源(包括Nib和图像),将被复制到单独的包(文件夹)中,如MyApp.app/Myframework.bundle/MyNib.nib

你需要通过传递一个NSBundle对象而不是nil来指定这个bundle。 你可以得到你的捆绑对象如下:

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

至于图片,你可以在Myframework.bundle/前加上他们的名字:

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

这也适用于Interface Builder。

最后,您的用户安装/更新框架是一个痛苦,特别是与资源,所以更好地尝试使用CocoaPods 。

不幸的是,因为iOS没有公开dynamic片段加载的概念,所以有些NSBundle最有用的function有点难以实现。 你想要做的就是用NSBundle注册框架包,然后你可以通过它的标识符来find这个包 – 系统应该能够正确地在这个包中findnibs等等。 请记住,框架只是一种捆绑。

为了让NSBundle“看到”你的包和它的元信息(从Info.plist),你必须得到它来尝试加载包。 它会logging一个错误,因为不会有CFPlugin类被指定为一个主类,但它会工作。

举个例子:

 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]; } 

一旦完成,你可以使用bundleWithIdentifier:find你的框架,其中的标识符是你的框架的Info.plist中的CFBundleIdentifier。 如果您需要直接使用UINib直接加载您的视图控制器笔尖,应该很容易使用bundleWithIdentifier:来定位该bundle bundleWithIdentifier:并将该值bundleWithIdentifier: nibWithNibName:bundle: bundleWithIdentifier:

XIBs附带的框架通常也会捆绑 – 所以你可能不应该在框架部分通过零。

右键单击框架 – >在finder中显示打开并查看资源文件夹中的软件包名称(例如,Facebook使用FBUserSettingsViewResources.bundle)并使用它。

一般来说 – 静态库不包含xib或资源文件。 框架基本上是静态库,头文件和一些资源的封装(通常在一个包中)

您需要指定要在内部search笔尖的包。 否则,它只是(浅显地)search你的应用程序的资源目录。

 - (id)initWithTitle:(NSString *)aTitle { // replace 'framework' with 'bundle' for a static resources bundle NSURL *frameworkURL = [[NSBundle mainBundle] URLForResource:@"myFrameworkName" withExtension:@"framework"]; NSBundle *framework = [NSBundle bundleWithURL:frameworkURL]; self = [super initWithNibName:@"ViewControllerWithinFramework" bundle:framework]; if (self) { _viewControllerTitle = aTitle; } return self; } 

在main.m中试试这个

 #import "ViewControllerWithinFramework.h" //import this int main(int argc, char *argv[]) { @autoreleasepool { [ViewControllerWithinFramework class]; //call class function return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } 

我希望这会对你有用。

我将以这样的方式回答你达成你想要的结果的方式,但这可能不是最好的方法,改进是非常值得欢迎的! 我在Cocoa Touch Framework子项目上做了这个,但是如果Cocoa Touch Static Library已经不能工作的话,应该很容易适应。 说实话,这将更像是一个教程,而不是一个答案,但无论如何…

首先是事情。 我的解决scheme快速概览:您将在同一工作区有两个项目。 一个是框架,另一个是应用程序本身。 您将在框架上创buildxib / storyboard,并在框架或应用程序中使用它(尽pipe后者对我来说没有多大意义)。

框架项目将包含一个构build运行脚本,将其所有的资源(图像,xibs,故事板)复制到一个包,该包将成为应用程序项目的一部分。 此外,框架项目将是您的应用程序项目的依赖项。 这样,无论何时编译应用程序,构build运行脚本都应该自动运行,并在打包应用程序之前更新资源包。

这确实不是一个快速和容易的事情,但这就是为什么我回答你的问题。 这样我就可以自己回到这里,记住我是如何达到同样的目标的。 你永远不知道什么时候阿尔茨海默症会打你:)

无论如何,让我们开始工作。

  1. 创build/打开你的应用程序的项目。 我将这个称为AppProject
  2. 创build一个框架/库项目,并将其作为子项目添加到AppProject ,或者只是将当前的框架项目拖到AppProject 。 这里重要的是框架项目是AppProject的一个子项目。 我将把框架项目称为MyFramework
  3. configuration您的特定项目所需的任何东西。 我想这是一个标准的事情,至less使用链接器标志-all_load-all_load 。 这对于这个迷你教程的目的并不是很有用,但是项目至less应该编译。 你的工作空间应该是这样的:

    工作区

  4. 打开AppProject文件夹并创build一个名为MyBundle.bundle的新目录。

  5. MyBundle.bundle拖动到AppProject (这很重要:您应该将其拖到库项目!)。 Copy items if needed ,您可能想要取消Copy items if needed并在编译应用程序时select/检查将该软件包复制到的目标。
  6. 现在单独留下MyBundle.bundle
  7. 转到MyFrameworkBuild Settings并添加一个新的Run script phase
  8. 这一步可能是可选的,但它为我这样工作,所以我包括它。 将刚才创build的Run script Compile sources阶段的正上方。
  9. 编辑Run script阶段,如果它不是已经存在的,将它的shell更改为/bin/sh 。 将脚本设置为以下(注释应解释脚本):

运行脚本

 #!/bin/bash echo "Building assets bundle." # Bundle name (without the ".bundle" part). I separated this because I have different app targets, each one with a different bundle. BUNDLE_NAME='MyBundle' CURRENT_PATH=`pwd` # This should generate the full path to your bundle. Might need to be adapted if the bundle # directory you created was in another directory. BUNDLE_PATH="$CURRENT_PATH/$BUNDLE_NAME.bundle" # Check if the bundle exists, and removes it completely if it does. if [ -d "$BUNDLE_PATH" ]; then rm -rf "$BUNDLE_PATH" fi # Re-creates the same bundle (I know this is weird. But at least I am SURE the bundle will # be clean for the next steps) mkdir "$BUNDLE_PATH" # Copy all .jpg files to the bundle find ./ -name *.jpg -type f -print0 | xargs -0 -J% cp % "$BUNDLE_PATH" # Copy all .png files to the bundle find ./ -name *.png -type f -print0 | xargs -0 -J% cp % "$BUNDLE_PATH" # Copy all .xib files to the bundle. find ./ -name *.xib -type f -print0 | xargs -0 -J% cp % "$BUNDLE_PATH" # Copy all .storyboard files to the bundle. find ./ -name *.storyboard -type f -print0 | xargs -0 -J% cp % "$BUNDLE_PATH" # This is the golden thing. iOS code will not load .xib or storyboard files, you need to compile # them for that. That's what these loop do: they get each .xib / .storyboard file and compiles them using Xcode's # ibtool CLI. for f in "$BUNDLE_PATH/"*.xib ; do # $f now holds the complete path and filename a .xib file XIB_NAME="$f"; # Replace the ".xib" at the end of $f by ".nib" NIB_NAME="${f%.xib}.nib"; # Execute the ibtool to compile the xib file ibtool --compile "$NIB_NAME" "$XIB_NAME" # Since the xib file is now useless for us, remove it. rm -f "$f" done for f in "$BUNDLE_PATH/"*.storyboard ; do # $f now holds the complete path and filename a .storyboard file STORYBOARD_NAME="$f"; # Replace the ".storyboard" at the end of $f by ".storyboardc" (could've just added the final "c", but I like # to keep both loops equal) COMPILED_NAME="${f%.storyboard}.storyboardc"; # Execute the ibtool to compile the storyboard file ibtool --compile "$COMPILED_NAME" "$STORYBOARD_NAME" # Since the .storyboard file is now useless for us, remove it. rm -f "$f" done 
  1. 你的工作区/设置应该看起来像这样:

设置

  1. 转到AppProject Build phases ,并将您的框架添加到Link Binary with Libraries部分。 还要添加框架作为Target dependencies部分。

依赖

  1. 一切似乎都成立了。 在框架项目上创build一个xib或storyboard文件,并按照通常的方式创build视图(或视图控制器)。 这里没什么特别的 你甚至可以为你的组件和一切设置自定义类(只要自定义类在框架项目中,而不在应用程序项目中)。

在编译应用程序项目之前

before_compiling

编译应用程序项目后

after_compiling

  1. 在您的代码中,无论您需要加载您的NIB / Xib,请使用以下代码之一。 如果你设法追踪到目前为止,我甚至不需要告诉你,你需要将这些代码改编成你想用xib / Storyboard做的任何事情,所以… Enjoy 🙂

注册一个UITableView的单元格xib:

 NSString* bundlePath = [[NSBundle mainBundle] pathForResource:@"MyBundle" ofType:@"bundle"]; NSBundle* bundle = [NSBundle bundleWithPath:bundlePath]; UINib* nib = [UINib nibWithNibName:@"TestView" bundle:bundle]; [tableView registerNib:nib forCellReuseIdentifier:@"my_cell"]; 

推UIViewController到导航控制器:

 NSString* bundlePath = [[NSBundle mainBundle] pathForResource:@"MyBundle" ofType:@"bundle"]; NSBundle* bundle = [NSBundle bundleWithPath:bundlePath]; UIViewController* vc = [[UIViewController alloc] initWithNibName:@"BundleViewController" bundle:bundle]; // You can use your own classes instead of the default UIViewController [navigationController pushViewController:vc animated:YES]; 

从最初的UIViewController中以模态方式呈现UIStoryboard:

 NSString* bundlePath = [[NSBundle mainBundle] pathForResource:@"MyBundle" ofType:@"bundle"]; NSBundle* bundle = [NSBundle bundleWithPath:bundlePath]; UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"BundleStoryboard" bundle:bundle]; UIViewController* vc = [storyboard instantiateInitialViewController]; vc.modalPresentationStyle = UIModalPresentationFormSheet; vc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; [self presentViewController:vc animated:YES completion:^{}]; 

如果它不适合你(或访问这个问题/答案的人),请让我知道,我会尽力帮助。

作为另一种select,你可以直接把你的xib文件放到你的框架项目中,并且可以调用你的nib

Swift 3Swift 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]; 

最简单的方法是使用[NSBundle bundleForClass:[self class]]来获取框架的NSBundle实例。 这将不能够通过Bundle ID获得框架的NSBundle实例,但这通常是不必要的。

你的代码的问题是initWithNibName:@"Name" bundle:nil在给定包中获取一个名为Name.xib的文件。 由于bundlenil ,它看起来在主机应用程序的捆绑,而不是你的框架。

OP的问题的更正的代码是这样的:

 /*** 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; } 

唯一改变的是给出正确的bundle实例。