SwiftUI to deklaratywny framework Apple do budowania interfejsów użytkownika na wszystkich platformach ekosystemu. Zamiast imperatywnego opisywania kroków, programista deklaruje, jak powinien wyglądać interfejs, a SwiftUI zarządza jego renderowaniem i aktualizacją. Według Apple Developer Documentation (2025), SwiftUI obsługuje iOS 15+, iPadOS 15+, macOS 12+, watchOS 8+ i tvOS 15+ i wykorzystuje View Protocol jako podstawowy budulec wszystkich komponentów interfejsu.
Najważniejsze
body — podstawa każdego komponentu UI w SwiftUI, zwracająca opis ekranu poprzez kompozycję widoków.SwiftUI to deklaratywny framework, wprowadzony przez Apple w 2019 roku w celu zastąpienia UIKit w nowych projektach. Zamiast ręcznego tworzenia instancji UIView i dodawania ich do hierarchii, programista opisuje interfejs za pomocą struktur implementujących protokół View. SwiftUI automatycznie oblicza różnicę między bieżącym a nowym stanem i przerysowuje tylko zmienione części, używając własnego silnika renderującego.
Framework został napisany w Swift z wykorzystaniem value semantics (struktury, a nie klasy), co sprawia, że komponenty UI są lekkie i bezpieczne wątkowo. W przeciwieństwie do UIKit, gdzie UIViewController może ważyć 200+ bajtów z powodu Objective-C runtime, widok SwiftUI to po prostu struktura o rozmiarze kilku bajtów. Jest to szczególnie ważne dla watchOS z jej ograniczoną pamięcią.
Ten sam opis View działa na iPhone, iPad, Mac, Apple Watch, Apple TV i Apple Vision Pro. SwiftUI dostosowuje interfejs do platformy: na iOS — gesty dotykowe, na macOS — skróty klawiaturowe, na watchOS — przewijanie Digital Crown. Skraca to czas tworzenia aplikacji dla firm wydających oprogramowanie na wiele platform Apple, ale wymaga dodatkowej konfiguracji dla specyficznych elementów każdej platformy.
W SwiftUI każdy ekran to struktura implementująca protokół View z jedynym wymaganiem: computed property body typu some View. Słowo kluczowe some (opaque type) ukrywa konkretny typ widoku, pozwalając SwiftUI optymalizować renderowanie. Wewnątrz body programista łączy gotowe komponenty — Text, Image, Button, List — za pomocą ViewBuilder, który zbiera kilka widoków w jeden.
struct GreetingView: View {
let name: String
var var body: some View {
VStack {
Text("Cześć, \(name)!")
.font(.title)
.foregroundColor(.blue)
Image(systemName: "hand.wave")
.imageScale(.large)
}
.padding()
}
}
W przykładzie VStack (pionowy stos) zawiera Text i Image. Wartość name jest przekazywana przez inicjalizator struktury — tak działa DI (Dependency Injection) w SwiftUI bez zewnętrznych kontenerów DI. Każdy modyfikator zwraca nowy widok z zastosowaną zmianą, bez mutowania oryginału. Jest to możliwe dzięki niezmienności (immutability) typów wartościowych.
ViewBuilder to result builder, adnotowany @resultBuilder, który zbiera do 10 widoków w jeden. Wewnątrz body można używać if/else, switch i ForEach bez dodatkowych opakowań. ForEach działa z elementami Identifiable — każdy widok otrzymuje unikalny id do poprawnej animacji przy wstawianiu/usuwaniu.
W SwiftUI stan określa, jaka treść jest wyświetlana na ekranie. Gdy stan się zmienia, SwiftUI odtwarza body zależnego widoku i porównuje wynik z poprzednim, stosując algorytm diff. Do przechowywania stanu używa się property wrappers — każdy z nich rozwiązuje swoje zadanie: stan lokalny, połączenie z widokiem potomnym lub zewnętrzny model danych.
struct CounterView: View {
@State private var count = 0
var var body: some View {
VStack {
Text("Licznik: \(count)")
Button("Zwiększ") {
count += 1
}
}
}
}
class UserViewModel: ObservableObject {
@Published var name = ""
@Published var age = 0
}
@State przechowuje lokalną prostą wartość (Int, String, Bool) wewnątrz struktury View. SwiftUI przenosi pamięć ze struktury do osobnego magazynu — dlatego właściwość z @State można zmieniać (mutować), nawet jeśli View jest typem wartościowym. @ObservableObject — dla klas z właściwościami @Published, których zmiany automatycznie powiadamiają SwiftUI o konieczności przerysowania.
@Binding tworzy dwukierunkowe połączenie ze źródłem danych znajdującym się w widoku nadrzędnym. Rodzic przekazuje $variable (projected value), dziecko odczytuje i zapisuje wartość przez binding. Pozwala to na wydzielenie wprowadzania tekstu lub przełącznika do osobnego komponentu, zachowując stan w rodzicu. Bez @Binding każda zmiana wymagałaby zamknięcia callback do przekazania nowej wartości w górę.
Przed iOS 16 nawigacja w SwiftUI opierała się na NavigationView — przestarzałym API ze złożonym zachowaniem na iPadzie (split view, double column). Od iOS 16 Apple zaleca NavigationStack — uproszczoną alternatywę z bezpiecznymi typologicznie trasami. Programista definiuje enum możliwych tras, a NavigationStack automatycznie zarządza stosem ekranów z obsługą głębokich linków i powrotem do korzenia.
enum Route: Hashable {
case detail(id: Int)
case settings
}
struct ContentView: View {
var var body: some View {
NavigationStack {
List {
NavigationLink("Ekran szczegółów",
value: Route.detail(id: 42))
NavigationLink("Ustawienia",
value: Route.settings)
}
.navigationDestination(for: Route.self) { route in
switch route {
case .detail(let id): DetailView(id: id)
case .settings: SettingsView()
}
}
}
}
}
Trasy typu Route: Hashable pozwalają na używanie dowolnego typu danych do przekazywania parametrów. navigationDestination(for:destination:) łączy typ trasy z docelowym widokiem. Przewagą nad nawigacją UIKit jest brak konieczności przerysowania przy dodawaniu nowej trasy: wystarczy dodać case w enumie i obsługę w switch. Głębokie linki są obsługiwane przez processDeepLink na NavigationStack.
Do programowego przejścia (po zalogowaniu, timerze lub odpowiedzi serwera) używa się @State z inicjalizatorem NavigationLink: NavigationLink(isActive: $isActive). Po ustawieniu isActive = true przejście następuje bez dotknięcia użytkownika. Alternatywą jest binding tablicy $path w NavigationStack: $path.append(Route.detail(id: 1)).
Modifier to metoda zwracająca zmodyfikowaną kopię widoku. W przeciwieństwie do UIKit, gdzie konfiguracja właściwości odbywa się przez mutację istniejącego widoku, SwiftUI tworzy nową wartość z zastosowaną zmianą. Łańcuch modyfikatorów (chaining) buduje końcowy interfejs z sekwencyjnych przekształceń: czcionka → odstęp → kolor → cień → gest.
Apple udostępnia ponad 200 wbudowanych modyfikatorów. Najczęstsze to: .font(), .foregroundColor(), .padding(), .background(), .cornerRadius(), .shadow(), .opacity(), .offset(). Kolejność modyfikatorów ma znaczenie: .padding() przed .background() koloruje obszar z odstępem, po — tylko wewnętrzny obszar. Niestandardowe modyfikatory tworzy się przez protokół ViewModifier.
Modyfikatory można stosować warunkowo przez operator trójargumentowy: .foregroundColor(isError ? .red : .primary). Do animacji używa się .animation(.easeInOut, value: state) — modyfikator animacji wiąże się z konkretną właściwością stanu. Przy zmianie tej właściwości SwiftUI animuje przejście między starą a nową wartością. Animacja działa z opacity, offset, scale, rotation, rozmiarem i kolorem — dla każdej właściwości zdefiniowany jest odpowiedni AnimatableParameter.
Do niestandardowych animacji dostępne są .transition (pojawianie/znikanie) i .matchedGeometryEffect (płynne przejście elementu między dwoma kontenerami). Ten ostatni jest używany do hero-animacji na listach: ikona w komórce listy płynnie przekształca się w duży obraz na ekranie szczegółów.
Wybór między SwiftUI a UIKit to jeden z pierwszych dylematów programisty iOS. Oba frameworki są obsługiwane przez Apple, ale rozwiązują zadanie budowania interfejsu zasadniczo różnymi sposobami: SwiftUI deklaratywnie, UIKit imperatywnie. Różnica przejawia się w zarządzaniu stanem, nawigacji, wydajności i kompatybilności.
| Aspekt | SwiftUI | UIKit |
|---|---|---|
| Podejście | Deklaratywne: co pokazać | Imperatywne: jak zbudować |
| Stan | Property Wrappers, automatyczne przerysowanie | Ręcznie: reloadData, setNeedsLayout |
| Kod UI | Zręczny, łańcuchy modyfikatorów | Obszerny, NSCoder/Storyboard/constraints |
| Wydajność | Wysoka na iOS 17+, algorytm diff | Szczytowa na iOS 12–16, bezpośrednia kontrola |
| Minimalna wersja | iOS 15+ (pełne wsparcie) | iOS 2+ (wszystkie wersje) |
Dla nowych projektów z minimalną wersją iOS 17 Apple zaleca SwiftUI jako główny framework. UIKit pozostaje niezbędny do interfejsów wymagających precyzyjnej kontroli nad renderowaniem (niestandardowe UICollectionViewLayout, złożone sceny CAAnimation) lub obsługi iOS 12–14. Wiele projektów stosuje podejście hybrydowe: SwiftUI przez UIHostingController jest wbudowywany w aplikację UIKit, a UIViewRepresentable pozwala na używanie komponentów UIKit wewnątrz hierarchii SwiftUI.
Często zadawane pytania
Tak, przez UIHostingController (SwiftUI w UIKit) i UIViewRepresentable (UIKit w SwiftUI). To hybrydowe podejście, popularne przy migracji.
iOS 17 — pełna funkcjonalność: NavigationStack, Observation framework, Swift Charts. iOS 15 — minimalny próg dla produkcji.
Najczęstszą przyczyną jest zmiana właściwości @Published na wątku tła. ObservableObject powinien wysyłać zmiany na main actor: @MainActor class ViewModel.
Użyj .debounce przez Combine: Button.publisher(for: .tap) .debounce(for: .seconds(0.3), scheduler: RunLoop.main).
Tak, przez modyfikatory Gesture: DragGesture, LongPressGesture, MagnificationGesture, RotationGesture. Łącz je przez .simultaneousGesture() i .sequenced().
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również