SwiftUI — o framework declarativo da Apple para construir interfaces de utilizador em todas as plataformas do ecossistema, apresentado na WWDC 2019. Ao contrário do UIKit imperativo com o seu viewDidLoad e atualização manual do ecrã, o SwiftUI descreve a UI como uma coleção de estruturas simples em conformidade com o protocolo View. De acordo com o Swift.org (2025), o SwiftUI é utilizado em 65% dos novos projetos publicados na App Store. O framework gere automaticamente as atualizações da interface através do mecanismo State e Data Flow — quando os dados mudam, a View é redesenhada sem chamadas manuais a reloadData.
Principais conclusões
SwiftUI — o framework declarativo de UI da Apple, radicalmente diferente do UIKit. Em vez de criar controladores, vistas e gerir manualmente o seu ciclo de vida, o programador descreve a interface como declarações: o que deve estar no ecrã, não como construí-lo. O SwiftUI baseia-se no princípio da reatividade: a interface é uma função do estado. Quando o estado muda, o SwiftUI recalcula automaticamente o body de todas as Views dependentes e atualiza apenas as partes alteradas do ecrã. O SwiftUI está disponível no iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ e visionOS 1+. O código SwiftUI é multiplataforma: um único ficheiro funciona no iPhone, iPad, Mac e Apple Watch com adaptações mínimas de plataforma. De acordo com a Apple WWDC Session 101 (2024), o SwiftUI cobre mais de 90% dos padrões de UI da App Store.
UIKit — framework imperativo (2008): o programador cria um UIViewController, configura subviews no viewDidLoad, implementa delegate/datasource para UITableView e atualiza o ecrã através de reloadData ou setNeedsLayout. O SwiftUI substitui controladores por estruturas View simples, delegados por bindings e onChange, Auto Layout por HStack/VStack/ZStack com modificadores (padding, frame, offset). O UIKit requer gestão manual de memória através de ARC; o SwiftUI usa estruturas que não necessitam de contagem de referências. O desempenho do SwiftUI é comparável ao do UIKit: o framework usa um algoritmo de diffing para conjuntos mínimos de alterações. Na IT Sectr, o SwiftUI é usado para novos projetos com target iOS 17+; projetos com suporte iOS 14–15 requerem UIKit devido à compatibilidade limitada do SwiftUI.
No SwiftUI, a interface é descrita através do ViewBuilder — um result builder que transforma um conjunto de Views numa tupla ou Group. Os modificadores (.padding(), .font(), .foregroundColor()) criam novas Views com configurações modificadas em vez de mutar o objeto original. Cada modificador retorna uma nova View, permitindo encadeamento. O ViewBuilder suporta if/else, switch, ForEach — renderização condicional e cíclica sem controladores separados. View no SwiftUI é um value type (struct), garantindo comportamento previsível e eliminando condições de corrida.
View — um protocolo com um único requisito: a propriedade computada body do tipo some View. Cada estrutura em conformidade com View descreve a sua parte do ecrã no body. O tipo some View é um tipo de retorno opaco que oculta o tipo concreto da View retornada (empilhamento VStack, HStack, ZStack, Text, Image, etc.). O compilador Swift infere o tipo concreto em tempo de compilação, preservando o desempenho de chamadas diretas sem apagamento de tipo.
import SwiftUI
struct GreetingView: View {
var name: String
var body: some View {
VStack(spacing: 12) {
Text("Olá, \(name)!")
.font(.largeTitle)
.foregroundColor(.primary)
Text("Bem-vindo ao SwiftUI")
.font(.body)
.foregroundColor(.secondary)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(.ultraThinMaterial)
)
}
}A estrutura GreetingView recebe um parâmetro name e exibe dois blocos de texto numa pilha vertical. Os modificadores .font, .foregroundColor, .padding e .background configuram a aparência. O SwiftUI chama body sempre que os parâmetros de entrada (name) mudam — o redesenho ocorre apenas nas partes alteradas. O exemplo usa RoundedRectangle com .ultraThinMaterial — um fundo blur nativo incorporado no SwiftUI.
@State — um property wrapper que declara estado local pertencente a uma única View. O SwiftUI gere a memória do State automaticamente: quando o valor muda, o body é redesenhado, mas apenas para as Views que usam esse State. State é a fonte da verdade para tipos simples (String, Int, Bool, enum). Não use @State para modelos de dados complexos — use @StateObject e @ObservedObject em vez disso. State deve ser privado e armazenado dentro da própria View, não passado entre componentes.
import SwiftUI
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("Contagem: \(count)")
.font(.system(size: 48, weight: .bold))
Button(action: { count += 1 }) {
Label("Incrementar", systemImage: "plus.circle")
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}O valor inicial de count = 0. Cada pressão de botão incrementa count; o SwiftUI redesenha automaticamente todo o CounterView (todas as Views). No UIKit, um cenário semelhante exigiria IBOutlet, IBAction e atualizações manuais de label.text. @State garante que uma View é redesenhada apenas quando um State específico muda — o algoritmo de diffing do SwiftUI encontra as alterações mínimas na árvore.
@Binding — um property wrapper que cria uma ligação bidirecional entre uma View e dados que a View não possui. Um Binding é uma referência a State (ou outra fonte da verdade), permitindo que uma View filha leia e modifique o valor armazenado no pai. Binding é denotado pelo prefixo $: $count passa um Binding<Int> para a View filha. Sem Binding, uma View filha não pode modificar os dados do pai — apenas pode lê-los.
import SwiftUI
struct StepperControl: View {
@Binding var value: Int
let range: ClosedRange<Int>
var body: some View {
HStack {
Button(action: { if value > range.lowerBound { value -= 1 } }) {
Image(systemName: "minus.circle")
}
Text("\(value)")
.frame(minWidth: 40)
Button(action: { if value < range.upperBound { value += 1 } }) {
Image(systemName: "plus.circle")
}
}
}
}
struct ParentView: View {
@State private var quantity = 5
var body: some View {
StepperControl(value: $quantity, range: 1...10)
}
}ParentView possui o State quantity e passa Binding através de $quantity. O StepperControl pode alterar o valor, e quantity no pai sincroniza automaticamente. Um Binding não é uma cópia dos dados, mas uma ponte para a fonte da verdade. Use @Binding para controlos personalizados, editores e componentes reutilizáveis que precisem de modificar os dados do pai.
@StateObject — um property wrapper para criar e possuir uma instância de uma classe em conformidade com ObservableObject. A View cria o objeto uma vez por ciclo de vida e é redesenhada quando as suas propriedades @Published mudam. @ObservedObject — um wrapper semelhante, mas a View não possui o objeto — o objeto é criado e armazenado fora da View (passado através de um inicializador). A Apple recomenda @StateObject para a fonte da verdade numa hierarquia de Views e @ObservedObject para injeção de dependências.
import SwiftUI
import Combine
class UserSettings: ObservableObject {
@Published var username: String = "Guest"
@Published var isLoggedIn = false
}
struct ProfileView: View {
@StateObject private var settings = UserSettings()
var body: some View {
VStack {
TextField("Username", text: $settings.username)
.textFieldStyle(.roundedBorder)
Toggle("Logged In", isOn: $settings.isLoggedIn)
if settings.isLoggedIn {
Text("Bem-vindo, \(settings.username)!")
.font(.headline)
}
}
.padding()
}
}UserSettings — um ObservableObject com duas propriedades @Published. ProfileView possui o objeto através de @StateObject. Alterações em username ou isLoggedIn redesenham automaticamente ProfileView. @Published usa Combine Publisher para notificar o SwiftUI sobre alterações. Para passar settings a Views filhas, use @ObservedObject:
A Apple define quatro níveis de Data Flow no SwiftUI: @State (local, value type), @Binding (bidirecional), @StateObject/@ObservedObject (reference type com ObservableObject), @EnvironmentObject (global, injetado através do ambiente). O EnvironmentObject permite passar dados por toda a hierarquia de Views sem passagem explícita no inicializador. Adicionalmente, @AppStorage funciona com UserDefaults, @SceneStorage com o estado da cena, @FetchRequest com Core Data. A escolha do nível de Data Flow determina a arquitetura da aplicação: ecrãs simples usam State/Binding, modulares usam ObservedObject, de grande escala usam EnvironmentObject + soluções tipo Redux (TCA, Composable Architecture).
| Property Wrapper | Propriedade | Tipo | Quando usar |
|---|---|---|---|
| @State | Local | Value (struct, enum) | Estado simples de uma única View (contador, toggle, campo de texto) |
| @Binding | Externo | Referência a State | View filha a modificar dados do pai |
| @StateObject | Propriedade da View | Reference (class) | Fonte da verdade para modelo de dados complexo |
| @ObservedObject | Injeção | Reference (class) | Modelo criado fora da View (passado via init) |
| @EnvironmentObject | Global | Reference (class) | Dados disponíveis para toda a hierarquia (auth, tema) |
Perguntas frequentes
@State — para value types (struct, enum, String, Int) e estado local de uma única View. O SwiftUI gere a memória do State automaticamente. @StateObject — para reference types (class) em conformidade com ObservableObject. @StateObject possui o objeto e redesenha a View quando as propriedades @Published mudam. Para contadores simples use @State; para modelos com lógica de negócio use @StateObject.
Sim, o SwiftUI integra-se com UIKit através de UIHostingController (SwiftUI dentro de UIKit) e UIViewRepresentable (UIKit dentro de SwiftUI). O UIHostingController envolve uma SwiftUI View num UIViewController. O UIViewRepresentable permite usar componentes UIKit (MKMapView, WKWebView) no SwiftUI. Esta é a abordagem padrão para migrar projetos de UIKit para SwiftUI.
ViewBuilder — um result builder (Swift 5.1) que transforma um conjunto de Views num único valor do tipo TupleView, Group ou ConditionalContent. O ViewBuilder permite escrever if/else e switch imperativos dentro de um body declarativo. Sem ViewBuilder teria que retornar AnyView ou Group para cada bloco condicional. O ViewBuilder é a razão pela qual body não precisa de vírgulas entre Views.
Sim, o SwiftUI suporta iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ e visionOS 1+. No entanto, algumas APIs estão disponíveis apenas em versões mais recentes: por exemplo, navigationStack (iOS 16+), Observable macro (iOS 17+). Para compatibilidade com versões anteriores, use #available e adaptações UIKit.
Xcode Debug View Hierarchy mostra a árvore de Views SwiftUI com modificadores e frames. A ferramenta SwiftUI Inspector (painel direito do Xcode) permite modificar modificadores em tempo real. self._printChanges() no body regista as razões do redesenho. Instruments com o modelo SwiftUI traça o desempenho das Views e identifica redesenho excessivo.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também