swiftui · tutorial · architecture · state

Passing Bindings Through Multiple View Layers

Published February 26, 2026 · 3 min read · intermediate

Goal

By the end of this tutorial, you will have built a three-layer SwiftUI flow where top-level state is edited safely from deep child views.

“Build a profile editor where username is owned at the screen level and edited inside a nested row.”


Requirements

  • Basic SwiftUI knowledge
  • Xcode 15+
  • iOS 17+

The use case

Large screens often contain nested sections and reusable rows. A common mistake is moving ownership into a child with @State just to make editing easier. We will keep ownership at the top and forward bindings through layers.


Step 1 - The simplest working version

Start with one owner and one editor.

import SwiftUI

struct ProfileScreen: View {
    @State private var username = "Pascal"

    var body: some View {
        UsernameEditor(username: $username)
    }
}

struct UsernameEditor: View {
    @Binding var username: String

    var body: some View {
        TextField("Username", text: $username)
            .textFieldStyle(.roundedBorder)
            .padding()
    }
}

This already has one source of truth.


Step 2 - Make it reusable

Introduce an intermediate section and forward the binding.

struct ProfileScreen: View {
    @State private var username = "Pascal"

    var body: some View {
        ProfileSection(username: $username)
    }
}

struct ProfileSection: View {
    @Binding var username: String

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text("Account")
                .font(.headline)
            UsernameRow(username: $username)
        }
        .padding()
    }
}

struct UsernameRow: View {
    @Binding var username: String

    var body: some View {
        HStack {
            Text("Username")
            Spacer()
            TextField("Username", text: $username)
                .multilineTextAlignment(.trailing)
                .textFieldStyle(.roundedBorder)
                .frame(maxWidth: 180)
        }
    }
}

Each layer reflects or forwards; ownership still lives in ProfileScreen.


Step 3 - SwiftUI-specific refinement

Add validation and intent callbacks without moving ownership.

struct UsernameRow: View {
    @Binding var username: String
    let onSubmit: () -> Void

    var body: some View {
        TextField("Username", text: $username)
            .textFieldStyle(.roundedBorder)
            .onSubmit(onSubmit)
            .overlay(alignment: .trailing) {
                if username.count < 3 {
                    Image(systemName: "exclamationmark.triangle.fill")
                        .foregroundStyle(.orange)
                }
            }
    }
}

This keeps editing (@Binding) and side effects (onSubmit) clearly separated.


Result

You now have a nested editing flow that is:

  • reusable
  • composable
  • ownership-safe

Common SwiftUI pitfalls

  • Mistake: Child view declares @State for shared form values
    Why it happens: quick extraction without ownership check
    How to fix it: move state up, replace with @Binding

  • Mistake: Passing callbacks for direct value edits
    Why it happens: trying to avoid bindings everywhere
    How to fix it: use @Binding for direct data edits, callbacks for intent


When NOT to use this

If a value is purely local to one child and never needed outside, local @State is simpler than forwarding bindings across many layers.


Takeaway

If you remember one thing: forward bindings across layers, but keep ownership at the level that decides behavior.

Next steps