Невозможно использовать инфраструктуру iOS, которая внутренне использует .xib файл

Я пытаюсь сделать универсальную фреймворк для iOS следующими шагами, указанными в этом URL-адресе: 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;
}

При создании фреймворка я включил все .xib файлы, включая ViewControllerWithinFramework.xib в фазе сборки Copy Bundle Resources.

Теперь моя проблема в том, что когда я пытаюсь интегрировать эту структуру в другой проект, она падает с трассировкой ниже трассировки:

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.

Ответ 1

Если вы используете Universal-framework-iOS, все ресурсы (включая Nibs и изображения) будут скопированы внутри отдельного пакета (папки), например 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.

Ответ 2

К сожалению, поскольку у 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:, где идентификатор - это CFBundleIdentifier в вашей инфраструктуре Info.plist. Если вам нужно напрямую использовать UINib для непосредственного загрузки контроллера элемента управления в эту точку, вам должно быть легко найти узел с помощью bundleWithIdentifier: и дать это значение nibWithNibName:bundle:.

Ответ 3

Рамки, которые поставляются с XIB, обычно поставляются вместе с пакетами, поэтому вы, вероятно, не должны пропускать нуль в части фреймворка.

Щелкните правой кнопкой мыши рамку → Показать в поиске Откройте его и посмотрите, что имя пакета в папке ресурсов (например, Facebook использует FBUserSettingsViewResources.bundle) и используйте его.

В общем - статические библиотеки не включают файлы xib или resource. Frameworks в основном представляет собой оболочку для статической библиотеки, заголовков и некоторых ресурсов (обычно внутри пакета)

Ответ 4

Вам нужно указать пакет для поиска внутри для nib. В противном случае он просто (неглубоко) ищет ваш каталог ресурсов приложений.

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

Ответ 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

Я собираюсь ответить на этот вопрос так, как я достиг результатов, которые вы намеревались, но это может быть не лучший подход, улучшения более чем приветствуются! Я сделал это в подпроекте Cocoa Touch Framework, но его легко адаптировать к Cocoa Touch Static Library, если он не работает уже. Честно говоря, это будет скорее учебник, чем ответ, но в любом случае...

Прежде всего. Быстрый обзор моего решения: у вас будет два проекта на одном рабочем пространстве. Один из них - это основа, другая - это приложение. Вы создадите xib/раскадровку в фреймворке и будете использовать его либо в каркасе, либо в приложении (хотя последнее не имеет для меня большого смысла).

Проект framework будет содержать проект сборки script, который скопирует все его ресурсы (изображения, xibs, раскадровки) в пакет, и этот пакет станет частью проекта приложения. Кроме того, базовый проект будет зависеть от вашего проекта приложения. Таким образом, всякий раз, когда вы компилируете свое приложение, запуск сборки script должен запускаться автоматически и обновлять пакет ресурсов перед упаковкой вашего приложения.

Уверен, что это не простая и легкая задача, но почему я отвечаю на ваш вопрос. Таким образом, я смогу вернуться сюда сам и вспомнить, как я достиг той же цели. Вы никогда не знаете, когда Альцгеймер ударит вас:)

В любом случае, давайте работать.

  • Создайте/откройте проект приложения. Я буду ссылаться на это как AppProject.
  • Создайте проект framework/library и добавьте его в качестве подпроекта в AppProject или просто перетащите текущий проект рамки в AppProject. Здесь важно, что проект framework является подпроектом AppProject. Я буду ссылаться на проект рамки как MyFramework.
  • Настройте все, что вам нужно для ваших конкретных проектов. Я полагаю, что стандартное использование флажков компоновщика -ObjC и -all_load, по крайней мере. Это не очень полезно для целей этого мини-учебника, но проект, по крайней мере, должен компилироваться. Ваша рабочая область должна выглядеть примерно так:

    workspace

  • Откройте папку AppProject и создайте новый каталог с именем MyBundle.bundle.

  • Перетащите MyBundle.bundle в AppProject (это важно: вы НЕ должны перетащить его в проект библиотеки!), Вероятно, вы захотите снять флажок Copy items if needed и выбрать/проверить целевые объекты, на которые будет скопирован этот пакет при компиляции вашего приложения.
  • Оставьте MyBundle.bundle отдельно.
  • Перейдите в MyFramework Build Settings и добавьте новый Run script phase.
  • Этот шаг может быть необязательным, но для меня это работало так, поэтому я включаю его. Перетащите только что созданный Run script прямо над фазой Compile sources.
  • Отредактируйте фазу Run script, изменив ее оболочку на /bin/sh, если она уже не так. Установите script следующим образом (комментарии должны объяснять script):

Выполнить script

#!/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 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. Ваше рабочее пространство/настройки должно выглядеть примерно так:

settings

  1. Перейдите в AppProject Build phases и добавьте фреймворк в раздел Link Binary with Libraries. Также добавьте структуру как раздел Target dependencies.

dependencies

  1. Все, кажется, настроено. Создайте файл xib или раскадровки в проекте фреймворка и создайте представление (или контроллер просмотра) так, как обычно. Здесь ничего особенного. Вы даже можете настроить пользовательские классы для своих компонентов и всего (пока пользовательский класс находится внутри проекта framework, а не в проекте приложения).

Перед компиляцией проекта приложения

before_compiling

После компиляции проекта приложения

after_compiling

  1. В вашем коде, где вам нужно загрузить свой NIB/Xib, используйте один из следующих кодов. Если вам удалось зайти до сих пор, мне даже не нужно говорить вам, что вам нужно будет адаптировать эти коды к тому, что вы хотите сделать с помощью xib/Storyboard, поэтому... Наслаждайтесь:)

Регистрация ячейки xib для UITableView:

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

Представление модально UIStoryboard из него начального UIViewController:

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

Если это не работает для вас (или тот, кто посещает этот вопрос/ответ), пожалуйста, дайте мне знать, и я постараюсь помочь.

Ответ 7

В качестве еще одного варианта вы можете напрямую поместить свой xib файл в свой проект фреймворка и можете получить свой nib с вызовом

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

Ответ 8

Самый простой способ - использовать [NSBundle bundleForClass:[self class]] для получения экземпляра NSBundle вашего фреймворка. Это не даст возможности получить экземпляр framework NSBundle его идентификатором Bundle, но это обычно не требуется.

Проблема с вашим кодом - это initWithNibName:@"Name" bundle:nil получает файл с именем Name.xib в данном комплекте. Поскольку bundle - nil, он выглядит в комплекте приложений для хоста, а не в вашей инфраструктуре.

Исправленный код для проблемы с 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.