【问题标题】:Unable to use iOS framework which is internally using .xib file无法使用内部使用 .xib 文件的 iOS 框架
【发布时间】:2014-04-25 11:40:31
【问题描述】:

我正在尝试按照此 URL 中指定的步骤为 iOS 创建一个通用框架: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;
}

在创建框架时,我在其Copy Bundle Resources 构建阶段包含了所有 .xib 文件,包括 ViewControllerWithinFramework.xib。

现在我的问题是,当我尝试将该框架集成到其他项目中时,它会崩溃并显示以下堆栈跟踪:

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,它就可以正常工作。

【问题讨论】:

  • 您能否确认 nib 包含在 ipa 捆绑包中?找到安装 ipa 的模拟器目录,显示包内容,然后查看您的 nib 是否确实存在。

标签: ios objective-c cocoa-touch ios7 xib


【解决方案1】:

作为另一种选择,您可以直接将您的 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];

【讨论】:

    【解决方案2】:

    最简单的方法是使用[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 实例。

    【讨论】:

      【解决方案3】:

      我将以实现您预期结果的方式回答这个问题,但这可能不是最好的方法,非常欢迎改进!我是在 Cocoa Touch Framework 子项目上完成的,但如果它还不能工作,它应该很容易适应 Cocoa Touch Static Library。老实说,这更像是一个教程而不是一个答案,但无论如何......

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

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

      设置起来肯定不是一件快速简单的事情,但这就是我回答您的问题的原因。这样我就可以自己回到这里,记住我是如何实现同样的目标的。你永远不知道老年痴呆症什么时候会袭击你:)

      不管怎样,让我们​​开始工作吧。

      1. 创建/打开您应用的项目。我将把它称为 AppProject

      2. 创建一个框架/库项目并将其作为子项目添加到 AppProject,或者只是将您当前的框架项目拖到 AppProject。这里重要的是框架项目是 AppProject 的子项目。我将把框架项目称为 MyFramework

      3. 为您的特定项目配置您需要的任何东西。我想至少使用链接器标志-ObjC-all_load 是一件标准的事情。这对于这个迷你教程的目的并不是很有用,但该项目至少应该正在编译。您的工作区应该是这样的:

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

      5. MyBundle.bundle 拖到 AppProject(这很重要:您应该将它拖到库项目中!) .您可能希望取消选中 Copy items if needed 并选择/检查在编译您的应用程序时该捆绑包将被复制到的目标。

      6. 暂时别管 MyBundle.bundle

      7. 转到 MyFrameworkBuild Settings 并添加一个新的Run script phase

      8. 这一步可能是可选的,但它对我来说就像这样,所以我将它包括在内。将刚刚创建的Run script 拖动到Compile sources 阶段的正上方。

      9. 编辑Run script 阶段,将其shell 更改为/bin/sh(如果还没有的话)。将脚本设置为以下(cmets 应解释脚本):

      运行脚本

      #!/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. 似乎一切都设置好了。在框架项目上创建一个 xib 或故事板文件,并按照您通常的方式创建视图(或视图控制器)。这里没什么特别的。您甚至可以为您的组件和所有内容设置自定义类(只要自定义类在框架项目中,而不是在应用程序项目中)。

      编译应用项目之前

      编译应用项目后

      1. 在您的代码中,无论您需要在何处加载您的 NIB/Xib,请使用以下代码之一。如果你能做到这一点,我什至不需要告诉你,你需要将这些代码调整为你想用 xib/Storyboard 做的任何事情,所以......享受吧:)

      为 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:^{}];
      

      如果它对您(或访问此问题/答案的人)不起作用,请告诉我,我会尽力提供帮助。

      【讨论】:

        【解决方案4】:

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

        您需要通过传递NSBundle 对象而不是nil 来指定此捆绑包。您可以按如下方式获取您的捆绑对象:

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

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

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

        这也适用于 Interface Builder。

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

        【讨论】:

        • 您好,我有一个包含大量资源的静态库。我想从 xcassets 访问图像,但无法访问。您能建议任何解决方法吗?
        【解决方案5】:

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

        我希望这对你有用。

        【讨论】:

        • 你试过我的答案了吗?如果是,它是否解决了您的问题。或者你有任何其他解决方案。如果是这样,请发布答案。
        【解决方案6】:

        不幸的是,因为 iOS 没有公开的动态片段加载概念,所以 NSBundle 的一些最有用的功能有点难以实现。 您要做的是向 NSBundle 注册框架包,从那时起,您可以通过它的标识符找到该包 - 系统应该能够在该包中正确找到 nib 等。 请记住,框架只是一种捆绑包。

        要让 NSBundle “看到”你的包和它的元信息(来自它的 Info.plist),你必须让它尝试加载包。它将记录一个错误,因为没有将 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: 找到您的框架包,其中标识符是框架 Info.plist 中的 CFBundleIdentifier。 如果您需要直接使用UINib 直接在该点加载您的视图控制器笔尖,使用bundleWithIdentifier: 定位捆绑包并将该值赋予nibWithNibName:bundle: 应该很容易。

        【讨论】:

        • 我相信这实际上不仅仅是解决 OP 问题所必需的。由于获取 nib 的代码是框架的一部分,因此可以只使用 [NSBundle bundleForClass:[self class] 获取框架的包实例并在 initWithNibName:bundle: 中使用它。
        【解决方案7】:

        您需要指定要在内部搜索笔尖的捆绑包。否则,它只会(浅浅地)搜索应用程序的资源目录。

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

        【讨论】:

          【解决方案8】:

          XIB 附带的框架通常也附带捆绑包 - 因此您可能不应该在框架部分传递 nil。

          右键单击框架 -> 在查找器中显示 打开它并查看资源文件夹中的包名称是什么(例如 - Facebook 使用 FBUserSettingsViewResources.bundle)并使用它。

          通常 - 静态库不包含 xib 或资源文件。 框架基本上是静态库、头文件和一些资源(通常在包中)的包装器

          【讨论】:

          • 而且 iOS 开发原生不支持 3rd 方框架,所以你仍然需要自己添加资源并设置正确的 headers 路径。
          猜你喜欢
          • 2012-01-18
          • 1970-01-01
          • 2018-10-26
          • 1970-01-01
          • 2022-01-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-08-20
          相关资源
          最近更新 更多