Подтвердить что ты не робот

Xcode 6 IB_DESIGNABLE - не загружать ресурсы из пакета в построителе интерфейса

Я пытаюсь создать настраиваемый элемент управления, который обновляется в интерфейсе Builder с помощью новой опции IB_DESIGNABLE, описанной здесь.

- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();

CGRect myFrame = self.bounds;
CGContextSetLineWidth(context, 10);
CGRectInset(myFrame, 5,5);
[[UIColor redColor] set];
UIRectFrame(myFrame);

NSBundle *bundle = [NSBundle mainBundle];
NSString *plistPath;

plistPath = [bundle pathForResource:@"fileExample" ofType:@"plist"];
UIImage *tsliderOff = [UIImage imageNamed:@"btn_slider_off.png"];

[tsliderOff drawInRect:self.bounds];
}

Когда я запускаю симулятор, я получаю красную рамку с моим изображением в центре (как и ожидалось): enter image description here

Но когда я пытаюсь использовать Interface Builder, он появляется только как красное поле (без изображения в середине): Rendered in Interface Builder

Когда я его отлаживаю: Editor- > Debug Selected Views, он показывает, что все, что загружено из пакета, равно nil. PlistPath и tsliderOff отображаются как ноль.

Я убедился, что btn_slider_off.png включен в Targets- > myFrameWork- > Build Phases- > Copy Bundle Resources.

Любая идея, почему Interface Builder не видит файл png во время редактирования, но отображается нормально при запуске? "Создание пользовательского представления, которое визуализирует в построителе интерфейса" немного ограничено, если я не могу загрузить изображения для рендеринга...


редактирование на основе решения rickster

Риккер указал мне на решение - проблема в том, что файлы не живут в mainBundle, они живут в [NSBundle bundleForClass: [self class]]; И кажется, что [UIImage imageNamed: @ "btn_slider_off.png" ] автоматически использует mainBundle.

Следующий код работает!

#if !TARGET_INTERFACE_BUILDER
NSBundle *bundle = [NSBundle mainBundle];
#else
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
#endif
NSString *fileName = [bundle pathForResource:@"btn_slider_off" ofType:@"png"];
UIImage *image = [UIImage imageWithContentsOfFile:fileName];
[image drawInRect:self.bounds];
4b9b3361

Ответ 1

С того момента, когда этот вопрос был впервые задан, создание управляемого IB элемента управления потребовало его упаковки в целевой среде. Вам больше не нужно это делать - доставка Xcode 6.0 (и более поздняя версия) также будет просматривать элементы управления, совместимые с IB, с целью вашего приложения. Однако проблема и решение одинаковы.

Почему? [NSBundle mainBundle] возвращает основной пакет текущего запущенного приложения. Когда вы вызываете это из фреймворка, вы получаете другой пакет, который возвращается на основе того, какое приложение загружает вашу фреймворк. Когда вы запускаете приложение, ваше приложение загружает фреймворк. Когда вы используете элемент управления в IB, специальное вспомогательное приложение Xcode загружает фреймворк. Даже если ваш управляемый IB-контроль находится в вашей целевой точке приложения, Xcode создает специальное вспомогательное приложение для запуска управления внутри IB.

Решение? Вместо этого вызовите +[NSBundle bundleForClass:] (или NSBundle(forClass:) в Swift). Это дает вам пакет, содержащий исполняемый код для любого заданного вами класса. (Вы можете использовать [self class]/self.dynamicType там, но будьте осторожны, результат изменится для подклассов, определенных в разных пакетах.)

Если вы используете фреймворк-подход, который может быть полезен для некоторых приложений, даже если он больше не требуется для управляемых IB элементов управления, лучше всего разместить ресурсы изображений в той же структуре с помощью кода, который их использует. Если ваш код инфраструктуры предполагает использовать ресурсы, предоставленные во время выполнения любым приложением, загружающим фреймворк, лучше всего сделать это для IB-дизайна, чтобы подделать его. Внедрите метод prepareForInterfaceBuilder в свой элемент управления и загрузите ресурсы из известного места (например, пакет структуры или статический путь в рабочем пространстве Xcode).

Ответ 2

Я столкнулся с аналогичной проблемой в Swift. Начиная с Xcode 6 beta 3, вам не нужно использовать фреймворк для рендеринга в реальном времени. Однако вам все равно придется иметь дело с проблемой пакета для Live View, чтобы Xcode знал, где найти активы. Предполагая, что "btn_slider_off" - это изображение, установленное в Images.xcassets, вы можете сделать это в Swift для живого рендеринга, и оно будет работать, когда приложение также будет запущено нормально.

let name = "btn_slider_off"
let myBundle = NSBundle(forClass: self.dynamicType)
// if you want to specify the class name you can do that instead
// assuming the class is named CustomView the code would be
// let myBundle = NSBundle(forClass: CustomView.self)
let image = UIImage(named: name, inBundle: myBundle, compatibleWithTraitCollection: self.traitCollection)
if let image = image {
    image.drawInRect(self.bounds)
}

Ответ 3

Предупреждение о вышеупомянутом разрешении пучка/пути, в то время как в IB_DESIGNABLE:

XCode не будет разрешать ресурс, если вызов содержится в инициализации UIView. Я могу получить XCode только для разрешения пути в drawRect:

Я нашел большое/легкое обходное решение для вышеупомянутых методов разрешения пакетов, просто просто присвойте свойству IBInspectable значение UIImage, а затем укажите изображение в Interface Builder. Пример:

@IBInspectable var mainImage: UIImage?
@IBInspectable var sideImage: UIImage?

Ответ 4

Я исправил его, используя следующую строку кода:

let image = UIImage(named: "image_name", inBundle: NSBundle(forClass: self.dynamicType), compatibleWithTraitCollection: nil)

После реализации этого способа изображения отображаются правильно в построителе интерфейса

Ответ 5

В SWIFT 3.0 код @rickster изменился на:

// DYNAMIC BUNDLE DEFINITION FOR DESIGNABLE CLASS
let theBundle : Bundle = Bundle(for: type(of: self))

// OR ALTERNATEVLY BY PROVDING THE CONCRETE NAME OF DESIGNABLE CLASS
let theBundle : Bundle = Bundle(for: RH_DesignableView.self)

// AND THEN SUCCESSFULLY YOU CAN LOAD THE RESSOURCE
let theImage  : UIImage? = UIImage(named: "Logo", in: theBundle, compatibleWith: nil)

Ответ 6

У меня также была та же проблема: глядя на этот ответ и после WWDC 2014 что нового в Interface Builder. Я решил это так:

- (void)prepareForInterfaceBuilder{
     NSArray *array =    [[NSProcessInfo processInfo].environment[@"IB_PROJECT_SOURCE_DIRECTORIES"] componentsSeparatedByString:@":"];
     if (array.count > 0) {
         NSString *str = array[0];
         NSString *newStr =  [str stringByAppendingPathComponent:@"/MyImage.jpg"];
         image = [UIImage imageWithContentsOfFile:newStr]; 
     }
}