SwiftUI is a declarative framework from Apple for building user interfaces across all platforms of the ecosystem. Instead of imperatively describing steps, the developer declares how the interface should look, and SwiftUI manages its rendering and updating. According to Apple Developer Documentation (2025), SwiftUI supports iOS 15+, iPadOS 15+, macOS 12+, watchOS 8+, and tvOS 15+ and uses the View Protocol as the basic building block for all interface components.
Key Takeaways
body property is the foundation of any SwiftUI UI component, returning a screen description through view composition.SwiftUI is a declarative framework introduced by Apple in 2019 to replace UIKit in new projects. Instead of manually creating UIView instances and adding them to the hierarchy, the developer describes the interface through structures that conform to the View protocol. SwiftUI automatically computes the difference between the current and new state and redraws only the changed parts using its own rendering engine.
The framework is written in Swift using value semantics (structures, not classes), making UI components lightweight and thread-safe. Unlike UIKit, where UIViewController can weigh 200+ bytes due to Objective-C runtime, a SwiftUI View is simply a structure a few bytes in size. This is especially important for watchOS with its limited memory.
The same View description works on iPhone, iPad, Mac, Apple Watch, Apple TV, and Apple Vision Pro. SwiftUI adapts the interface to the platform: touch gestures on iOS, keyboard combinations on macOS, Digital Crown scrolling on watchOS. This reduces development time for companies releasing apps on multiple Apple platforms but requires additional configuration for platform-specific elements.
In SwiftUI, each screen is a structure that implements the View protocol with a single requirement: a computed property body of type some View. The some keyword (opaque type) hides the concrete view type, allowing SwiftUI to optimize rendering. Inside body, the developer combines ready-made components — Text, Image, Button, List — using ViewBuilder, which assembles multiple views into one.
struct GreetingView: View {
let name: String
var var body: some View {
VStack {
Text("Hello, \(name)!")
.font(.title)
.foregroundColor(.blue)
Image(systemName: "hand.wave")
.imageScale(.large)
}
.padding()
}
}
In the example, VStack (vertical stack) contains Text and Image. The name value is passed through the structure initializer — this is how DI (Dependency Injection) works in SwiftUI without external DI containers. Each modifier returns a new view with the applied change, without mutating the original. This is possible thanks to the immutability of value types.
ViewBuilder is a result builder annotated with @resultBuilder that assembles up to 10 views into one. Inside body, you can use if/else, switch, and ForEach without additional wrappers. ForEach works with Identifiable elements — each view is assigned a unique id for proper animation during insertion/deletion.
In SwiftUI, state determines what content is displayed on the screen. When state changes, SwiftUI recreates the body of the dependent view and compares the result with the previous one using a diff algorithm. Property wrappers are used to store state — each one solves its specific task: local state, connection with a child view, or external data model.
struct CounterView: View {
@State private var count = 0
var var body: some View {
VStack {
Text("Counter: \(count)")
Button("Increment") {
count += 1
}
}
}
}
class UserViewModel: ObservableObject {
@Published var name = ""
@Published var age = 0
}
@State stores a local simple value (Int, String, Bool) inside the View structure. SwiftUI moves the memory from the structure to a separate storage — so a property with @State can be mutated even if the View is a value type. @ObservableObject is for classes with @Published properties, whose changes automatically notify SwiftUI about the need to redraw.
@Binding creates a two-way connection to a data source located in the parent view. The parent passes $variable (projected value), and the child reads and writes the value through the binding. This allows moving text input or a toggle into a separate component while keeping the state in the parent. Without @Binding, each change would require a callback closure to pass the new value upward.
Before iOS 16, navigation in SwiftUI was built on NavigationView — a legacy API with complex behavior on iPad (split view, double column). Starting with iOS 16, Apple recommends NavigationStack — a simplified alternative with type-safe routes. The developer defines an enum of possible routes, and NavigationStack automatically manages the screen stack with support for deep links and returning to the root.
enum Route: Hashable {
case detail(id: Int)
case settings
}
struct ContentView: View {
var var body: some View {
NavigationStack {
List {
NavigationLink("Detail screen",
value: Route.detail(id: 42))
NavigationLink("Settings",
value: Route.settings)
}
.navigationDestination(for: Route.self) { route in
switch route {
case .detail(let id): DetailView(id: id)
case .settings: SettingsView()
}
}
}
}
}
Routes conforming to Hashable allow using any data type for passing parameters. navigationDestination(for:destination:) associates the route type with the target view. The advantage over UIKit navigation is that no redrawing is required when adding a new route: just add a case to the enum and a handler in the switch. Deep links are handled through processDeepLink on NavigationStack.
For programmatic navigation (after login, timer, or server response), @State is used with the NavigationLink initializer: NavigationLink(isActive: $isActive). When isActive = true, the transition occurs without user touch. An alternative is binding the $path array in NavigationStack: $path.append(Route.detail(id: 1)).
Modifier is a method that returns a modified copy of the view. Unlike UIKit, where property configuration is done through mutating an existing view, SwiftUI creates a new value with the applied change. Modifier chaining builds the final interface from sequential transformations: font → padding → color → shadow → gesture.
Apple provides over 200 built-in modifiers. The most common ones: .font(), .foregroundColor(), .padding(), .background(), .cornerRadius(), .shadow(), .opacity(), .offset(). The order of modifiers matters: .padding() before .background() fills the area with padding, after — only the inner area. Custom modifiers are created through the ViewModifier protocol.
Modifiers can be applied conditionally using the ternary operator: .foregroundColor(isError ? .red : .primary). For animation, .animation(.easeInOut, value: state) is used — the animation modifier is tied to a specific state property. When this property changes, SwiftUI animates the transition between the old and new value. Animation works with opacity, offset, scale, rotation, size, and color — each property has a corresponding AnimatableParameter.
For custom animations, .transition (appearance/disappearance) and .matchedGeometryEffect (smooth transition of an element between two containers) are available. The latter is used for hero animation in lists: an icon in a list cell smoothly transforms into a large image on the detail screen.
Choosing between SwiftUI and UIKit is one of the first dilemmas for iOS developers. Both frameworks are supported by Apple but solve the interface building problem in fundamentally different ways: SwiftUI declaratively, UIKit imperatively. The difference manifests in state management, navigation, performance, and compatibility.
| Aspect | SwiftUI | UIKit |
|---|---|---|
| Approach | Declarative: what to show | Imperative: how to build |
| State | Property Wrappers, automatic redraw | Manual: reloadData, setNeedsLayout |
| UI Code | Compact, modifier chains | Verbose, NSCoder/Storyboard/constraints |
| Performance | High on iOS 17+, diff algorithm | Peak on iOS 12–16, direct control |
| Minimum Version | iOS 15+ (full support) | iOS 2+ (all versions) |
For new projects with a minimum iOS 17 version, Apple recommends SwiftUI as the primary framework. UIKit remains necessary for interfaces requiring fine-grained control over rendering (custom UICollectionViewLayout, complex CAAnimation scenes) or support for iOS 12–14. Many projects use a hybrid approach: SwiftUI via UIHostingController is embedded into a UIKit app, and UIViewRepresentable allows using UIKit components inside the SwiftUI hierarchy.
Frequently Asked Questions
Yes, through UIHostingController (SwiftUI in UIKit) and UIViewRepresentable (UIKit in SwiftUI). This is a hybrid approach, popular during migration.
iOS 17 provides full functionality: NavigationStack, Observation framework, Swift Charts. iOS 15 is the minimum threshold for production.
The most common reason is changing a @Published property on a background thread. ObservableObject must send changes on the main actor: @MainActor class ViewModel.
Use .debounce through Combine: Button.publisher(for: .tap) .debounce(for: .seconds(0.3), scheduler: RunLoop.main).
Yes, through Gesture modifiers: DragGesture, LongPressGesture, MagnificationGesture, RotationGesture. Combine them using .simultaneousGesture() and .sequenced().
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also