ios - apple - aula app
¿Cómo creo una clase de vista de iOS personalizada y crea una instancia de varias copias de la misma(en IB)? (4)
Actualmente estoy haciendo una aplicación que tendrá múltiples temporizadores, que básicamente son todos iguales.
Quiero crear una clase personalizada que use todo el código para los temporizadores, así como el diseño / animaciones, para que pueda tener 5 temporizadores idénticos que operan independientemente el uno del otro.
Creé el diseño usando IB (xcode 4.2) y todo el código para los temporizadores está actualmente en la clase viewcontroller.
Tengo dificultades para entender cómo encapsular todo en una clase personalizada y luego agregarlo al controlador de vistas; cualquier ayuda sería muy apreciada.
Ejemplo de Swift
Actualizado para Xcode 9 y Swift 4
Aquí hay un recorrido básico. Originalmente aprendí mucho de esto al ver esta serie de videos de Youtube . Más tarde, actualicé mi respuesta en base a este artículo .
Agregar archivos de vista personalizados
Los siguientes dos archivos formarán su vista personalizada:
- archivo .xib para contener el diseño
- archivo .swift como subclase
UIView
Los detalles para agregarlos están debajo.
Archivo Xib
Agregue un archivo .xib a su proyecto (Archivo> Nuevo> Archivo ...> Interfaz de usuario> Ver) . Estoy llamando al mío ReusableCustomView.xib
.
Cree el diseño que desea que tenga su vista personalizada. Como ejemplo, haré un diseño con UILabel
y UIButton
. Es una buena idea usar el diseño automático para que las cosas cambien de tamaño automáticamente, sin importar el tamaño que establezca más adelante. (Utilicé Freeform para el tamaño xib en el inspector de Atributos para poder ajustar las métricas simuladas, pero esto no es necesario).
Archivo Swift
Agregue un archivo .swift a su proyecto (Archivo> Nuevo> Archivo ...> Fuente> Archivo Swift) . Es una subclase de UIView
y llamo mina ReusableCustomView.swift
.
import UIKit
class ResuableCustomView: UIView {
}
Haga que el archivo Swift sea el propietario
Regrese a su archivo .xib y haga clic en "Propietario del archivo" en el contorno del documento. En Identity Inspector, escriba el nombre de su archivo .swift como el nombre de clase personalizado.
Agregar código de vista personalizado
Reemplace los contenidos del archivo ReusableCustomView.swift
con el siguiente código:
import UIKit
class ResuableCustomView: UIView {
let nibName = "ReusableCustomView"
var contentView:UIView?
@IBOutlet weak var label: UILabel!
@IBAction func buttonTap(_ sender: UIButton) {
label.text = "Hi"
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
self.addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
Asegúrese de escribir correctamente para el nombre de su archivo .xib.
Conecta los Outlets y las Acciones
Conecta las tomas de corriente y las acciones con el control arrastrando desde la etiqueta y el botón en el diseño de xib hasta el código de vista personalizado rápido.
Usa tu vista personalizada
Su vista personalizada ha terminado ahora. Todo lo que tienes que hacer es agregar un UIView
donde quieras en tu guión gráfico principal. Establezca el nombre de clase de la vista en ReusableCustomView
en el Inspector de identidad.
En el momento de escribir este documento, tenía problemas para agregar @IBDesignable al código, por lo que las vistas no se muestran en IB, pero sí cuando ejecutas tu aplicación. (En la imagen de arriba, le di a las vistas IB un color de fondo para que fueran visibles en el momento del diseño).
Bien para responder conceptualmente, su temporizador debería ser una subclase de UIView
lugar de NSObject
.
Para instanciar una instancia de su temporizador en IB simplemente arrastre un UIView
en la vista de su controlador de vista, y configure su clase con el nombre de clase de su temporizador.
Recuerde #import
su clase de temporizador en su controlador de vista.
Editar: para el diseño de IB (para la creación de instancias de código, ver el historial de revisión)
No estoy muy familiarizado con el guión gráfico, pero sí sé que puedes construir tu interfaz en IB usando un archivo .xib
que es casi idéntico al uso de la versión del guión gráfico; Incluso debería poder copiar y pegar sus vistas como un todo desde su interfaz existente al archivo .xib
.
Para probar esto, creé un nuevo .xib
vacío llamado "MyCustomTimerView.xib". Luego agregué una vista y agregué una etiqueta y dos botones. Al igual que:
UIView
un nuevo objetivo, clase C, subclasificación de UIView
llamado "MyCustomTimer". En mi .xib
configuré la clase Propietario del archivo como MyCustomTimer . Ahora puedo conectar acciones y puntos de venta como cualquier otra vista / controlador. El archivo .h
resultante se ve así:
@interface MyCustomTimer : UIView
@property (strong, nonatomic) IBOutlet UILabel *displayLabel;
@property (strong, nonatomic) IBOutlet UIButton *startButton;
@property (strong, nonatomic) IBOutlet UIButton *stopButton;
- (IBAction)startButtonPush:(id)sender;
- (IBAction)stopButtonPush:(id)sender;
@end
El único obstáculo que queda por saltar es obtener este .xib
en mi subclase UIView
. El uso de .xib
reduce drásticamente la configuración requerida. Y ya que está usando storyboards para cargar los temporizadores que conocemos -(id)initWithCoder:
es el único inicializador que se llamará. Así que aquí es cómo se ve el archivo de implementación:
#import "MyCustomTimer.h"
@implementation MyCustomTimer
@synthesize displayLabel;
@synthesize startButton;
@synthesize stopButton;
-(id)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super initWithCoder:aDecoder])){
[self addSubview:
[[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView"
owner:self
options:nil] objectAtIndex:0]];
}
return self;
}
- (IBAction)startButtonPush:(id)sender {
self.displayLabel.backgroundColor = [UIColor greenColor];
}
- (IBAction)stopButtonPush:(id)sender {
self.displayLabel.backgroundColor = [UIColor redColor];
}
@end
El método llamado loadNibNamed:owner:options:
hace exactamente lo que parece. Carga el Nib y establece la propiedad "Propietario del archivo" para uno mismo. Extraemos el primer objeto en la matriz y esa es la vista de la raíz del Nib. Agregamos la vista como una subvista y Voila está en la pantalla.
Obviamente, esto solo cambia el color de fondo de la etiqueta cuando se presionan los botones, pero este ejemplo debería ayudarte a avanzar.
Notas basadas en comentarios:
Vale la pena señalar que si tiene problemas de recursión infinitos probablemente se haya perdido el truco sutil de esta solución. No está haciendo lo que crees que está haciendo. La vista que se coloca en el guión gráfico no se ve, sino que carga otra vista como subvista. Esa vista que carga es la vista que se define en el plumín. El "propietario del archivo" en el plumín es esa vista invisible. Lo mejor de todo es que esta vista invisible sigue siendo una clase Objective-C que se puede usar como una especie de controlador de vista para la vista que trae desde el plumín. Por ejemplo, los métodos IBAction
en la clase MyCustomTimer
son algo que esperaría más en un controlador de vista que en una vista.
Como nota al margen, algunos pueden argumentar que esto rompe el MVC
y estoy de acuerdo en algo. Desde mi punto de vista, está más estrechamente relacionado con una UITableViewCell
personalizada, que a veces también tiene que ser un controlador de parte.
También vale la pena señalar que esta respuesta fue proporcionar una solución muy específica; crea una punta que pueda ser instanciada varias veces en la misma vista que se presenta en un guión gráfico. Por ejemplo, puede imaginar fácilmente seis de estos temporizadores en una pantalla de iPad a la vez. Si solo necesita especificar una vista para un controlador de vista que se utilizará varias veces en su aplicación, la solución que ofrece jyavenard a esta pregunta es casi seguramente una mejor solución para usted.
Esta no es realmente una respuesta, pero creo que es útil compartir este enfoque.
C objetivo
- Importe CustomViewWithXib.h y CustomViewWithXib.m a su proyecto
- Cree los archivos de vista personalizados con el mismo nombre (.h / .m / .xib)
- Heredar su clase personalizada de CustomViewWithXib
Rápido
- Importe CustomViewWithXib.swift a su proyecto
- Cree los archivos de vista personalizados con el mismo nombre (.swift y .xib)
- Heredar su clase personalizada de CustomViewWithXib
Opcional :
- Vaya a su archivo xib, establezca el propietario con su nombre de clase personalizado si necesita conectar algunos elementos (para obtener más detalles, consulte la parte Hacer que el archivo Swift sea el propietario de las respuestas de @Suragch)
Es todo, ahora puedes agregar tu vista personalizada a tu guión gráfico y se mostrará :)
CustomViewWithXib.h :
#import <UIKit/UIKit.h>
/**
* All classes inherit from CustomViewWithXib should have the same xib file name and class name (.h and .m)
MyCustomView.h
MyCustomView.m
MyCustomView.xib
*/
// This allows seeing how your custom views will appear without building and running your app after each change.
IB_DESIGNABLE
@interface CustomViewWithXib : UIView
@end
CustomViewWithXib.m :
#import "CustomViewWithXib.h"
@implementation CustomViewWithXib
#pragma mark - init methods
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// load view frame XIB
[self commonSetup];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
// load view frame XIB
[self commonSetup];
}
return self;
}
#pragma mark - setup view
- (UIView *)loadViewFromNib {
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
// An exception will be thrown if the xib file with this class name not found,
UIView *view = [[bundle loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
return view;
}
- (void)commonSetup {
UIView *nibView = [self loadViewFromNib];
nibView.frame = self.bounds;
// the autoresizingMask will be converted to constraints, the frame will match the parent view frame
nibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// Adding nibView on the top of our view
[self addSubview:nibView];
}
@end
CustomViewWithXib.swift :
import UIKit
@IBDesignable
class CustomViewWithXib: UIView {
// MARK: init methods
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonSetup()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonSetup()
}
// MARK: setup view
private func loadViewFromNib() -> UIView {
let viewBundle = NSBundle(forClass: self.dynamicType)
// An exception will be thrown if the xib file with this class name not found,
let view = viewBundle.loadNibNamed(String(self.dynamicType), owner: self, options: nil)[0]
return view as! UIView
}
private func commonSetup() {
let nibView = loadViewFromNib()
nibView.frame = bounds
// the autoresizingMask will be converted to constraints, the frame will match the parent view frame
nibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
// Adding nibView on the top of our view
addSubview(nibView)
}
}
Puedes encontrar algunos ejemplos here .
Espero que ayude.
Respuesta para controladores de vista, no vistas :
Hay una forma más fácil de cargar un xib desde un guión gráfico. Supongamos que su controlador es del tipo MyClassController que hereda de UIViewController
.
Agrega un UIViewController
utilizando IB en su guión gráfico; cambia el tipo de clase para que sea MyClassController. Elimine la vista que se agregó automáticamente en el guión gráfico.
Asegúrese de que el XIB que desea llamar se llame MyClassController.xib.
Cuando se creará una instancia de la clase durante la carga del guión gráfico, el xib se cargará automáticamente. La razón de esto se debe a la implementación predeterminada de UIViewController
que llama al XIB nombrado con el nombre de clase.