Skip to content

Conversation

@lanserxt
Copy link
Contributor

@lanserxt lanserxt commented Feb 8, 2026

From discussion in previous PR: #7

Need to highlight in Skill that using such init, in general, is not very good pattern and breaks the logical scope.

CC @malhal

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9d2e2975eb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

### @StateObject instantiation in View's initializer
If you need to create a @StateObject with initialization parameters in your view's custom initializer, be aware of redundant allocations and hidden side effects.

This approach is an anti-pattern in general. Prefer storing the StateObject in the parent view or wherever the model is actually owned, then pass it down (as an StateObject direct or as binding) to keep ownership and lifecycle explicit.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid suggesting passing a @StateObject to child views

The new guidance says to “pass it down (as an StateObject direct or as binding)”, but child views should not declare the same instance as @StateObject; they should use @ObservedObject, @EnvironmentObject, or @Bindable (for @Observable) while ownership stays in the parent. Suggesting @StateObject or a binding here can mislead readers into duplicating ownership or expecting bindings for reference types, which breaks the lifecycle clarity this section is trying to enforce.

Useful? React with 👍 / 👎.

@EngOmarElsayed
Copy link
Collaborator

It's better to say use such a thing with Parent View (Main View screen) because in such case it is valid useCase and will not break ownership lifeCycle. But don't say it is anti pattern because one of the core Philosophy of this skill is that we don't to force any specific pattern or way of writing code.

@malhal
Copy link

malhal commented Feb 9, 2026

It’s not valid, see the discussion.

@EngOmarElsayed
Copy link
Collaborator

It’s not valid, see the discussion.

I saw the discussion and the thing is: when we init the @StateObject in the init of the View using StateObject(initial: _), all what we are doing is the following setting the default value of the State doing so doesn't break lifecycle of the State at all.

What makes it a problem is when you expect the State to update when you change the value given to the view, but in case of you know that this is the parent view (Main View of the screen) and it takes a parameter as an input (not a state) and you use it in the init it's safe and valid approach, putting a side any architecture approach.

See reference (StateObject is the same as State different is StateObject is for objects):
image
image
image

@malhal
Copy link

malhal commented Feb 9, 2026

I'm not sure where those pages are from but those pages and your explanation are the exact anti-pattern we need the skill to avoid. SwiftUI is the architecture and the architecture requires lifting the state into parent View and pass Binding down for write access (or let if its read only).

Explained more in the discussion here:
#7 (comment)

e.g.

struct CounterView: View {
    @Binding private var value: Int
  
    var body: some View {
         Button(" Increment: \(value)") {
            value += 1
        }
    }
}

struct SomeParent: View {
    @State var counter = Counter()

    var body: some View {
        CounterView(value: $counter.value)
    }
}

struct Counter {
    var value = 0
    // other vars or would be no point in having a struct to relate them

    struct mutating func reset() { // the people that use the anti-pattern are usually not aware of mutating func
        value = 0
    }
}

@EngOmarElsayed
Copy link
Collaborator

When you add a parameter (initial value ) for a view this doesn't mean we are removing the state from the parent view, all what it does is giving initial value of the state the same as the initial vale we put to the state after =.

So Actually doing so has zero effect and it's a valid SwiftUI code that doesn't break the framework, maybe it's not the best architecture discussion and we don't include or force any architecture preferences in this skill.

And by the way this book is created by objc.io specifically by chris eidhof. He is one of the top people in SwiftUI check him out.

@malhal
Copy link

malhal commented Feb 10, 2026

Passing an initial value to a child's @State creates a 'Split Source of Truth.' Once the child View is initialized, the parent can no longer update that value. The child 'owns' its own copy now, and the two will drift apart, creating a real mess to get back in sync.

Could you show the specific use case you're working on? It's better to update the parent’s state before the child view appears, rather than trying to sync two separate states. And that can actually be tricky for someone new to SwiftUI, it might need the use of a struct or a computed Binding.

@lanserxt
Copy link
Contributor Author

I've made a snippet to play with based on ObjC SwiftUI example:

import SwiftUI

struct Counter: View {
    
    init(value: Int = 0) {
        _value = State(wrappedValue: value)
    }
    
    @State private var value: Int
    
    var body: some View {
        Button("Increment: \(value)") {
            value += 1
        }
    }
    
}

struct StateBindView: View {
    
    @State private var value: Int = 0
    
    var body: some View {
        Divider()
        Counter(value: value)
        Divider()
        Counter(value: value)
            .id(value)
        Divider()
        Counter(value: 5)
        Divider()
        Button("Increment: \(value)") {
            value += 1
        }
    }
}

#Preview {
    StateBindView()
}

As we can see, it decouples from rendering tree if we will use such init. Last paragraph says: it's not very useful.

My suggestion: Let's remove "anti-pattern sentence" suggested by Codex.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants