Modularização no iOS: Estruturando Projetos com Swift Package Manager

lys

Lys

Posted on August 12, 2024

Modularização no iOS: Estruturando Projetos com Swift Package Manager

Ajudando em reutilização, escalabilidade e gerenciamento de dependências, é comum que projetos mobile sejam modularizados.
Pense em um app financeiro, como exemplo esse abaixo achado numa plataforma chamada dribbble

Screenshot de um projeto de aplicativo financeiro

Pensando em times trabalhando em diferentes partes do app, modularizar ajudaria cada equipe a cuidar de um módulo específico. Aqui poderíamos ter um módulo para "transfer", "invest" e "budget", e eles seriam instanciados em um projeto principal. Também pensando em reutilização de código, é comum que haja um módulo para a parte de design system e componentes que serão utilizados. Ajudam na testabilidade, padronização do projeto e também evitará a repetição de código, já que cada módulo do projeto poderá ter o módulo de componentes.

Diagrama módulos

Uma possível estrutura:

  • BankApp seria nosso projeto principal, adicionaremos dependências nele.
  • Invest, Transfer e Budget serão dependências de BankApp.
  • Components será uma dependência de Invest, Transfer e Budget.
  • Firebase* e Alamofire* também serão dependências de BankApp.

*Como são libs mais conhecidas, quis exemplificar em que "nível" ela pode estar quando for usada no app principal.

E também não é uma regra, já que você pode criar módulos que tenham o Alamofire, por exemplo, e, nesse caso, o app principal não precisaria conhecê-lo.

O Cocoapods e o Carthage são outras formas de trabalhar com gerenciamento de dependencias e distribuição de bibliotecas em um projeto iOS, mas pra esse projeto, usaremos o Swift Package Manager

Uma curiosidade é que ele foi anunciado pela primeira vez em 2015, é open source e está disponível no repositório do Swift no GitHub. Trabalhar com ele é bom por essa experiência mais nativa e integrada ao xcode.

Criando um projeto

Abra seu xcode

File > New > Project

Opções de novo projeto no Xcode

Configurando um novo projeto no Xcode

Screenshot criando projeto com nome finance-app

Repita esse processo, agora criando um projeto para o que será nosso módulo, o nome do projeto pode ser Components

Quando criado, acessei a pasta onde estava meu projeto

cd Components
Enter fullscreen mode Exit fullscreen mode

Primeiros comandos para instanciar o spm no seu diretório

Pra inicializar um Package.swift e /Sources no diretório atual.

swift package init --type executable
Enter fullscreen mode Exit fullscreen mode

Pra buscar e resolver todas as dependências especificadas no arquivo Package.swift do projeto.

swift package resolve
Enter fullscreen mode Exit fullscreen mode

Pra compilar o código-fonte do pacote e todas as suas dependências.

swift build
Enter fullscreen mode Exit fullscreen mode

Entendendo o package.swift

O nome do pacote, que deve ser único dentro do contexto de onde ele será usado.

import PackageDescription

let package = Package(
    name: "components-views",
Enter fullscreen mode Exit fullscreen mode

Especifica as plataformas e versões mínimas suportadas pelo pacote.

platforms: [
        .macOS(.v10_15),
        .iOS(.v13),
        .tvOS(.v13),
        .watchOS(.v6)
    ],
Enter fullscreen mode Exit fullscreen mode

Produtos são as bibliotecas ou executáveis que o pacote produz e pode compartilhar com outros pacotes. Nesse caso, quando nosso projeto utilizar esse pacote, poderá importar Components pra acessar o que há no target Components, isso é útil porque podemos separar os targets que serão exibidos, e o projeto que utilizar esse módulo, poderá escolher qual library ele irá importar.

products: [
        .library(
            name: "Components",
            targets: ["Components"]),
    ],
Enter fullscreen mode Exit fullscreen mode

Permite especificar dependências externas, como outros pacotes Swift disponíveis em repositórios Git.

dependencies: [
        .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.10.0")
    ],
Enter fullscreen mode Exit fullscreen mode

Targets são as unidades de construção do pacote. Podem ser módulos, bibliotecas ou executáveis.

    targets: [
        .target(
            name: "Components",
            dependencies: []),
        .testTarget(
            name: "ComponentsTests",
            dependencies: ["Components"]),
        .testTarget(
            name: "ComponentsUITests",
            dependencies: ["Components"]),
    ]
)

Enter fullscreen mode Exit fullscreen mode

Para conseguirmos visualizar pelo menos um ínicio de tela, crie em Components uma view chamada TransactView

import SwiftUI

public struct TransactView: View {

    public init() { }

    public var body: some View {
        VStack {
            Text("Transact")
                .font(.headline)

            HStack(spacing: 16) {
                createImageView(with: "Buy", imageName: "envelope")
                createImageView(with: "Sell", imageName: "scissors")
                createImageView(with: "Deposit", imageName: "arrow.down")
                createImageView(with: "Withdraw", imageName: "arrow.up")
                createImageView(with: "More", imageName: "plus")
            }
            .padding()
        }
        .padding()
    }

    func createImageView(with text: String, imageName: String) -> some View {
        VStack {
            Image(systemName: imageName)
                .padding()
                .background(Circle().fill(Color.gray.opacity(0.2)))
            Text(text)
                .font(.caption)
                .multilineTextAlignment(.center)
        }
        .frame(width: 60)
    }
}

Enter fullscreen mode Exit fullscreen mode

Agora, crie um repositório público no github e publique seu diretório criado, vamos deixar ele visível para adicionarmos no app principal.

Depois de criado, abra o projeto do app principal e vá até a raiz do projeto.

Menu com as pastas que o projeto finance-app tem

Clique em package dependencies, e depois, no botão + mais abaixo.

Screenshot da tela quando clicado em package dependencies

Coloque o link do seu repositório e veja que você não precisa utilizar ele só da main, há outras opções também.

Adicionando dependência por branch no Xcode

Clique em Add Package, e quando for adicionado com sucesso, a estrutura do Components deve se parecer com essa imagem.

Estrutura do package dentro do finance-app

Agora, ja conseguimos importar Components

Screenshot da tela no xcode com o arquivo FinanceApp aberto

Deixaremos nossa ContentView assim

import SwiftUI
import Components

struct ContentView: View {
    var body: some View {
        TransactView()
    }
}

#Preview {
    ContentView()
}
Enter fullscreen mode Exit fullscreen mode

E ao clicar em run, quando o simulador for aberto, a tela deve se parecer com esse componente

Simulação de Iphone 12 mostrando o componente de transações, uma lista de imagens com os nomes

Adicionando módulos localmente

Sem a necessidade de criar repositórios, no xcode há a possibilidade de adicionar um package localmente.

No projeto, vá até

File > Add Package Dependencies

Clique em Add Local e depois de escolher o diretório do seu módulo, a sua tela deve parecer isso.

Screenshot da tela de selecionar target que usará o módulo adicionado

Certifique-se de não estar com o projeto do seu módulo aberto numa outra aba do xcode, isso pode interferir no build e seu módulo não será carregado.

💖 💪 🙅 🚩
lys
Lys

Posted on August 12, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related