xcode 6 IB_DESIGNABLE- не загружать ресурсы из пакета в Интерфейсном Разработчике

Я пытаюсь создать пользовательский элемент управления, который обновляется в Интерфейсном Разработчике, используя новую опцию 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];
}

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

Но когда я пытаюсь использовать Interface Builder, он отображается только в виде красной рамки (без изображения в середине):Отображается в Интерфейсном Разработчике

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

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

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


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

Рикстер указал мне на решение - проблема в том, что файлы не живут в 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];

6 ответов

Решение

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

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

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

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

Я столкнулся с аналогичной проблемой в 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)
}

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

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

После такой реализации изображения правильно отображаются в Интерфейсном Разработчике

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

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

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

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

В 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)

У меня также была та же проблема: смотреть на этот ответ и следить за новостями WWDC 2014 в Интерфейсном Разработчике. Я решил это так:

- (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]; 
     }
}
Другие вопросы по тегам