Controlador, ¡tómalo con calma! Sacamos el código en UIView

¿Tienes un gran UIViewController? Para muchos sí. Por un lado, funciona con datos, por otro, con la interfaz.

Las tareas de separar la lógica de la interfaz se describen en cientos de artículos sobre arquitectura: MVP, MVVM, VIPER. Resuelven el problema del flujo de datos, pero no responden a la pregunta de cómo trabajar con la interfaz: en un lugar queda la creación de elementos, diseño, configuración, procesamiento de entrada y animación.

Separemos la vista del controlador y veamos cómo loadView () nos ayuda.



La interfaz de la aplicación para iOS es la jerarquía UIView . Las tareas de cada view : crear elementos, personalizar, organizar en lugares, animar. Esto se puede ver a partir de los métodos que están en la clase UIView: addSubview(), drawRect(), layoutSubviews().

Si observa los métodos de la clase UIViewController , puede ver que administra la view: carga, responde a las pantallas de carga y las acciones del usuario, y muestra nuevas pantallas. A menudo, el código que debe estar en el UIView , lo escribimos en subclases del UIViewController , esto lo hace demasiado grande. Separarlo

loadView ()


El ciclo de vida de un UIViewController comienza con loadView() . Una implementación simplificada se ve así:

 // CustomViewController.swift func loadView() { self.view = UIView() } 

Podemos anular el método y especificar nuestra clase.

super.loadView() no es necesario llamar a super.loadView() !

 // CustomViewController.swift override func loadView() { self.view = CustomView() } 

Implementación CustomView.swift
 // CustomView.swift final class CustomView { let square: UIView = UIView() init() { super.init() square.backgroundColor = .red addSubview(square) } } 


El controlador cargará CustomView, agregará a la jerarquía, expondrá .frame . La propiedad .view será la clase que necesitamos:

 // CustomViewController.swift print(view) // CustomView 

Pero mientras el compilador no sabe acerca de la clase y cree que hay una UIView normal. Arreglemos esto con una función de conversión de tipo:

 // CustomViewController.swift func view() -> CustomView { return self.view as! CustomView } 

Ahora puede ver las variables CustomView :

 // CustomViewController.swift func viewDidLoad() { super.viewDidLoad() view().square //  } 

Simplifica con el tipo asociado
Ruslan Kavetsky propuso eliminar la duplicación de código mediante la expansión del protocolo:

 protocol ViewSpecificController { associatedtype RootView: UIView } extension ViewSpecificController where Self: UIViewController { func view() -> RootView { return self.view as! RootView } } 

Para cada nuevo controlador, solo necesita especificar el protocolo y la subclase para su UIView mediante typealias :

 // CustomViewController.swift final class CustomViewController: UIViewController, ViewSpecificController { typealias RootView = CustomView func viewDidLoad() { super.viewDidLoad() view().square //  } } 

Código en una subclase de UIView


Crear y configurar controles


Las fuentes, los colores, las constantes y la jerarquía se pueden configurar directamente en el constructor CustomView:

 // CustomView.swift init() { super.init() backgroundColor = .lightGray addSubview(square) } 

layoutSubviews ()


El mejor lugar para un diseño manual es el método layoutSubviews() . Se llama cada vez que se cambia el tamaño de la view , por lo que puede confiar en el tamaño de los bounds para los cálculos correctos:

 // CustomView.swift override func layoutSubviews() { super.layoutSubviews() square.frame = CGRect(x: 0, y: 0: width: 200, height: 200) square.center = CGPoint(x: bounds.width / 2, y: bounds.height / 2) } 

Controles privados, propiedades públicas.


Si hay tiempo, entonces hago que los controles de property privados, pero los administro a través de variables o funciones públicas "en el campo del conocimiento". Un ejemplo más simple:

 // CustomView.swift private let square = UIView() var squarePositionIsValid: Bool { didSet { square.backgroundColor = squarePositionIsValid? .green : .red } } func moveSquare(to newCenter: CGPoint) { square.center = newCenter } 

La ventaja de la encapsulación: la lógica interna está oculta detrás de la interfaz. Por ejemplo, la validez de un objeto puede estar indicada por el color del área, no por el cuadrado, pero el controlador no sabrá nada al respecto.

¿Qué queda en viewDidLoad ()?


Si usa Interface Builder, a menudo viewDidLoad() vacío. Si crea una view en código, debe vincular sus acciones a través del patrón de acción de destino, agregar un UIGestureRecognizer o conectar delegados.

Personalizable a través de Interface Builder


La subclase para la view se puede configurar a través de Interface Builder (en adelante, IB).

Debe seleccionar el objeto de view (no el controlador) y establecer su clase. No es necesario escribir su propio loadView() , el controlador lo hará por sí mismo. Pero aún debe UIView tipo UIView .



IBOutlet en UIView


Si selecciona el control dentro de la view , el Editor Asistente reconoce la clase UIView y la ofrece como el segundo archivo en modo Automático. Para que pueda transferir IBOutlet para view .



Si no funciona
Abra la clase CustomView manualmente, escriba IBOutlet . Ahora puede arrastrar por el marcador y colocar el cursor sobre un elemento en IB.



Si crea una interfaz en código, todos los objetos son accesibles después de init() , pero cuando se trabaja con IB, el acceso a IBOutlet aparece solo después de cargar la interfaz desde UIStoryboard en el método awakeFromNib() :

 // CustomView.swift func awakeFromNib() { super.awakeFromNib() square.layer.cornerRadius = 8 } 

IBAction en UIViewController


Para mi gusto, el controlador debe dejar todas las acciones del usuario. De estándar:

  • acción objetivo de los controles
  • delegar implementación en UIViewController
  • implementación de bloque
  • reacción a la Notification

En este caso, el UIViewController controla solo la interfaz. Todo lo relacionado con la lógica empresarial debe sacarse del controlador, pero esta es una opción: MVP, VIPER, etc.

Objetivo-c


En Objective-C, puede reemplazar completamente el tipo UIView . Para hacer esto, declare la propiedad con la clase deseada, anule setter y getter , especificando la clase:

 // CustomViewController.m @interface CustomViewController @property (nonatomic) CustomView *customView; @end @implementation - (void)setView:(CustomView *)view{ [super setView:view]; } - (CustomView *)view { return (CustomView *)super.view; } @end 

El final


En el ejemplo de GitHub, puede ver la separación de clases para una tarea simple: el color del cuadrado depende de su posición (en el área verde es verde, afuera es rojo).

Cuanto más compleja sea la pantalla, mejor será el efecto: el controlador se reduce, el código se transfiere a su lugar. El código simplemente se transfiere a la view , pero la encapsulación facilita la interacción y la lectura del código. A veces, la view se puede reutilizar con otro controlador. Por ejemplo, diferentes controladores para iPhone y iPad reaccionan a su manera a la apariencia del teclado, pero esto no cambia el código de view .

Usé este código en diferentes proyectos y con diferentes personas, cada vez que el equipo agradeció la simplificación y retomó la práctica. Espero que lo disfrutes también. Todo fácil UIViewController !

Source: https://habr.com/ru/post/es432718/


All Articles