SwiftUI: what it is, key concepts and View Protocol

Author: IT Sectr Published: 2026-04-30 Reading time: 8 min

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

  • SwiftUI is a declarative Apple framework where the developer describes the interface and updates are performed automatically.
  • View Protocol with the body property is the foundation of any SwiftUI UI component, returning a screen description through view composition.
  • Property Wrappers — @State, @Binding, @ObservedObject, @StateObject — manage state and trigger redrawing when data changes.
  • NavigationStack (iOS 16+) is a modern navigation API with type-safe routes and declarative transitions.
  • Modifier is a chain of calls for customizing the appearance and behavior of views without class inheritance.

What is SwiftUI?

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.

SwiftUI Cross-Platform

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.

View Protocol and the View Body

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.

swift
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 and Conditionals

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.

State Management: @State, @Binding, @ObservedObject

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.

swift
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 and Parent-Child Connection

@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.

swift
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.

Programmatic Navigation

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)).

View Modifier — Customizing Appearance

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.

Conditional Modifiers and Animation

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.

SwiftUI vs UIKit: Comparing Approaches

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.

AspectSwiftUIUIKit
ApproachDeclarative: what to showImperative: how to build
StateProperty Wrappers, automatic redrawManual: reloadData, setNeedsLayout
UI CodeCompact, modifier chainsVerbose, NSCoder/Storyboard/constraints
PerformanceHigh on iOS 17+, diff algorithmPeak on iOS 12–16, direct control
Minimum VersioniOS 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

Can I use SwiftUI and UIKit in the same project?

Yes, through UIHostingController (SwiftUI in UIKit) and UIViewRepresentable (UIKit in SwiftUI). This is a hybrid approach, popular during migration.

Which iOS version should I start a SwiftUI project with?

iOS 17 provides full functionality: NavigationStack, Observation framework, Swift Charts. iOS 15 is the minimum threshold for production.

Why does SwiftUI sometimes not update the interface?

The most common reason is changing a @Published property on a background thread. ObservableObject must send changes on the main actor: @MainActor class ViewModel.

How to handle button press with debounce in SwiftUI?

Use .debounce through Combine: Button.publisher(for: .tap) .debounce(for: .seconds(0.3), scheduler: RunLoop.main).

Does SwiftUI support custom gestures?

Yes, through Gesture modifiers: DragGesture, LongPressGesture, MagnificationGesture, RotationGesture. Combine them using .simultaneousGesture() and .sequenced().

Summary

  • SwiftUI is a declarative Apple framework where the interface is described as a composition of View structures with property wrappers for state management.
  • View Protocol with computed property body is the single entry point for any view. ViewBuilder assembles up to 10 views into one without extra containers.
  • @State, @Binding, and @ObservedObject cover all data management scenarios: local state, parent-child connection, and external models.
  • NavigationStack with type-safe enum routes replaced NavigationView, adding support for deep links and programmatic navigation.
  • Modifier is a key SwiftUI pattern that allows customizing the appearance of views through a chain of calls without inheritance.
  • SwiftUI and UIKit coexist through UIHostingController and UIViewRepresentable, allowing gradual project migration.
  • For iOS 17+, Apple recommends SwiftUI as the primary framework; UIKit remains for complex custom interfaces and support for older versions.

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.

Discuss the project

Read also