Controlador, vá com calma! Retiramos o código no UIView

Você tem um grande UIViewController? Para muitos, sim. Por um lado, ele trabalha com dados, por outro - com a interface.

As tarefas de separação da lógica da interface são descritas em centenas de artigos sobre arquitetura: MVP, MVVM, VIPER. Eles resolvem o problema do fluxo de dados, mas não respondem à questão de como trabalhar com a interface: em um local resta a criação de elementos, layout, configuração, processamento de entrada e animação.

Vamos separar a visualização do controlador e ver como o loadView () nos ajuda.



A interface do aplicativo para iOS é a hierarquia do UIView . As tarefas de cada view : criar elementos, personalizar, organizar em locais, animar. Isso pode ser visto nos métodos que estão na classe UIView: addSubview(), drawRect(), layoutSubviews().

Se você observar os métodos da classe UIViewController , poderá ver que ele gerencia a view: carrega, responde ao carregamento de telas e ações do usuário e mostra novas telas. Frequentemente, o código que deveria estar no UIView , escrevemos nas subclasses do UIViewController , o que o torna muito grande. Separe.

loadView ()


O ciclo de vida de um UIViewController começa com loadView() . Uma implementação simplificada se parece com isso:

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

Podemos substituir o método e especificar nossa classe.

super.loadView() não precisa ser chamado!

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

Implementação CustomView.swift
 // CustomView.swift final class CustomView { let square: UIView = UIView() init() { super.init() square.backgroundColor = .red addSubview(square) } } 


O controlador irá carregar o CustomView, adicioná-lo à hierarquia, expor .frame . A propriedade .view será a classe que precisamos:

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

Mas enquanto o compilador não conhece a classe e acredita que existe um UIView normal. Vamos corrigir isso com uma função de conversão de tipo:

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

Agora você pode ver as variáveis CustomView :

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

Simplifique com o tipo associado
Ruslan Kavetsky propôs remover a duplicação de código usando a expansão do protocolo:

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

Para cada novo controlador, você só precisa especificar o protocolo e a subclasse para o seu UIView por meio de typealias :

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

Código em uma subclasse de UIView


Criando e configurando controles


Fontes, cores, constantes e hierarquia podem ser definidas diretamente no construtor CustomView:

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

layoutSubviews ()


O melhor lugar para um layout manual é o método layoutSubviews() . Ele é chamado toda vez que o tamanho da view é alterado, para que você possa confiar no tamanho dos bounds para os cálculos corretos:

 // 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, propriedades públicas


Se houver tempo, tornarei os controles de property privados, mas os gerencio por meio de variáveis ​​ou funções públicas "no campo do conhecimento". Um exemplo mais simples:

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

A vantagem do encapsulamento: a lógica interna está oculta por trás da interface. Por exemplo, a validade de um objeto pode ser indicada pela cor da área, não pelo quadrado, mas o controlador não saberá nada sobre ele.

O que resta em viewDidLoad ()?


Se você usa o Interface Builder, geralmente o viewDidLoad() vazio. Se você criar uma view no código, precisará vincular suas ações através do padrão de ação de destino, adicionar um UIGestureRecognizer ou vincular delegados.

Personalizável através do Interface Builder


A subclasse para view pode ser configurada através do Interface Builder (a seguir IB).

Você precisa selecionar o objeto de view (não o controlador) e definir sua classe. Não é necessário escrever seu próprio loadView() , o controlador fará isso sozinho. Mas UIView ainda precisa UIView tipo UIView .



IBOutlet em UIView


Se você selecionar o controle dentro da view , o Editor do Assistente reconhecerá a classe UIView e a oferecerá como o segundo arquivo no modo Automático. Então você pode transferir o IBOutlet para view .



Se não estiver funcionando
Abra a classe CustomView manualmente, escreva IBOutlet . Agora você pode arrastar pelo marcador e passar o mouse sobre um elemento no IB.



Se você criar uma interface no código, todos os objetos estarão acessíveis após o init() , mas ao trabalhar com o IB, o acesso ao IBOutlet aparecerá apenas após o carregamento da interface do UIStoryboard no método awakeFromNib() :

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

IBAction no UIViewController


Para meu gosto, o controlador deve deixar todas as ações do usuário. Do padrão:

  • ação-alvo dos controles
  • delegar implementação no UIViewController
  • implementação de bloco
  • reação à Notification

Nesse caso, o UIViewController controla apenas a interface. Tudo relacionado à lógica de negócios deve ser retirado do controlador, mas esta é uma opção: MVP, VIPER etc.

Objetivo-c


No Objective-C, você pode substituir completamente o tipo de UIView . Para fazer isso, declare a propriedade com a classe desejada, substitua setter e getter , especificando a classe:

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

O fim


No exemplo do GitHub, você pode observar a separação de classes para uma tarefa simples: a cor do quadrado depende da sua posição (na área verde é verde, fora de vermelho).

Quanto mais complexa a tela, melhor o efeito: o controlador é reduzido, o código é transferido para seu lugar. O código é simplesmente portado para view , mas o encapsulamento facilita a interação e a leitura do código. Às vezes, a view pode ser reutilizada com outro controlador. Por exemplo, diferentes controladores para iPhone e iPad reagem de maneira própria à aparência do teclado, mas isso não altera o código de view .

Usei esse código em diferentes projetos e com pessoas diferentes, toda vez que a equipe aceitava a simplificação e adotava a prática. Espero que você goste também. Tudo fácil UIViewController !

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


All Articles