If you mark any variables as @State in a SwiftUI View and bind them to a property inside the body of that View, the body will be recalculated whenever the @State variable changes and hence your whole View will be redrawn. Also, @State variables should serve as the single source of truth for a View. For these reasons, @State variables should only be accessed and updated from within the body of a View and hence should be declared private.
You should use @State when you are binding some user input (such as the value of a TextField or the chosen value from a Picker). @State should be used for value types (structs and enums).
On the other hand, @ObservedObject should be used for reference types (classes), since they trigger refreshing a view whenever any @Published property of the ObservableObject changes.
You should use @ObservedObject when you have some data coming in from outside your View, such as in an MVVM architecture with SwiftUI, your ViewModel should be stored as an @ObservedObject on your View.
A common mistake with @ObservedObjects is to declare and initialise them inside the View itself. This will lead to problems, since every time the @ObservedObject emits an update (one of its @Published properties gets updated), the view will be recreated - which will also create a new @ObservedObject, since it was initialised in the View itself. To avoid this problem, whenever you use @ObservedObject, you always have to inject it into the view. The iOS 14 @StateObject solves this issue.