Integrando UIKit com SwiftUI usando UIViewRepresentable — Parte 1 First Responder.

João Vitor
Code With Coffee

--

Vamos falar sobre como integrar suas UIViews em interfaces SwiftUI, utilizaremos de exemplo a classe UITextField e criaremos uma lógica para que tenhamos controle sobre dizer quem é o first responder.

Com a chegada do SwiftUI, vieram muitas novidades, com um foco muito maior em programação reativa, a chegada das Views com o intuito de representar parte da UI do seu app, a remoção do acoplamento que tínhamos antes entre UIView e UIViewController. Em fim, muitas mudanças que vão guiar como desenvolvemos nossos aplicativos no futuro.

O Problema

Sem dúvida o SwiftUI facilita muito a vida do desenvolvedor, sua nova maneira de criar interfaces e como nos comunicamos com ela. Porém, nem todas as necessidades do desenvolvedor foram supridas pelo SwiftUI.

Ter acesso ao controle de foco, de quando uma TextField precisa ganhar ou perder foco é um dos exemplos mais comuns para problemas que o SwiftUI ainda não nos fornece uma solução adequada. Ter acesso ao controle do first responder é uma necessidade que muitos apps tem, então, usarei deste exemplo para ensinar como integrar suas UIViews no SwiftUI.

Quando uma UITextField é o first responder, seja por que um usuário tocou numa UITextField ou por que o programador chamou a função becomeFirstResponder(), o sistema automaticamente foca a text field, exibe o teclado e prepara a text field para inserção de dados.

O App

Exemplo do nosso app

Para mostrar como realizar isso em SwiftUI criei um app bastante simples, composto de um botão escrito "Focar" e um TextField com uma simples borda para destacar do fundo branco, ambos dentro de uma VStack.

A intenção deste app é que ao tocar no botão "Focar" fará com que o TextField chame a função becomeFirstResponder().
Como Observado, não existe nenhuma função em SwiftUI que faça com que o TextField ganhe foco sem que o usuário toque nele, para fazer isso, vamos ter que recorrer ao TextField do UIKit.

Criaremos uma nova estrutura (struct), chamei-a de "TextFieldResponder" e ela vai conformar com o protocolo UIViewRepresentable.

O protocolo UIViewRepresentable é utilizado para criar e manipular uma UIView dentro do SwiftUI.
Para conformar com o protocolo, precisamos implementar duas funções, sendo elas: makeUIView(context: Context) e
updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<TextFieldResponder>).
ambos recebem um objeto context, um context é um objeto usado para acessar seu coordinator, falaremos do coordinator na segunda parte.

O makeUIView, como implica o nome, cria e configura o estado inicial da sua UIView, nele criaremos e retornaremos um simples UITextField. Caso queira modificar mais sua UIView, toda a configuração inicial dela deve ser feito aqui (Texto placeholder, tamanhos, bordas e etc…)

O updateUIView serve para atualizarmos o estado da sua UIView com informações providas, geralmente, pelo SwiftUI (Como por exemplo, se sua view foi invalidada por causa de uma mudança em uma variável com a notação @ State ). Nela adicionaremos uma condicional para sabermos quando darmos (becomeFirstResponder() ) e removermos (resignFirstResponder() ) o foco do nosso UITextField.

Precisaremos criar também duas variáveis com a notação @Binding, uma para o texto do UITextField e outra para controlarmos o responder.

Com isso ja podemos ter o comportamento que queríamos, volte ao ContentView que criamos e substitua o TextField por (text: $text, isFirstResponder: $isTextFieldFirstResponder) e crie a variavel com notacao state isTextFieldFirstResponder.

@State var isTextFieldFirstResponder = false
...
var body: some View {
...
TextFieldResponder(text: $text, isFirstResponder: $isTextFieldFirstResponder)
...
}

execute o app e observe, todas as nossas configurações de padding e borda sumiram.

O nosso botão, foi para o limite mais ao sul do nosso app enquanto nosso TextField agora esta praticamente invisível. O que acontece é que as configurações do SwiftUI podem não funcionar nos objetos do UIKit, para corrigirmos isso, vamos adicionar ao nosso TextFieldResponder, na função makeUIView três novas linhas entre a criação do UITextField e seu retorno, são elas:

textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
textField.setContentHuggingPriority(.defaultLow, for: .horizontal)
textField.borderStyle = .roundedRect

Agora que configuramos a borda diretamente pelo UIKit podemos apagar a configuração de borda do SwiftUI no nosso ContentView. adicione também a linha:

self.isTextFieldFirstResponder = true

dentro do action do seu button no ContentView, execute o código e veja que ao tocar no botão Focar, o teclado aparece e o TextView ganha foco.

Percebe-se que mesmo colocando o TextField como first responder, alguns comportamentos ainda não são automáticos, por exemplo quando apertamos em return, o teclado não desaparece, veremos como resolver isso na Parte 2.

Obrigado por ler, espero que este tutorial tenha sido claro, estou trabalhando na segunda parte onde aprenderemos sobre a integração dos delegates com os coordinators e alguns detalhes mais!

--

--