SwiftUI — Key Concepts: View, State and Data Flow

Author: IT Sectr Published: 2026-02-21 Reading time: 8 min

SwiftUI — Apple's declarative framework for building user interfaces across all ecosystem platforms, introduced at WWDC 2019. Unlike the imperative UIKit with its viewDidLoad and manual screen updates, SwiftUI describes UI as a collection of simple structures conforming to the View protocol. According to Swift.org (2025), SwiftUI is used in 65% of new projects published on the App Store. The framework automatically manages interface updates through the State and Data Flow mechanism — when data changes, the View redraws without manual reloadData calls.

Key Takeaways

  • SwiftUI — Apple's declarative UI framework (2019), where the interface is described by structures conforming to the View protocol.
  • View — the basic building block of SwiftUI; each View describes its part of the screen through a computed property body.
  • @State — a property wrapper for storing local state, when changed the View automatically redraws.
  • @Binding — a two-way connection between View and data, allowing a child View to modify the parent's state.
  • @ObservedObject and @StateObject — connection to external data models through classes conforming to the ObservableObject protocol.

What is SwiftUI?

SwiftUI — Apple's declarative UI framework, radically different from UIKit. Instead of creating controllers, views and manually managing their lifecycle, the developer describes the interface as declarations: what should be on the screen, not how to build it. SwiftUI is based on the principle of reactivity: the interface is a function of state. When the state changes, SwiftUI automatically recalculates the body of all dependent Views and updates only the changed parts of the screen. SwiftUI is available on iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ and visionOS 1+. SwiftUI code is cross-platform: one file works on iPhone, iPad, Mac and Apple Watch with minimal platform adaptations. According to Apple WWDC Session 101 (2024), SwiftUI covers over 90% of standard UI patterns in the App Store.

SwiftUI vs UIKit

UIKit — imperative framework (2008): the developer creates a UIViewController, configures subviews in viewDidLoad, implements delegate/datasource for UITableView and updates the screen via reloadData or setNeedsLayout. SwiftUI replaces controllers with simple View structures, delegates with bindings and onChange, Auto Layout with HStack/VStack/ZStack and modifiers (padding, frame, offset). UIKit requires manual memory management through ARC; SwiftUI uses structures that don't require reference counting. SwiftUI performance is comparable to UIKit: the framework uses a diffing algorithm for minimal change sets. At IT Sectr, SwiftUI is used for new projects with iOS 17+ target; projects supporting iOS 14–15 require UIKit due to limited SwiftUI compatibility.

SwiftUI Declarative Syntax

In SwiftUI, the interface is described through ViewBuilder — a result builder that transforms a set of Views into a tuple or Group. Modifiers (.padding(), .font(), .foregroundColor()) create new Views with modified settings rather than mutating the original object. Each modifier returns a new View, enabling chaining. ViewBuilder supports if/else, switch, ForEach — conditional and cyclic rendering without separate controllers. View in SwiftUI is a value type (struct), ensuring predictable behavior and eliminating race conditions.

View Protocol and computed property body

View — a protocol with a single requirement: the computed property body of type some View. Each structure conforming to View describes its part of the screen in body. The some View type is an opaque return type that hides the concrete type of the returned View (stacking VStack, HStack, ZStack, Text, Image, etc.). The Swift compiler infers the concrete type at compile time, preserving the performance of direct calls without type erasure.

swift
import SwiftUI

struct GreetingView: View {
    var name: String
    
    var body: some View {
        VStack(spacing: 12) {
            Text("Hello, \(name)!")
                .font(.largeTitle)
                .foregroundColor(.primary)
            
            Text("Welcome to SwiftUI")
                .font(.body)
                .foregroundColor(.secondary)
        }
        .padding()
        .background(
            RoundedRectangle(cornerRadius: 12)
                .fill(.ultraThinMaterial)
        )
    }
}

The GreetingView structure takes a name parameter and displays two text blocks in a vertical stack. Modifiers .font, .foregroundColor, .padding and .background configure the appearance. SwiftUI calls body each time the input parameters (name) change — redrawing occurs only for the changed parts. The example uses RoundedRectangle with .ultraThinMaterial — a native blur background built into SwiftUI.

@State: Local State in SwiftUI

@State — a property wrapper that declares local state belonging to a single View. SwiftUI manages State memory automatically: when the value changes, body redraws, but only for Views using that State. State is the source of truth for simple types (String, Int, Bool, enum). Do not use @State for complex data models — use @StateObject and @ObservedObject instead. State should be private and stored within the View itself, not passed between components.

swift
import SwiftUI

struct CounterView: View {
    @State private var count = 0
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Count: \(count)")
                .font(.system(size: 48, weight: .bold))
            
