SwiftUI — deklaratywny framework Apple do budowania interfejsów użytkownika na wszystkich platformach ekosystemu, zaprezentowany na WWDC 2019. W przeciwieństwie do imperatywnego UIKit z jego viewDidLoad i ręcznym odświeżaniem ekranu, SwiftUI opisuje UI jako kolekcję prostych struktur zgodnych z protokołem View. Według Swift.org (2025), SwiftUI jest używany w 65% nowych projektów opublikowanych w App Store. Framework automatycznie zarządza aktualizacją interfejsu poprzez mechanizm State i Data Flow — przy zmianie danych View jest przerysowywane bez ręcznego wywoływania reloadData.
Najważniejsze
SwiftUI — deklaratywny framework UI Apple, radykalnie różniący się od UIKit. Zamiast tworzenia kontrolerów, widoków i ręcznego zarządzania ich cyklem życia, programista opisuje interfejs w postaci deklaracji: co powinno być na ekranie, a nie jak to zbudować. SwiftUI opiera się na zasadzie reaktywności: interfejs jest funkcją stanu. Przy zmianie stanu (State) SwiftUI automatycznie przelicza body wszystkich zależnych View i aktualizuje tylko zmienione części ekranu. SwiftUI jest dostępny na iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ i visionOS 1+. Kod SwiftUI jest wieloplatformowy: jeden plik działa na iPhone, iPad, Mac i Apple Watch z minimalnymi adaptacjami platformowymi. Według Apple WWDC Session 101 (2024), SwiftUI pokrywa ponad 90% standardowych wzorców UI w App Store.
UIKit — imperatywny framework (2008): programista tworzy UIViewController, konfiguruje subviews w viewDidLoad, implementuje delegate/datasource dla UITableView i odświeża ekran przez reloadData lub setNeedsLayout. SwiftUI zastępuje kontrolery prostymi strukturami View, delegaty — binding i onChange, Auto Layout — HStack/VStack/ZStack z modyfikatorami (padding, frame, offset). UIKit wymaga ręcznego zarządzania pamięcią przez ARC; SwiftUI — struktury niewymagające zliczania referencji. Wydajność SwiftUI jest porównywalna z UIKit: framework używa algorytmu diffing do minimalnego zestawu zmian. W IT Sectr SwiftUI jest używany do nowych projektów z targetem iOS 17+; projekty z obsługą iOS 14–15 wymagają UIKit ze względu na ograniczoną kompatybilność SwiftUI.
W SwiftUI interfejs opisuje się przez ViewBuilder — result builder, transformujący zestaw View w krotkę lub Group. Modyfikatory (.padding(), .font(), .foregroundColor()) tworzą nowe View ze zmienionymi ustawieniami, a nie mutują oryginalnego obiektu. Każdy modyfikator zwraca nowe View, co umożliwia chaining. ViewBuilder obsługuje if/else, switch, ForEach — warunkowe i cykliczne renderowanie bez osobnych kontrolerów. View w SwiftUI to value type (struct), co gwarantuje przewidywalne zachowanie i eliminuje race conditions.
View — protokół z jednym wymaganiem: computed property body typu some View. Każda struktura zgodna z View opisuje swoją część ekranu w body. Typ some View — opaque return type, ukrywający konkretny typ zwracanego View (sparowanie VStack, HStack, ZStack, Text, Image itd.). Kompilator Swift wyprowadza konkretny typ na etapie kompilacji, zachowując wydajność bezpośredniego wywołania bez zacierania typu.
import SwiftUI
struct GreetingView: View {
var name: String
var body: some View {
VStack(spacing: 12) {
Text("Witaj, \(name)!")
.font(.largeTitle)
.foregroundColor(.primary)
Text("Witamy w SwiftUI")
.font(.body)
.foregroundColor(.secondary)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(.ultraThinMaterial)
)
}
}Struktura GreetingView przyjmuje parametr name i wyświetla dwa bloki tekstowe w pionowym stosie. Modyfikatory .font, .foregroundColor, .padding i .background konfigurują wygląd. SwiftUI wywołuje body za każdym razem przy zmianie parametrów wejściowych (name) — przerysowanie następuje tylko dla zmienionych części. W przykładzie użyto RoundedRectangle z .ultraThinMaterial — natywne tło blur wbudowane w SwiftUI.
@State — property wrapper, deklarujący lokalny stan należący do jednego View. SwiftUI automatycznie zarządza pamięcią State: przy zmianie wartości body jest przerysowywane, ale tylko dla View używających tego State. State — źródło prawdy (source of truth) dla prostych typów (String, Int, Bool, enum). Nie używaj @State dla złożonych modeli danych — dla nich przeznaczone są @StateObject i @ObservedObject. State powinien być private i przechowywany w samym View, a nie przekazywany między komponentami.
import SwiftUI
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("Licznik: \(count)")
.font(.system(size: 48, weight: .bold))
Button(action: { count += 1 }) {
Label("Zwiększ", systemImage: "plus.circle")
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}Wartość początkowa count = 0. Każde naciśnięcie przycisku inkrementuje count; SwiftUI automatycznie przerysowuje CounterView w całości (wszystkie View). W UIKit analogiczny scenariusz wymagałby IBOutlet, IBAction i ręcznej aktualizacji label.text. @State gwarantuje, że View jest przerysowywane tylko przy zmianie konkretnego State — algorytm diffing SwiftUI znajduje minimalne zmiany w drzewie.
@Binding — property wrapper, tworzący dwukierunkowe połączenie między View a danymi, którymi View nie zarządza. Binding to referencja do State (lub innego source of truth), umożliwiająca dziecka View odczyt i zmianę wartości przechowywanej w rodzicu. Binding oznacza się prefiksem $: $count przekazuje Binding<Int> do dziecka View. Bez Binding dziecko View nie może zmienić danych rodzica — może je tylko odczytać.
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 posiada State quantity i przekazuje Binding przez $quantity. StepperControl może zmieniać value, a quantity w rodzicu synchronizuje się automatycznie. Binding to nie kopia danych, a most do źródła prawdy. Używaj @Binding do niestandardowych kontrolek, edytorów i wielokrotnie używanych komponentów, które powinny zmieniać dane rodzica.
@StateObject — property wrapper do tworzenia i posiadania instancji klasy zgodnej z ObservableObject. View tworzy obiekt raz na cykl życia i przerysowuje się przy zmianie jego @Published właściwości. @ObservedObject — podobny wrapper, ale View nie jest właścicielem obiektu — obiekt jest tworzony i przechowywany poza View (przekazywany przez inicjalizator). Apple zaleca @StateObject dla source of truth w hierarchii View i @ObservedObject do wstrzykiwania zależności.
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("Witaj, \(settings.username)!")
.font(.headline)
}
}
.padding()
}
}UserSettings — ObservableObject z dwiema @Published właściwościami. ProfileView jest właścicielem obiektu przez @StateObject. Zmiana username lub isLoggedIn automatycznie przerysowuje ProfileView. @Published używa Combine Publisher do powiadamiania SwiftUI o zmianach. Do przekazywania settings do dziecka View używaj @ObservedObject:
Apple definiuje cztery poziomy Data Flow w SwiftUI: @State (lokalny, value type), @Binding (dwukierunkowy), @StateObject/@ObservedObject (reference type z ObservableObject), @EnvironmentObject (globalny, wstrzykiwany przez środowisko). EnvironmentObject umożliwia przesyłanie danych przez całą hierarchię View bez jawnego przekazywania w inicjalizatorze. Dodatkowo @AppStorage działa z UserDefaults, @SceneStorage — ze stanem sceny, @FetchRequest — z Core Data. Wybór poziomu Data Flow determinuje architekturę aplikacji: proste ekrany używają State/Binding, modułowe — ObservedObject, rozbudowane — EnvironmentObject + rozwiązania podobne do Redux (TCA, Composable Architecture).
| Property Wrapper | Własność | Typ | Kiedy używać |
|---|---|---|---|
| @State | Lokalna | Value (struct, enum) | Prosty stan jednego View (licznik, toggle, pole tekstowe) |
| @Binding | Zewnętrzna | Referencja do State | Dziecko View zmieniające dane rodzica |
| @StateObject | Własność View | Reference (class) | Źródło prawdy dla złożonego modelu danych |
| @ObservedObject | Wstrzyknięcie | Reference (class) | Model utworzony poza View (przekazany przez init) |
| @EnvironmentObject | Globalna | Reference (class) | Dane dostępne dla całej hierarchii (autoryzacja, motyw) |
Często zadawane pytania
@State — dla value types (struct, enum, String, Int) i lokalnego stanu jednego View. SwiftUI automatycznie zarządza pamięcią State. @StateObject — dla reference types (class) zgodnych z ObservableObject. @StateObject jest właścicielem obiektu i przerysowuje View przy zmianie @Published właściwości. Dla prostych liczników używaj @State; dla modeli z logiką — @StateObject.
Tak, SwiftUI integruje się z UIKit przez UIHostingController (SwiftUI wewnątrz UIKit) i UIViewRepresentable (UIKit wewnątrz SwiftUI). UIHostingController opakowuje SwiftUI View w UIViewController. UIViewRepresentable umożliwia używanie komponentów UIKit (MKMapView, WKWebView) w SwiftUI. To standardowe podejście do migracji projektów z UIKit na SwiftUI.
ViewBuilder — result builder (Swift 5.1), transformujący zestaw View w pojedynczą wartość typu TupleView, Group lub ConditionalContent. ViewBuilder umożliwia pisanie imperatywnego if/else i switch wewnątrz deklaratywnego body. Bez ViewBuilder trzeba by zwracać AnyView lub Group dla każdego bloku warunkowego. ViewBuilder jest powodem, dla którego w body nie potrzeba przecinka między View.
Tak, SwiftUI obsługuje iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ i visionOS 1+. Jednak niektóre API są dostępne tylko w nowych wersjach: na przykład navigationStack (iOS 16+), Observable macro (iOS 17+). Dla kompatybilności wstecznej używaj #available i adaptacji UIKit.
Xcode Debug View Hierarchy pokazuje drzewo SwiftUI View z modyfikatorami i ramkami. Narzędzie SwiftUI Inspector (prawy panel Xcode) umożliwia zmianę modyfikatorów w czasie rzeczywistym. self._printChanges() w body loguje przyczyny przerysowania. Instruments z szablonem SwiftUI śledzi wydajność View i wykrywa nadmierne przerysowania.
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ż