Swift / SwiftUI

UIAlertController com Construtores de Função

Pierre Felgines apresenta uma nova abordagem para simplificar a API do UIAlertController em Swift, utilizando o recurso de Function Builders introduzido na versão 5.1 da linguagem. O objetivo é criar um código mais legível e conciso, similar ao estilo SwiftUI, eliminando a necessidade de múltiplas instâncias de UIAlertAction.

Compartilhar
Image 1. Result

UIAlertController com Construtores de Função

Sempre achei a API do UIAlertController muito verbosa. Você primeiro tem que criar uma instância de UIAlertController, depois criar várias instâncias de UIAlertAction e finalmente adicionar as ações ao controlador.

Neste post, mostrarei como podemos aproveitar a nova funcionalidade do Swift 5.1 de Construtores de Função para criar uma API simplificada e altamente legível.

Nosso objetivo

Vamos usar este código de exemplo:

let alertController = UIAlertController(
    title: "Deleção",
    message: "Você tem certeza?",
    preferredStyle: .alert
)
let deleteAction = UIAlertAction(title: "Deletar", style: .destructive) { _ in
    // Executar deleção
}
let cancelAction = UIAlertAction(title: "Cancelar", style: .cancel)
alertController.addAction(deleteAction)
alertController.addAction(cancelAction)
present(alertController, animated: true)
Imagem 1. Resultado

Imagem 1. Resultado

Como disse anteriormente, há muito o que escrever e podemos fazer melhor. Podemos melhorar a API com padrões regulares do Swift patterns, mas o objetivo aqui é usar construtores de função para criar este código semelhante ao SwiftUI:

let alertController = Alert(title: "Deleção"", message: "Tem certeza?"") {
    Action.destructive("Excluir"") {
        // Executar exclusão
    }
    Action.cancel("Cancelar"")
}
present(alertController, animated: true)

Function Builders

Esta nova funcionalidade introduzida no Swift 5.1 ainda não está totalmente implementada. Em vez da anotação pública @functionBuilder, você deve usar a privada @_functionBuilder. No entanto, você pode encontrar os detalhes da proposta aqui.

Para entender melhor o propósito desta funcionalidade, este trecho destaca os casos de uso dos construtores de função:

Esta proposta não visa habilitar todos os tipos de DSL embutidos no Swift. Ela se concentra em um tipo específico de problema que pode ser significativamente melhorado com o uso de uma DSL: criar listas e árvores (geralmente heterogêneas) de dados a partir de padrões regulares. Isso é uma necessidade comum em várias áreas diferentes, incluindo geração de dados estruturados (por exemplo, XML ou JSON), hierarquias de visualização da interface do usuário (notavelmente incluindo o novo framework SwiftUI da Apple, que obviamente é um forte motivador para os autores) e casos semelhantes.

A proposta se concentra em uma DSL para representar árvores HTML aqui, mas também é amplamente usada no SwiftUI para criar hierarquias de visualização com o atributo @ViewBuilder aqui.

A documentação não é oficial, mas após algumas pesquisas, aqui estão os métodos que podemos implementar:

  • buildBlock(_ components: Component...) -> Component necessário
  • buildIf(_ component: Component?) -> Component opcional, anteriormente nomeado buildOptional.
  • buildEither(first: Component) -> Component e buildEither(second: Component) -> Component, opcionais

O buildExpression, buildDo e buildFunction não têm efeito no momento.

Na prática

O que queremos construir no nosso caso é uma lista de ações de alerta. Uma Ação é composta por um título, um estilo (default, destructive ou cancel) e uma função, acionada quando o usuário toca no botão de alerta na tela.

struct Action {
    let title: String
    let style: UIAlertAction.Style
    let action: () -> Void
}

Agora que temos um array de Ação, podemos criar um controlador de alerta com este método-fábrica:

func makeAlertController(title: String,
                         message: String,
                         style: UIAlertController.Style,
                         actions: [Action]) -> UIAlertController {
    let controller = UIAlertController(
        title: title,
        message: message,
        preferredStyle: style
    )
    for action in actions {
        let uiAction = UIAlertAction(title: action.Note: Se pensarmos em views e SwiftUI, isso faz sentido. De múltiplas subviews (os componentes para o @ViewBuilder), o buildBlock criará uma superview que contém todas as subviews.

No nosso caso, o tipo Component é um array de ações, então o método buildBlock pode ser escrito da seguinte forma:

@_functionBuilder
struct ActionBuilder {

    typealias Component = [Action]

    static func buildBlock(_ children: Component...) -> Component {
        return children.flatMap { $0 }
    }
}

Agora que temos nosso ActionBuilder, vamos usá-lo para recuperar um array de ações e passá-lo à função makeAlertController.

func Alert(title: String,
           message: String,@ActionBuilder 
let alertController = 
@_functionBuilder
struct ActionBuilder {

    
extension Action {
    static func default(_ title
let alertController = Alert(title: "Deleção", message: "Tem certeza?") {
    Action.destructive("Excluir") {
        // Realizar exclusão
    }
    Action.
@_functionBuilder
struct ActionBuilder {

    typealias Component = 
@_functionBuilder
struct ActionBuilder {

    typealias Component = [Action]

    static func buildBlock(_ children: Component...) -> Component { return children.flatMap { $0 } }
    static func buildIf(_ component: Component?) -> Component { return component ?? [] }
    static func buildEither(first component: Component) -> Component { return component }
    static func buildEither(second component: Component) -> Component { return component }
}

E isso faz nosso código compilar novamente com condições if / else.

Loops

No momento, não temos nenhuma maneira de iterar sobre um array de strings e criar ações com elas.

Se tentarmos, sempre encontramos o mesmo erro do compilador.

let alertController = Alert(title: "Title", message: "Message") { for string in ["Action1", "Action2"] { Action.default(string) { print(string) } } }

// erro: closure containing control flow statement cannot be used with function builder

Uma maneira de resolver este problema é criar uma função auxiliar que gera uma lista de ações para cada elemento de uma sequência e, em seguida, agrupa todas essas listas de ações em uma única.

func ForIn<S

Fonte original

Conteúdo traduzido e adaptado pela redação do Notícias Mobile. Confira também a matéria na fonte original.

Leia a matéria completa