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