objective-c - software - xcode 8 tutorial español
xcode 6 IB_DESIGNABLE- no está cargando recursos del paquete en el generador de interfaces (6)
Estoy tratando de hacer un control personalizado que se actualice en vivo en Interface Builder usando la nueva opción IB_DESIGNABLE que se describe here .
- (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];
}
Cuando corro en el simulador, obtengo un cuadro rojo con mi imagen en el centro (como se esperaba):
Pero cuando intento usar el Interface Builder, solo aparece como un cuadro rojo (sin imagen en el medio):
Cuando lo depuro: Editor-> Depurar vistas seleccionadas, revela que todo lo que se carga del paquete es nulo. Tanto plistPath como tsliderOff aparecen como nulos.
Me aseguré de que btn_slider_off.png se incluyera en Targets-> myFrameWork-> Build Phases-> Copy Bundle Resources.
¿Alguna idea de por qué Interface Builder no ve el archivo png durante la edición, pero se muestra bien cuando se ejecuta? "Crear una vista personalizada que se rinda en Interface Builder" está un poco limitado si no puedo cargar ninguna imagen para renderizar ...
Edición basada en solución por rickster.
rickster me señaló la solución: el problema es que los archivos no se encuentran en mainBundle, sino en [NSBundle bundleForClass: [self class]]; Y parece que [UIImage imageNamed: @ "btn_slider_off.png"] usa mainBundle automáticamente.
¡El siguiente código funciona!
#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];
A partir de la fecha en que se hizo esta pregunta por primera vez, la creación de un control designable por el IB requería su empaquetado en un marco objetivo. Ya no tiene que hacer eso, ya que Xcode 6.0 (y más adelante) de envío también tendrá una vista previa de los controles de diseño de IB desde el objetivo de su aplicación. Sin embargo, el problema y la solución son los mismos.
¿Por qué? [NSBundle mainBundle]
devuelve el paquete primario de la aplicación actualmente en ejecución. Cuando llama a eso desde un marco, obtiene un paquete diferente que se devuelve según la aplicación que carga su marco. Cuando ejecutas tu aplicación, tu aplicación carga el marco. Cuando usa el control en IB, una aplicación especial de ayuda de Xcode carga el marco. Incluso si su control de diseño de IB está en su objetivo de aplicación, Xcode está creando una aplicación de ayuda especial para ejecutar el control dentro de IB.
¿La solución? Llame a +[NSBundle bundleForClass:]
lugar (o NSBundle(forClass:)
en Swift). Esto le permite obtener el paquete que contiene el código ejecutable para la clase que especifique. (Puede usar [self class]
/ self.dynamicType
allí, pero tenga en cuenta que el resultado cambiará para las subclases definidas en diferentes paquetes).
Si está utilizando el enfoque de marco (que puede ser útil para algunas aplicaciones, aunque ya no es necesario para los controles de diseño del IB), es mejor colocar los recursos de imagen en el mismo marco con el código que los usa. Si su código de marco espera usar los recursos proporcionados en el tiempo de ejecución por cualquier aplicación que cargue el marco, lo mejor que puede hacer para que sea designable por IB es falsificarlo. Implemente el método prepareForInterfaceBuilder
en su control y prepareForInterfaceBuilder
que cargue recursos de un lugar conocido (como el paquete de infraestructura o una ruta estática en su área de trabajo de Xcode).
Advertencia sobre la resolución del paquete / ruta anterior mientras se encuentra en IB_DESIGNABLE:
XCode no resolverá un recurso si la llamada está contenida en la inicialización de UIView. Solo pude obtener XCode para resolver una ruta mientras estaba en drawRect:
Encontré una solución excelente / fácil para las técnicas de resolución de paquetes anteriores, simplemente designe una propiedad IBInspectable
para que sea un UIImage
, luego especifique la imagen en Interface Builder. Ex:
@IBInspectable var mainImage: UIImage?
@IBInspectable var sideImage: UIImage?
EN SWIFT 3.0 el código de @rickster ha cambiado a:
// 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)
Lo he arreglado usando la siguiente línea de código:
let image = UIImage(named: "image_name", inBundle: NSBundle(forClass: self.dynamicType), compatibleWithTraitCollection: nil)
Después de implementarlo de esta manera, las imágenes se muestran correctamente en el Interface Builder.
Me encontré con un problema similar en Swift. A partir de Xcode 6 beta 3, no necesita usar un marco para obtener la representación en vivo. Sin embargo, todavía tiene que lidiar con el problema del paquete para Live View para que Xcode sepa dónde encontrar los activos. Suponiendo que "btn_slider_off" es una imagen establecida en Images.xcassets, entonces puede hacer esto en Swift para la representación en vivo y funcionará cuando la aplicación se ejecute normalmente también.
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)
}
También tuve el mismo problema: mirando esta respuesta y siguiendo WWDC 2014, ¿qué hay de nuevo en Interface Builder ? Así lo resolví:
- (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];
}
}