ios - otro - ¿Cómo incrustar una vista personalizada xib en una escena de guión gráfico?
pasar de un viewcontroller a otro swift 3 (9)
He estado usando este fragmento de código durante años. Si planea tener vistas de clase personalizadas en su XIB, simplemente colóquelo en el archivo .m de su clase personalizada.
Como efecto secundario, se llama a awakeFromNib para que pueda dejar allí su código de inicio / configuración.
- (id)awakeAfterUsingCoder:(NSCoder*)aDecoder {
if ([[self subviews] count] == 0) {
UIView *view = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil][0];
view.frame = self.frame;
view.autoresizingMask = self.autoresizingMask;
view.alpha = self.alpha;
view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
return view;
}
return self;
}
Soy relativamente nuevo en el mundo XCode / iOS; He hecho algunas aplicaciones basadas en el guión gráfico de un tamaño decente, pero nunca me he cortado los dientes en todo el asunto de la punta / xib. Quiero utilizar las mismas herramientas para escenas para diseñar / diseñar una vista / control reutilizable. Así que creé mi primer xib para mi subclase view y lo pinté:
Tengo mis enchufes conectados y la configuración de las restricciones, al igual que estoy acostumbrado a hacer en el guión gráfico. Establecí la clase de mi File Owner
en la de mi subclase UIView
personalizada. Así que supongo que puedo crear una instancia de esta subclase de vista con alguna API, y se configurará / conectará como se muestra.
Ahora, de vuelta en mi guión gráfico, quiero incorporar / reutilizar esto. Lo estoy haciendo en una celda de prototipo de vista de tabla:
Tengo una vista. Le he puesto la clase a mi subclase. Creé una salida para poder manipularlo.
La pregunta de $ 64 es ¿dónde / cómo puedo indicar que no es suficiente simplemente poner una instancia vacía / no configurada de mi subclase de vista allí, sino usar el .xib que creé para configurar / instanciarlo? Sería genial, si en XCode6, pudiera simplemente ingresar el archivo XIB para utilizarlo en un UIView determinado, pero no veo un campo para hacerlo, así que supongo que tengo que hacer algo en el código en alguna parte.
(Veo otras preguntas como esta en SO, pero no he encontrado ninguna pregunta sobre esta parte del rompecabezas, ni estoy actualizado con XCode6 / 2015)
Actualizar
Puedo hacer que esto funcione al implementar el awakeFromNib
mi celda de la tabla de la siguiente manera:
- (void)awakeFromNib
{
// gather all of the constraints pointing to the uncofigured instance
NSArray* progressConstraints = [self.contentView.constraints filteredArrayUsingPredicate: [NSPredicate predicateWithBlock:^BOOL(id each, NSDictionary *_) {
return (((NSLayoutConstraint*)each).firstItem == self.progressControl) || (((NSLayoutConstraint*)each).secondItem == self.progressControl);
}]];
// fetch the fleshed out variant
ProgramProgressControl *fromXIB = [[[NSBundle mainBundle] loadNibNamed:@"ProgramProgressControl" owner:self options:nil] objectAtIndex:0];
// ape the current placeholder''s frame
fromXIB.frame = self.progressControl.frame;
// now swap them
[UIView transitionFromView: self.progressControl toView: fromXIB duration: 0 options: 0 completion: nil];
// recreate all of the constraints, but for the new guy
for (NSLayoutConstraint *each in progressConstraints) {
id firstItem = each.firstItem == self.progressControl ? fromXIB : each.firstItem;
id secondItem = each.secondItem == self.progressControl ? fromXIB : each.secondItem;
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem: firstItem attribute: each.firstAttribute relatedBy: each.relation toItem: secondItem attribute: each.secondAttribute multiplier: each.multiplier constant: each.constant];
[self.contentView addConstraint: constraint];
}
// update our outlet
self.progressControl = fromXIB;
}
¿Es esto tan fácil como se consigue entonces? ¿O estoy trabajando demasiado para esto?
La respuesta "correcta" es que no está destinado a hacer vistas reutilizables con las puntas correspondientes. Si una subclase de vista es valiosa como objeto reutilizable, rara vez necesitará una punta para acompañarla. Tomemos como ejemplo cada subclase de vista proporcionada por UIKit. Parte de este pensamiento es una subclase de vista que en realidad es valiosa y no se implementará usando una punta, que es la vista general en Apple.
Por lo general, cuando utiliza una vista en punta o guión gráfico, querrá ajustar gráficamente para el caso de uso dado de todos modos.
Puede considerar usar "copiar y pegar" para recrear vistas iguales o similares en lugar de hacer plumillas separadas. Creo que esto cumple los mismos requisitos y te mantendrá más o menos en línea con lo que Apple está haciendo.
Si bien no recomiendo la ruta que está bajando, se puede hacer colocando una "vista de controlador de vista incrustada" donde desee que aparezca la vista.
Incruste un controlador de vista que contenga una vista única: la vista que desea reutilizar.
Solo tiene que arrastrar y soltar UIView
en su IB y UIView
y configurarlo
yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject];
[self.view addSubview:yourView]
Paso
- Agregar nuevo archivo => Interfaz de usuario => UIView
- Establecer clase personalizada - yourUIViewClass
- Establecer ID de restauración - yourUIViewClass
-
yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject]; [self.view addSubview:yourView]
Ahora puedes personalizar la vista como quieras.
Una forma genial y reutilizable de hacer este Interface Builder y Swift 3 (Swift 2 es similar, pero tendrás que actualizarlo tú mismo):
Crea una nueva clase como esta:
import Foundation import UIKit @IBDesignable class XibView: UIView { @IBInspectable var xibName: String? override func awakeFromNib() { guard let name = self.xibName, let xib = Bundle.main.loadNibNamed(name, owner: self), let views = xib as? [UIView], views.count > 0 else { return } self.addSubview(views[0] as! UIView) } }
En su guión gráfico, cree una vista que albergue el archivo .xib. Dale un nombre de clase de
XibView
:En el inspector de propiedades del nuevo XibView, configure el nombre de su .xib (sin la extensión de archivo):
Una cosa a tener en cuenta: esto supone que está haciendo coincidir el marco .xib con su contenedor. Si no lo hace, o necesita que se pueda cambiar el tamaño, deberá agregar algunas restricciones programáticas o modificar el marco de la subvista para que se ajuste. Uso snapkit para facilitar las cosas:
xibView.snp_makeConstraints(closure: { (make) -> Void in
make.edges.equalTo(self)
})
Una versión un poco más rápida de la idea de @brandonscript con retorno anticipado:
override func awakeFromNib() {
guard let xibName = xibName,
let xib = Bundle.main.loadNibNamed(xibName, owner: self, options: nil),
let views = xib as? [UIView] else {
return
}
if views.count > 0 {
self.addSubview(views[0])
}
}
Ya casi estás ahí. Debe anular initWithCoder en la clase personalizada a la que asignó la vista.
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"ViewYouCreated" owner:self options:nil] objectAtIndex:0]];
}
return self; }
Una vez hecho esto, StoryBoard sabrá cargar el xib dentro de UIView.
Aquí hay una explicación más detallada:
Así es como se ve tu UIViewController
en tu story board:
El espacio azul es básicamente una UIView que "sostendrá" tu xib.
Este es tu xib:
Hay una acción conectada a un botón que imprimirá algo de texto.
y este es el resultado final:
La diferencia entre el primer clickMe y el segundo es que el primero se agregó al UIViewController
utilizando StoryBoard
. El segundo fue agregado usando código.
awakeAfterUsingCoder:
implementar awakeAfterUsingCoder:
en su subclase UIView
personalizada. Este método le permite intercambiar el objeto descodificado (del guión gráfico) con un objeto diferente (de su xib reutilizable), así:
- (id) awakeAfterUsingCoder: (NSCoder *) aDecoder
{
// without this check you''ll end up with a recursive loop - we need to know that we were loaded from our view xib vs the storyboard.
// set the view tag in the MyView xib to be -999 and anything else in the storyboard.
if ( self.tag == -999 )
{
return self;
}
// make sure your custom view is the first object in the nib
MyView* v = [[[UINib nibWithNibName: @"MyView" bundle: nil] instantiateWithOwner: nil options: nil] firstObject];
// copy properties forward from the storyboard-decoded object (self)
v.frame = self.frame;
v.autoresizingMask = self.autoresizingMask;
v.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
v.tag = self.tag;
// copy any other attribtues you want to set in the storyboard
// possibly copy any child constraints for width/height
return v;
}
Aquí hay una muy buena reseña sobre esta técnica y algunas alternativas.
Además, si agrega IB_DESIGNABLE
a su declaración @interface, y proporciona un método initWithFrame:
puede obtener una vista previa en tiempo de diseño para trabajar en IB (se requiere Xcode 6):
IB_DESIGNABLE @interface MyView : UIView
@end
@implementation MyView
- (id) initWithFrame: (CGRect) frame
{
self = [[[UINib nibWithNibName: @"MyView"
bundle: [NSBundle bundleForClass: [MyView class]]]
instantiateWithOwner: nil
options: nil] firstObject];
self.frame = frame;
return self;
}
- Crear archivo xib
File > New > New File > iOS > User Interface > View
- Crear clase UIView personalizada
File > New > New File > iOS > Source > CocoaTouch
- Asignar la identidad del archivo xib a la clase de vista personalizada
- En viewDidLoad del controlador de vista inicialice el xib y su archivo asociado utilizando
loadNibNamed:
enNSBundle.mainBundle
y la primera vista devuelta se puede agregar como una subvista de self.view. La vista personalizada cargada desde el plumín se puede guardar en una propiedad para establecer el marco en
viewDidLayoutSubviews
. Simplemente ajuste el marco en el marco deself.view
menos que necesite hacerlo más pequeño queself.view
.class ViewController: UIViewController { weak var customView: MyView! override func viewDidLoad() { super.viewDidLoad() self.customView = NSBundle.mainBundle().loadNibNamed("MyView", owner: self, options: nil)[0] as! MyView self.view.addSubview(customView) addButtonHandlerForCustomView() } private func addButtonHandlerForCustomView() { customView.buttonHandler = { [weak self] (sender:UIButton) in guard let welf = self else { return } welf.buttonTapped(sender) } } override func viewDidLayoutSubviews() { self.customView.frame = self.view.frame } private func buttonTapped(button:UIButton) { } }
Además, si desea hablar desde el xib a su instancia de
UIViewController
, cree una propiedad débil en la clase de la vista personalizada.class MyView: UIView { var buttonHandler:((sender:UIButton)->())! @IBAction func buttonTapped(sender: UIButton) { buttonHandler(sender:sender) } }
Aquí está el proyecto en GitHub