let or a @Binding when it needs data or its own state.Why this happens
Nesting a struct inside a View is only namespacing. ContentView.InnerView is a completely separate type that happens to be declared inside another type’s braces. It does not capture the enclosing instance, and it is not created by ContentView in any special way — SwiftUI instantiates it like any other view.
So when you write ContentView.someVar inside InnerView, the compiler looks for a type-level member on ContentView. someVar is an instance property, so the lookup fails. The message points at InnerView because that is the type being type-checked at that moment:
Instance member 'someVar' cannot be used on type 'InnerView'; did you mean to use a value of this type instead?
There is no syntax that lets a nested type reach into the parent value. SwiftUI views are value types that get recreated on every state change, so “the outer view” is not a stable object you can point at. Data has to travel down explicitly, through initializer parameters, bindings, or the environment.
How to fix it
Pick the option that matches what the subview actually needs.
1. Use a computed property for layout-only subviews
If the piece you are extracting is just a chunk of the parent’s body and needs no state of its own, make it a computed property. It is part of ContentView, so it reads someVar directly.
import SwiftUI
struct ContentView: View {
@State private var someVar = 0
var body: some View {
VStack {
innerView
}
}
private var innerView: some View {
Text("someVar = \(someVar)")
.padding()
}
}
This is the closest thing to what the nested struct was trying to do, and it keeps the code short. The trade-off: a computed property cannot hold @State of its own, because it is not a View type — it only produces one.
2. Use a function when the subview needs parameters
When the same layout is reused with different values, turn it into a method that takes arguments.
private func row(title: String, value: Int) -> some View {
HStack {
Text(title)
Spacer()
Text("\(value)")
}
}
Call it as row(title: "Count", value: someVar). The method still runs inside ContentView, so it can read any of the parent’s properties as well as the parameters.
3. Make it a standalone View and pass the value in
If the subview is big enough to deserve its own type, declare it at file scope (or nested, it makes no difference) and give it the data it needs. Read-only data is a plain let.
struct InnerView: View {
let someVar: Int
var body: some View {
Text("someVar = \(someVar)")
.padding()
}
}
struct ContentView: View {
@State private var someVar = 0
var body: some View {
InnerView(someVar: someVar)
}
}
This is the version to prefer once the subview grows. It compiles on its own, it is previewable in isolation, and it does not care where the value came from.
4. Pass a @Binding when the child must write back
A let gives read-only access. If the child edits the value — a TextField, a Stepper, a toggle — pass a binding instead. The state stays owned by the parent.
struct StepperRow: View {
@Binding var value: Int
var body: some View {
Stepper("Value: \(value)", value: $value)
}
}
struct ContentView: View {
@State private var someVar = 0
var body: some View {
StepperRow(value: $someVar)
}
}
The $ prefix on someVar produces the projected value, which is the Binding<Int> the child expects. If you forget it, you get a type mismatch on Int vs Binding<Int>.
5. Use an @Observable model for many subviews
Once several subviews need overlapping pieces of the same data, threading parameters through every level gets noisy. Move the data into an observation-tracked class and share it through the environment.
import SwiftUI
import Observation
@Observable
final class FormModel {
var name = ""
var count = 0
}
struct ContentView: View {
@State private var model = FormModel()
var body: some View {
Form {
NameField(model: model)
CountLabel(model: model)
}
}
}
struct NameField: View {
@Bindable var model: FormModel
var body: some View {
TextField("Name", text: $model.name)
}
}
struct CountLabel: View {
let model: FormModel
var body: some View {
Text("Count: \(model.count)")
}
}
@Bindable gives you $model.name for editing; a plain let is enough for read-only children. For deep hierarchies, put the model in the environment with .environment(model) on the container and read it with @Environment(FormModel.self) private var model instead of passing it down by hand.
One caveat: @Observable requires iOS 17 / macOS 14 or later. On older deployment targets, use ObservableObject with @StateObject in the owner and @ObservedObject or @EnvironmentObject in the children.
How to prevent it
- Treat a nested
structas a separate type, never as a closure over the parent. If it needs parent data, pass it in. - Use computed properties and private methods for pure layout, and real
Viewtypes for anything with its own state or reuse. - Decide ownership first: the view that creates the value owns it with
@State; everyone else gets alet(read) or a@Binding(read/write). - Reach for
@Observableplus the environment only when the same data is needed three or more levels down, not as a default. - Keep subviews small enough that their initializer is obvious from the call site — a long parameter list is a hint that the data belongs in a model.
FAQ
Can a nested struct access the outer view’s properties at all?
No. Nesting affects only the type’s name (ContentView.InnerView), not its scope. The nested type has no reference to any instance of the enclosing type, so every value it needs must arrive through its initializer, a binding, or the environment.
Should I pass a value or a @Binding?
Pass a plain let if the child only displays the value. Pass @Binding if the child modifies it, because the parent must remain the owner of the state. Passing a let and trying to mutate it will not compile, and copying the value into the child’s own @State will silently desynchronize the two.
Is it bad practice to nest View structs inside another View?
Not inherently — it is legal and sometimes useful for namespacing. But it buys you nothing in terms of data access, and it makes the child harder to preview and reuse. Declaring it at file scope is the more common choice once the view is more than a few lines.
Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.
