New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: page custom widgets #602
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,52 +12,260 @@ description: >- | |
|
||
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. | ||
|
||
## Como criar componentes \(custom views\) e widgets? | ||
### Passo 1: Criar o componente nativo. | ||
|
||
### Passo 1: Criar o Widget | ||
Abaixo temos a definição da classe do componente Box. | ||
|
||
Segue abaixo um exemplo de um componente customizado que representa um UILabel: | ||
```swift | ||
import Foundation | ||
import UIKit | ||
|
||
class Box: UIView { | ||
|
||
// 1 Class parameter. | ||
private var title: String | ||
|
||
// 2 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") | ||
} | ||
|
||
// 3 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 | ||
} | ||
|
||
// 4 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. | ||
|
||
Agora temos o componente `Box`, para trasformar para um componente Beagle temos adotar o protocolo `Widget`, que é um protocolo que conforma com `Decodable` e é responsável por decodificar as propriedades que seu widget expõem ao backend. | ||
|
||
Crie uma struct BoxWidget adotando protocolo `Widget`, a interface widget irá adicionar a property de **widgetProperties** e o método **toView**. | ||
|
||
* **widgetProperties:** A propriedade de aplicar estilo, id e acessibilidade. | ||
|
||
* **toView:** Método para retornar a view do componente criado. | ||
|
||
Temos a estrutura da struct `BoxWidget` com os parâmetros `title` e `widgetProperties` e o componente `Box` criado no método `toView`. | ||
|
||
```swift | ||
struct MyCustomComponent: ServerDrivenComponent { | ||
let text: String | ||
import Foundation | ||
import UIKit | ||
import Beagle | ||
|
||
struct BoxWidget: Widget { | ||
|
||
// Class parameter. | ||
let title: String | ||
var widgetProperties: WidgetProperties | ||
|
||
// toView method of interface the widget. | ||
func toView(renderer: BeagleRenderer) -> UIView { | ||
let label = UILabel(frame: .zero) | ||
label.text = text | ||
label.numberOfLines = 0 | ||
return label | ||
let boxComponent = Box(title: title) | ||
|
||
} | ||
} | ||
|
||
``` | ||
|
||
Podemos ver que o `MyCustomComponent` é do tipo `ServerDrivenComponent`, que é um protocolo que conforma com `Decodable` e é responsável por decodificar as propriedades que seu widget expõem ao backend. | ||
Temos que criar a parte de inicialização e decodificação do componente, tem duas maneiras possíveis usando o `sourcery` gerador de código para a linguagem Swift, ou fazendo manualmente. | ||
|
||
**Sourcery:** | ||
|
||
### Passo 2: Registrar o Widget | ||
Para usar o `Sourcery` pode encontrar nesse [**link**]({{< ref path="/resources/customization/beagle-for-ios/sourcery" lang="pt" >}}) | ||
|
||
É obrigatório **registrá-lo no Beagle.** Dentro do arquivo de configuração utilize o método **`registerCustomComponent().`** O primeiro parâmetro é uma string que referencia como o seu BFF irá chama-lo e o segundo parâmetro é a classe criada do componente. | ||
**Manual:** | ||
|
||
Para fazer manual tem que criar o init e a decodificação dos parametros `title` e `widgetProperties` da struct `BoxWidget`. | ||
O widgetProperties tem sua propria parte decodificação, entao é preciso apenas passar o decoder para o objeto `WidgetProperties`. | ||
|
||
```swift | ||
Beagle.registerCustomComponent( | ||
"MyCustomComponent", | ||
componentType: MyCustomComponent.self | ||
) | ||
|
||
// Initialization part of the class. | ||
public init( | ||
title: String, | ||
widgetProperties: WidgetProperties = WidgetProperties() | ||
) { | ||
self.title = title | ||
self.widgetProperties = widgetProperties | ||
} | ||
|
||
// Enum with parameters for decoding. | ||
enum CodingKeys: String, CodingKey { | ||
case title | ||
} | ||
|
||
// Initialization for decoding | ||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
|
||
title = try container.decode(String.self, forKey: .title) | ||
widgetProperties = try WidgetProperties(from: decoder) | ||
} | ||
``` | ||
|
||
Para integrar o componente ao beagle é preciso utilizar o `sizeThatFits` ou `AutoLayoutWrapper`. | ||
|
||
{{< tabs id="T0" >}} | ||
{{% tab name="AutoLayoutWrapper" %}} | ||
|
||
|
||
### AutoLayoutWrapper | ||
|
||
**`AutoLayoutWrapper:`** O objeto calcula o tamanho levando em consideração as contraints do componente. | ||
Para isso primeiro é preciso desabilitar o `translatesAutoresizingMaskIntoConstraints` da view do componente, e depois adicionar a view do componente dentro do `AutoLayoutWrapper`. | ||
|
||
```swift | ||
yourComponent.translatesAutoresizingMaskIntoConstraints = false | ||
let beagleWrapper = AutoLayoutWrapper(view: yourComponent) | ||
``` | ||
|
||
{{% /tab %}} | ||
|
||
{{% tab name="SizeThatFits" %}} | ||
|
||
|
||
### SizeThatFits | ||
|
||
**`sizeThatFits:`** Método para implementar sua lógica de tamanho, usado na classe do componente customizado. | ||
|
||
```swift | ||
override func sizeThatFits(_ size: CGSize) -> CGSize { | ||
systemLayoutSizeFitting(size) | ||
} | ||
``` | ||
{{% /tab %}} | ||
{{< /tabs >}} | ||
|
||
Agora terminando as configurações, vamos usar o `AutoLayoutWrapper` do beagle para configurar o tamanho, pois o componente customizado não possue o `sizeThatFits` implementado. | ||
|
||
```swift | ||
boxComponent.translatesAutoresizingMaskIntoConstraints = false | ||
let beagleWrapper = AutoLayoutWrapper(view: boxComponent) | ||
``` | ||
|
||
A classe completa do Widget. | ||
|
||
```swift | ||
import Foundation | ||
import UIKit | ||
import Beagle | ||
|
||
struct BoxWidget: Widget { | ||
|
||
// Class parameter. | ||
let title: String | ||
var widgetProperties: WidgetProperties | ||
|
||
// Initialization part of the class. | ||
public init( | ||
title: String, | ||
widgetProperties: WidgetProperties = WidgetProperties() | ||
) { | ||
self.title = title | ||
self.widgetProperties = widgetProperties | ||
} | ||
|
||
// Enum with parameters for decoding. | ||
enum CodingKeys: String, CodingKey { | ||
case title | ||
} | ||
|
||
// Initialization for decoding | ||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
|
||
title = try container.decode(String.self, forKey: .title) | ||
widgetProperties = try WidgetProperties(from: decoder) | ||
} | ||
|
||
// toView method of interface the widget. | ||
func toView(renderer: BeagleRenderer) -> UIView { | ||
|
||
// Native component declaration. | ||
let boxComponent = Box(title: title) | ||
|
||
// Setting the beagle wrapper. | ||
boxComponent.translatesAutoresizingMaskIntoConstraints = false | ||
let beagleWrapper = AutoLayoutWrapper(view: boxComponent) | ||
Comment on lines
+214
to
+215
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sao duas propriedades requeridas entao para funcionar o auto layout? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depende da maneira que o usuário implementa, usando o AutoLayoutWrapper é dessa maneira, usando o sizeThatFits ele coloca o método no componente customizado dele. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mas se ele for usar o AutoLayoutWrapper não seria mais facil ja adicionar essa linha para ele entao? |
||
|
||
// Returning BeagleWrapper and component. | ||
return beagleWrapper | ||
} | ||
} | ||
``` | ||
|
||
|
||
### Passo 3: Registrar o Widget. | ||
|
||
É obrigatório **registrá-lo no Beagle.** Dentro do arquivo de configuração do beagle utilize o `dependencies` para registar. | ||
|
||
{{% alert color="info" %}} | ||
Para saber mais sobre o dependencies. [**Beagle Dependencies**]({{< ref path="/resources/customization/beagle-for-ios/beagles-dependencies" lang="pt" >}}). | ||
{{% /alert %}} | ||
|
||
O método `register` possui dois construtores, o primeiro passando apenas o `component` e segundo recebendo o `component` e `named`. | ||
|
||
* **component:** Passa a classe 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ções para encontrar seu componente. | ||
|
||
**Maneiras de Registrar** | ||
```swift | ||
// 1 | ||
dependencies.decoder.register(component: BoxWidget.self) | ||
luisgustavozup marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// 2 | ||
dependencies.decoder.register(component: BoxWidget.self, named: "BoxWidgetComponent") | ||
``` | ||
|
||
Após registrar o seu componente de customização, você pode usá-lo via server-driven. | ||
|
||
### Passo 3: Exibir o Componente | ||
### Passo 4: Exibir o Componente. | ||
|
||
Você pode usar o seu componente declarativamente assim como passá-lo por uma instância até o `BeagleScreenViewController` ou chamá-lo via método `toView()` para apresentar o`UIView` que aparece dentro do seu próprio view controller. | ||
|
||
```swift | ||
let beagleScreenViewController = Beagle.screen( | ||
luisgustavozup marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.declarative( | ||
.init(child: | ||
MyCustomComponent(text: "Hello Beagle!") | ||
BoxWidget(title: "Title my box!") | ||
) | ||
) | ||
) | ||
``` | ||
|
||
Se você usar componentes mais complexos que estejam no `UIViews` ou outros componentes não mencionados, o processo seria parecido, você apenas precisa providenciar um tipo de `ServerDrivenComponent` ou `Widget`. | ||
{{% alert color="info" %}} | ||
Para saber mais como exibir o Componente. [**Como exibir uma tela**]({{< ref path="/get-started/using-beagle/ios" lang="pt" >}}). | ||
{{% /alert %}} | ||
|
||
Exemplo renderizado: | ||
|
||
![](/shared/custom-component-box-ios.png) | ||
|
||
Se você usar componentes mais complexos que estejam no `UIViews` ou outros componentes não mencionados, o processo seria parecido. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Na mesma sessão do android da criação de custom widgets, ainda existe esse titulo com a pergunta