Widget Customizado

Aqui voce aprenderá como criar e utilizar um widget no Beagle

Requisitos:

  • Ter um projeto já configurado com o Beagle.

Introdução

O Beagle já possui alguns widgets básicos que podem ser usados para alterar a sua aplicação UI através do backend. No entanto, você pode adicionar novos componentes para fazer as views da sua aplicação fiquem “visíveis” ao Beagle e que possam também ser usadas no backend.

Passo 1: Criar o componente customizado.

Abaixo temos a definição da classe do componente Box. Criada com view code em swift e possui um parâmetro title, e um evento de tap.

import UIKit

class Box: UIView {
    
    // Class parameter.
    private var title: String
    
    // Initialization part of the class.
    public init(title: String) {
        self.title = title
        super.init(frame: .zero)
        setupView()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // Method to add component to hierarchy and pass position.
    private func setupView() {
        addSubview(label)
        
        label.text = title
        label.topAnchor.constraint(equalTo: topAnchor).isActive = true
        label.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
    }
    
    // Component `UILabel` created.
    private lazy var label: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 20, weight: .bold)
        label.backgroundColor = .red
        label.textAlignment = .center
        label.textColor = .white
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
}

Passo 2: Criar o Widget.

Para fazer seu componente nativo funcionar com o Beagle, basta criar uma struct e implementar o protocolo Widget. Isso indica que a sua struct irá conformar com Codable, sendo assim responsável por decodificar e codificar as propriedades que o widget expõem ao backend. Além disso, para ser um Widget também é necessário adicionar as propriedades id: String?, style: Style?, accessibility: Accessibility? e implementar o método toView.

Agora com o componente Box crie uma struct BoxWidget que implementa o protocolo Widget.

  • id: Identificador do componente

  • style: Atributos de estilização do componente.

  • accessibility: Atributos de acessibilidade do componente.

  • toView: Método que retorna a view do componente criado.

Temos a estrutura da struct BoxWidget com os parâmetros title, id, style, accessibility, no método toView o componente Box estanciado passando o parâmetro title.

import Foundation
import UIKit
import Beagle

struct BoxWidget: Widget {

    let title: String
    
    public var id: String?
    public var style: Style?
    public var accessibility: Accessibility?
    
    func toView(renderer: BeagleRenderer) -> UIView {
        let boxComponent = Box(title: title)

        return boxComponent
    }
}

Para integrar o componente ao beagle é preciso utilizar o sizeThatFits ou AutoLayoutWrapper.

AutoLayoutWrapper

AutoLayoutWrapper: O objeto calcula o tamanho levando em consideração as contraints do componente.

SizeThatFits

sizeThatFits: Método para implementar sua lógica de tamanho, usado na classe do componente customizado.

override func sizeThatFits(_ size: CGSize) -> CGSize {
    systemLayoutSizeFitting(size)
}

Passo 3: Registrar o Widget.

Por fim precisamos registrar nosso widget customizado no Beagle.

Logo, para registrá-lo no Beagle. basta chamar a função de registro do Coder (Dependência publica do Beagle) durante o processo de configuração do ambiente do Beagle.

O método register pode ser chamado passando somente o tipo do componente, ou também um nome customizado para identifica-lo.

  • type: Tipo do componente.

  • named: Parâmetro para setar o nome do componente. Não é obrigatório passar. Um caso é quando o nome do componente é registrado diferente com que você criou no backend. Ele será usado na deserialização para encontrar seu componente.

Maneiras de Registrar

// 1º maneira.
coder.register(type: BoxWidget.self)
// 2º maneira.
coder.register(type: BoxWidget.self, named: "BoxWidgetComponent")

Exemplo renderizado:

Casos especiais

Componentes que expõe eventos

Em casos em que o componente customizado contém eventos de interação com o usuario basta expor esses eventos no Widget do componente. Então suponhamos que o BoxComponent agora tem que lidar com um evento de touch:

import Foundation
import UIKit
import Beagle

struct BoxWidget: Widget {

    let title: String
    @AutoCodable
    let onTouch: [Action]?
    
    public var id: String?
    public var style: Style?
    public var accessibility: Accessibility?
    
    func toView(renderer: BeagleRenderer) -> UIView {
        let boxComponent = Box(title: title)
        boxComponent.onTouch = {
            renderer.controller?.execute(actions: onTouch, event: "onTouch", origin: boxComponent)
        }

        return boxComponent
    }
}

Passo 1: adicionamos o atributo let onTouch: [Action]? que consiste em uma lista de ações que serão executadas a partir do evento de touch.

Passo 2: supondo que o componente nativo ultilize closures para tratar eventos, então atribuimos uma closure para o evento onTouch que chama o método execute passando a lista de ações: renderer.controller?.execute(actions: onTouch, event: "onTouch", origin: boxComponent)

Componentes que encapsulam outros componentes

Em casos em que nosso componente customizado recebe outro componente para adiciona-lo como subview, basta adicionar um atributo no nosso widget que corresponde a esse componente filho.

Então supondo que nosso componente Box agora receba uma UIView que será adicionada em sua hierarquia de views:

struct BoxWidget: Widget {

    // Class parameter.
    let title: String
    @AutoCodable
    let child: ServerDrivenComponent
    
    public var id: String?
    public var style: Style?
    public var accessibility: Accessibility?
    
    // toView method of interface the widget.
    func toView(renderer: BeagleRenderer) -> UIView {
        let child: UIView = BeagleView(child)
        child.translatesAutoresizingMaskIntoConstraints = false

        let boxComponent = Box(title: title, child: child)

        return boxComponent
    }
}

Passo 1: adicionamos o atributo let child: ServerDrivenComponent que será o componente Server Driven.

Passo 2: transformamos o componente server driven em uma UIView, a partir da classe BeagleView: let child: UIView = BeagleView(child)

Passo 3: agora que temos a UIView passamos ela no inicializador do componente Box: Box(title: title, child: child)