            Button(action: { count += 1 }) {
                Label("Increment", systemImage: "plus.circle")
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

The initial count value = 0. Each button press increments count; SwiftUI automatically redraws the entire CounterView (all Views). In UIKit, a similar scenario would require IBOutlet, IBAction and manual label.text updates. @State ensures that a View redraws only when a specific State changes — SwiftUI's diffing algorithm finds the minimal changes in the tree.

@Binding: Two-Way Communication Between Views

@Binding — a property wrapper that creates a two-way connection between a View and data that the View does not own. A Binding is a reference to State (or another source of truth), allowing a child View to read and modify the value stored in the parent. Binding is denoted by the $ prefix: $count passes a Binding<Int> to the child View. Without Binding, a child View cannot modify the parent's data — it can only read it.

swift
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 owns the State quantity and passes Binding via $quantity. StepperControl can change the value, and quantity in the parent synchronizes automatically. A Binding is not a copy of the data, but a bridge to the source of truth. Use @Binding for custom controls, editors and reusable components that need to modify the parent's data.

@ObservedObject and @StateObject: External Data Models

@StateObject — a property wrapper for creating and owning an instance of a class conforming to ObservableObject. The View creates the object once per lifecycle and redraws when its @Published properties change. @ObservedObject — a similar wrapper, but the View does not own the object — the object is created and stored outside the View (passed through an initializer). Apple recommends @StateObject for the source of truth in a View hierarchy and @ObservedObject for dependency injection.

swift
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("Welcome, \(settings.username)!")
                    .font(.headline)
            }
        }
        .padding()
    }
}

UserSettings — an ObservableObject with two @Published properties. ProfileView owns the object via @StateObject. Changes to username or isLoggedIn automatically redraw ProfileView. @Published uses Combine Publisher to notify SwiftUI of changes. To pass settings to child Views, use @ObservedObject:

Data Flow in SwiftUI: The Complete Picture

Apple defines four levels of Data Flow in SwiftUI: @State (local, value type), @Binding (two-way), @StateObject/@ObservedObject (reference type with ObservableObject), @EnvironmentObject (global, injected through environment). EnvironmentObject allows passing data through the entire View hierarchy without explicit initializer passing. Additionally, @AppStorage works with UserDefaults, @SceneStorage with scene state, @FetchRequest with Core Data. The choice of Data Flow level determines the application architecture: simple screens use State/Binding, modular ones use ObservedObject, large-scale ones use EnvironmentObject + Redux-like solutions (TCA, Composable Architecture).

Property WrapperOwnershipTypeWhen to Use
@StateLocalValue (struct, enum)Simple state of a single View (counter, toggle, text field)
@BindingExternalReference to StateChild View modifying parent's data
@StateObjectView OwnershipReference (class)Source of truth for complex data model
@ObservedObjectInjectionReference (class)Model created outside View (passed via init)
@EnvironmentObjectGlobalReference (class)Data available to entire hierarchy (auth, theme)

Frequently Asked Questions

How is @State different from @StateObject?

@State — for value types (struct, enum, String, Int) and local state of a single View. SwiftUI manages State memory automatically. @StateObject — for reference types (class) conforming to ObservableObject. @StateObject owns the object and redraws the View when @Published properties change. For simple counters use @State; for models with business logic use @StateObject.

Can I use SwiftUI with UIKit?

Yes, SwiftUI integrates with UIKit through UIHostingController (SwiftUI inside UIKit) and UIViewRepresentable (UIKit inside SwiftUI). UIHostingController wraps a SwiftUI View in a UIViewController. UIViewRepresentable allows using UIKit components (MKMapView, WKWebView) in SwiftUI. This is the standard approach for migrating projects from UIKit to SwiftUI.

What is ViewBuilder in SwiftUI?

ViewBuilder — a result builder (Swift 5.1) that transforms a set of Views into a single value of type TupleView, Group or ConditionalContent. ViewBuilder allows writing imperative if/else and switch inside a declarative body. Without ViewBuilder you would have to return AnyView or Group for each conditional block. ViewBuilder is the reason why body doesn't need commas between Views.

Does SwiftUI work on all Apple devices?

Yes, SwiftUI supports iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ and visionOS 1+. However, some APIs are only available on newer versions: for example, navigationStack (iOS 16+), Observable macro (iOS 17+). For backwards compatibility, use #available and UIKit adaptations.

How to debug SwiftUI applications?

Xcode Debug View Hierarchy shows the SwiftUI View tree with modifiers and frames. The SwiftUI Inspector tool (Xcode right panel) allows modifying modifiers in real time. self._printChanges() in body logs redraw reasons. Instruments with the SwiftUI template traces View performance and identifies excessive redraws.

Summary

  • SwiftUI — Apple's declarative UI framework, where the interface is described by View structures with a computed property body (2019).
  • View — a value type (struct) conforming to the View protocol; body returns some View via ViewBuilder.
  • @State — local state for value types; when changed, the View automatically redraws.
  • @Binding — two-way connection via the $ prefix; a child View modifies the parent's data.
  • @StateObject / @ObservedObject — reference types with ObservableObject and @Published properties; StateObject owns the object, ObservedObject receives from outside.
  • @EnvironmentObject — global state for the entire View hierarchy; injected via .environmentObject().
  • Data Flow in SwiftUI — from State (local) through Binding (two-way) to ObservedObject (modular) and EnvironmentObject (global).

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