How do I instantiate a view with multiple EnvironmentObjects? - swift

I was learning about how EnvironmentObject works for a school project, and I was confused about how to instantiate a view with multiple EnvironmentObjects. For example, the following code:
import SwiftUI
class names: ObservableObject {
#Published var myName = ""
}
struct FirstView: View {
#StateObject var FirstName = names()
var body: some View {
NavigationView {
VStack {
TextField("Type", text: $FirstName.myName)
NavigationLink(destination: SecondView()) {
Text("Second View")
}
}
}.environmentObject(FirstName)
}
}
struct SecondView: View {
#StateObject var LastName = names()
var body: some View {
VStack {
TextField("Type", text: $LastName.myName)
NavigationLink(destination: ThirdView().environmentObject(FirstName).environmentObject(LastName)) {
Text("Third View")
}
}.environmentObject(LastName)
}
}
struct ThirdView: View {
#EnvironmentObject var FirstName: names
#EnvironmentObject var LastName: names
var body: some View {
Text("Full name: \(FirstName.myName) \(LastName.myName)")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
FirstView()
}
}
I need ThirdView to receive FirstName from FirstView and LastName from SecondView, but I can't instantiate ThirdView from SecondView with the required Environment Objects; this code above crashes with the error "Cannot find FirstName in scope".
Alternatively, If I try to instantiate ThirdView with only LastName as an environment object, the code will present something like "Smith Smith" if I entered "John" in the text field on FirstView and "Smith" in the text field on SecondView.
Can someone tell me what I'm doing wrong? Thank you! :)

Since they are of the same type you can’t have two. SwiftUI can’t tell the difference
//Names for classes and structs should start with an uppercase letter
class PersonModel: ObservableObject {
#Published var firstName = ""
#Published var lastName = ""
}
struct FirstNameView: View {
//variables start with lowercase
#StateObject var person: PersonModel = PersonModel()
var body: some View {
NavigationView {
VStack {
TextField("Type", text: $person.firstName)
NavigationLink(destination: LastNameView()) {
Text("Second View")
}
}
}.environmentObject(person)
}
}
struct LastNameView: View {
#EnvironmentObject var person: PersonModel
var body: some View {
VStack {
TextField("Type", text: $person.lastName)
NavigationLink(destination: FullNameView()) {
Text("Third View")
}
}
}
}
struct FullNameView: View {
#EnvironmentObject var person: PersonModel
var body: some View {
Text("Full name: \(person.firstName) \(person.lastName)")
}
}

environmentObject(_:) modifier method takes an ObservableObject and passes it down the view tree. It works without specifying an environment key because the type of the object is automatically used as the key.
So to resume your last name instance is somehow invalidating your first name instance.
I'd then suggest either to create a model that contains both first and last name or simply use #Environment with a key (as it's suggested by Damiaan Dufaux) if it’s possible to get away with passing a value type, because it’s the safer mechanism.

You are probably looking for EnvironmentKeys.
Use them like this:
private struct FirstNameKey: EnvironmentKey {
static let defaultValue = "No first name"
}
private struct LastNameKey: EnvironmentKey {
static let defaultValue = "No last name"
}
And add them to your EnvironmentValues:
extension EnvironmentValues {
var firstName: String {
get { self[FirstNameKey.self] }
set { self[FirstNameKey.self] = newValue }
}
var lastName: String {
get { self[LastNameKey.self] }
set { self[LastNameKey.self] = newValue }
}
}
They values can then be bound to the environment like this:
var body: some View {
MyCustomView()
.environment(\.firstName, "John")
.environment(\.lastName, "Doe")
}
And retrieved like this:
struct ThirdView: View {
#Environment(\.firstName) var firstName
#Environment(\.lastName) var lastName
var body: some View {
Text("Full name: \(firstName) \(lastName)")
}
}
Side note on conventions
To understand code more easily the Swift.org community asks to
Give types UpperCamelCase names (such as SomeStructure and SomeClass here) to match the capitalization of standard Swift types (such as String, Int, and Bool). Give properties and methods lowerCamelCase names (such as frameRate and incrementCount) to differentiate them from type names.
So it would be better to write your class names as class Names as it greatly improves readability for Swift users.

Related

SwiftUI - Nested links within NavigationStack inside a NavigationSplitView not working

I'm playing around with the new navigation API's offered in ipadOS16/macOS13, but having some trouble working out how to combine NavigationSplitView, NavigationStack and NavigationLink together on macOS 13 (Testing on a Macbook Pro M1). The same code does work properly on ipadOS.
I'm using a two-column NavigationSplitView. Within the 'detail' section I have a list of SampleModel1 instances wrapped in a NavigationStack. On the List I've applied navigationDestination's for both SampleModel1 and SampleModel2 instances.
When I select a SampleModel1 instance from the list, I navigate to a detailed view that itself contains a list of SampleModel2 instances. My intention is to navigate further into the NavigationStack when clicking on one of the SampleModel2 instances but unfortunately this doesn't seem to work. The SampleModel2 instances are selectable but no navigation is happening.
When I remove the NavigationSplitView completely, and only use the NavigationStack the problem does not arise, and i can successfully navigate to the SampleModel2 instances.
Here's my sample code:
// Sample model definitions used to trigger navigation with navigationDestination API.
struct SampleModel1: Hashable, Identifiable {
let id = UUID()
static let samples = [SampleModel1(), SampleModel1(), SampleModel1()]
}
struct SampleModel2: Hashable, Identifiable {
let id = UUID()
static let samples = [SampleModel2(), SampleModel2(), SampleModel2()]
}
// The initial view loaded by the app. This will initialize the NavigationSplitView
struct ContentView: View {
enum NavItem {
case first
}
var body: some View {
NavigationSplitView {
NavigationLink(value: NavItem.first) {
Label("First", systemImage: "house")
}
} detail: {
SampleListView()
}
}
}
// A list of SampleModel1 instances wrapped in a NavigationStack with multiple navigationDestinations
struct SampleListView: View {
#State var path = NavigationPath()
#State var selection: SampleModel1.ID? = nil
var body: some View {
NavigationStack(path: $path) {
List(SampleModel1.samples, selection: $selection) { model in
NavigationLink("\(model.id)", value: model)
}
.navigationDestination(for: SampleModel1.self) { model in
SampleDetailView(model: model)
}
.navigationDestination(for: SampleModel2.self) { model in
Text("Model 2 ID \(model.id)")
}
}
}
}
// A detailed view of a single SampleModel1 instance. This includes a list
// of SampleModel2 instances that we would like to be able to navigate to
struct SampleDetailView: View {
var model: SampleModel1
var body: some View {
Text("Model 1 ID \(model.id)")
List (SampleModel2.samples) { model2 in
NavigationLink("\(model2.id)", value: model2)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I removed this unclear ZStack and all works fine. Xcode 14b3 / iOS 16
// ZStack { // << this !!
SampleListView()
// }
Apple just releases macos13 beta 5 and they claimed this was resolved through feedback assistant, but unfortunately this doesn't seem to be the case.
I cross-posted this question on the apple developers forum and user nkalvi posted a workaround for this issue. I’ll post his example code here for future reference.
import SwiftUI
// Sample model definitions used to trigger navigation with navigationDestination API.
struct SampleModel1: Hashable, Identifiable {
let id = UUID()
static let samples = [SampleModel1(), SampleModel1(), SampleModel1()]
}
struct SampleModel2: Hashable, Identifiable {
let id = UUID()
static let samples = [SampleModel2(), SampleModel2(), SampleModel2()]
}
// The initial view loaded by the app. This will initialize the NavigationSplitView
struct ContentView: View {
#State var path = NavigationPath()
enum NavItem: Hashable, Equatable {
case first
}
var body: some View {
NavigationSplitView {
List {
NavigationLink(value: NavItem.first) {
Label("First", systemImage: "house")
}
}
} detail: {
SampleListView(path: $path)
}
}
}
// A list of SampleModel1 instances wrapped in a NavigationStack with multiple navigationDestinations
struct SampleListView: View {
// Get the selection from DetailView and append to path
// via .onChange
#State var selection2: SampleModel2? = nil
#Binding var path: NavigationPath
var body: some View {
NavigationStack(path: $path) {
VStack {
Text("Path: \(path.count)")
.padding()
List(SampleModel1.samples) { model in
NavigationLink("Model1: \(model.id)", value: model)
}
.navigationDestination(for: SampleModel2.self) { model in
Text("Model 2 ID \(model.id)")
.navigationTitle("navigationDestination(for: SampleModel2.self)")
}
.navigationDestination(for: SampleModel1.self) { model in
SampleDetailView(model: model, path: $path, selection2: $selection2)
.navigationTitle("navigationDestination(for: SampleModel1.self)")
}
.navigationTitle("First")
}
.onChange(of: selection2) { newValue in
path.append(newValue!)
}
}
}
}
// A detailed view of a single SampleModel1 instance. This includes a list
// of SampleModel2 instances that we would like to be able to navigate to
struct SampleDetailView: View {
var model: SampleModel1
#Binding var path: NavigationPath
#Binding var selection2: SampleModel2?
var body: some View {
NavigationStack {
Text("Path: \(path.count)")
.padding()
List(SampleModel2.samples, selection: $selection2) { model2 in
NavigationLink("Model2: \(model2.id)", value: model2)
// This also works (without .onChange):
// Button(model2.id.uuidString) {
// path.append(model2)
// }
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Passing a state variable to parent view

I have the following code:
struct BookView: View {
#State var title = ""
#State var author = ""
var body: some View {
TextField("Title", text: $title)
TextField("Author", text: $author)
}
}
struct MainView: View {
#State private var presentNewBook: Bool = false
var body: some View {
NavigationView {
// ... some button that toggles presentNewBook
}.sheet(isPresented: $presentNewBook) {
let view = BookView()
view.toolbar {
ToolbarItem(placement: principal) {
TextField("Title", text: view.$title)
}
}
}
}
}
This compiles but is giving me the following error on runtime:
Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update.
How do I pass a state variable to some other outside view? I can't use ObservableObject on BookView since that would require me to change it from struct to class
In general, your state should always be owned higher up the view hierarchy. Trying to access the child state from a parent is an anti-pattern.
One option is to use #Bindings to pass the values down to child views:
struct BookView: View {
#Binding var title : String
#Binding var author : String
var body: some View {
TextField("Title", text: $title)
TextField("Author", text: $author)
}
}
struct ContentView: View {
#State private var presentNewBook: Bool = false
#State private var title = ""
#State private var author = ""
var body: some View {
NavigationView {
VStack {
Text("Title: \(title)")
Text("Author: \(author)")
Button("Open") {
presentNewBook = true
}
}
}.sheet(isPresented: $presentNewBook) {
BookView(title: $title, author: $author)
}
}
}
Another possibility is using an ObservableObject:
class BookState : ObservableObject {
#Published var title = ""
#Published var author = ""
}
struct BookView: View {
#ObservedObject var bookState : BookState
var body: some View {
TextField("Title", text: $bookState.title)
TextField("Author", text: $bookState.author)
}
}
struct ContentView: View {
#State private var presentNewBook: Bool = false
#StateObject private var bookState = BookState()
var body: some View {
NavigationView {
VStack {
Text("Title: \(bookState.title)")
Text("Author: \(bookState.author)")
Button("Open") {
presentNewBook = true
}
}
}.sheet(isPresented: $presentNewBook) {
BookView(bookState: bookState)
}
}
}
I've altered your example views a bit because to me the structure was unclear, but the concept of owning the state at the parent level is the important element.
You can also pass a state variable among views as such:
let view = BookView(title: "foobar")
view.toolbar {
ToolbarItem(placement: principal) {
TextField("Title", text: view.$title)
}
}
Then, inside of BookView:
#State var title: String
init(title: String) {
_title = State(initialValue: title)
}
Source: How could I initialize the #State variable in the init function in SwiftUI?

SwiftUI pass overlay content to parent views

MainView and AnotherMainView contain names arrays. They iterate over these arrays and both use the DetailView to show the name with some additional content. MyViewModel provides some functionalities such as adding a name etc. These should be triggered from a DetailOverlayView which uses the selected name for the actions (please see code comments.
My problem is, starting an overlay within the DetailView results in a very small overlay within the DetailView "cell" which is not what I want. This is why I would like to show it on the parent view. Unfortunately, I don't know how to pass the DetailOverlayView to the MainView and AnotherMainView. I tried with bindings or environment but this did not work. I would like to avoid redundancy such as defining overlays in both parent views etc.
Alternatively, maybe there is a solution to call it from the DetailView, but bypassing the detail view frame without giving some hardcoded height and width values.
struct MainView: View {
#StateObject var viewModel: MyViewModel
var names = ["aaa", "ccc", "ddd", "gght"]
var body: some View {
VStack {
ForEach(names, id: \.self, content: { name in
DetailView(name: name).environmentObject(viewModel)
})
}///.overlay(... -> need DetailOverlayView here
}
}
struct AnotherMainView: View {
#StateObject var viewModel: MyViewModel
var names = ["bbb", "hhh"]
var body: some View {
VStack {
ForEach(names, id: \.self, content: { name in
DetailView(name: name).environmentObject(viewModel)
})
}///.overlay(... -> need DetailOverlayView here
}
}
struct DetailView: View {
#EnvironmentObject var viewModel: MyViewModel
#State var name: String
var body: some View {
VStack {
Text("Name of the user: ")
Text(name)
}///.overlay(... -> this would show an DetailOverlayView as a part of this small DetailView and in its bounds which is not what I want
}
struct DetailOverlayView: View {
#Binding var name: String ///this is the same name that the DetailView has, not sure if binding makes sense here
#EnvironmentObject var viewModel: MyViewModel
var body: some View {
VStack {
Button(action: {
viewModel.addName(name: name)
}, label: {
Text("Add name")
})
Button(action: {
viewModel.doSomething1(name: name)
}, label: {
Text("doSomething1")
})
Button(action: {
viewModel.doSomething2(name: name)
}, label: {
Text("doSomething2")
})
}
}
}
}
class MyViewModel: ObservableObject {
func addName(name: String) {
///
}
func doSomething1(name: String) {
///
}
func doSomething2(name: String) {
///
}
}

Trouble passing data between views using EnvironmentObject in SwiftUI

I'm having trouble passing data through different views with EnvironmentObject. I have a file called LineupDetails that contains the following:
import SwiftUI
class TeamDetails: ObservableObject {
let characterLimit = 3
#Published var TeamName: String = "" {
didSet {
if TeamName.count > characterLimit {
TeamName = String(TeamName.prefix(characterLimit))
}
}
}
#Published var TeamColor: Color = .blue
#Published var hitter1First: String = ""
#Published var hitter1Last: String = ""
#Published var hitter2First: String = ""
#Published var hitter2Last: String = ""
#Published var hitter3First: String = ""
#Published var hitter3Last: String = ""
#Published var hitter4First: String = ""
#Published var hitter4Last: String = ""
#Published var hitter5First: String = ""
#Published var hitter5Last: String = ""
#Published var hitter6First: String = ""
#Published var hitter6Last: String = ""
#Published var hitter7First: String = ""
#Published var hitter7Last: String = ""
#Published var hitter8First: String = ""
#Published var hitter8Last: String = ""
#Published var hitter9First: String = ""
#Published var hitter9Last: String = ""
#Published var Hitter1Pos = "P"
#Published var Hitter2Pos = "C"
#Published var Hitter3Pos = "1B"
#Published var Hitter4Pos = "2B"
#Published var Hitter5Pos = "3B"
#Published var Hitter6Pos = "SS"
#Published var Hitter7Pos = "LF"
#Published var Hitter8Pos = "CF"
#Published var Hitter9Pos = "RF"
}
These variables are edited through a form in SetHomeLineup. I have excluded the parts of the view not related to the problem, marking them with ...:
struct SetHomeLineup: View {
#EnvironmentObject var HomeTeam: TeamDetails
...
var body: some View {
HStack {
TextField("Name", text: $HomeTeam.hitter1First)
TextField("Last Name", text: $HomeTeam.hitter1Last)
Picker(selection: $HomeTeam.Hitter1Pos, label: Text("")) {
ForEach(positions, id: \.self) {
Text($0)
}
}
}
HStack {
TextField("Name", text: $HomeTeam.hitter2First)
TextField("Last Name", text: $HomeTeam.hitter2Last)
Picker(selection: $HomeTeam.Hitter2Pos, label: Text("")) {
ForEach(positions, id: \.self) {
Text($0)
}
}
}
...
}
// and textfields so on until Hitter9, first and last
Now, when I try to include the inputted values of the above text fields to a different view, with code like this, the view always appears empty to match the default value of the string.
struct GameView: View {
#EnvironmentObject var HomeTeam: TeamDetails
...
var body: some View {
Text(HomeTeam.hitter1First)
}
}
Can someone tell me what I'm doing wrong? I tried using a similar code in a fresh project and it seemed to work just fine, so I'm stumped.
EDIT:
My views are instantiated like so:
The first view of the app is the SetAwayLineup, which includes a NavigationLink to SetHomeLineup like so.
var details = TeamDetails()
NavigationLink(destination: SetHomeLineup().environmentObject(details), isActive: self.$lineupIsReady) {
Similarly, SetHomeLineup includes a navigation link to GameView like so
var details = TeamDetails()
NavigationLink(destination: GameView().environmentObject(details), isActive: self.$lineupIsReady) {
Both screens have an EnvironmentObject of AwayLineup and HomeLineup that I'm trying to call into GameView.
Hopefully this simplifies it
The trunk is injected in the NavigationView and doesn't need to be re-injected. Even if one of the children doesn't use it. Truck belongs to the NavigationView
Then I have created 2 branches A and B that have their own Objects A cannot see Bs and vicecersa.
Each branch has access to their object and the sub-branches (NavigationLink) can be connected to the branch's object by injecting it.
import SwiftUI
struct TreeView: View {
#StateObject var trunk: Trunk = Trunk()
var body: some View {
NavigationView{
List{
NavigationLink(
destination: BranchAView(),
label: {
Text("BranchA")
})
NavigationLink(
destination: BranchBView(),
label: {
Text("BranchB")
})
}.navigationTitle("Trunk")
}
//Available to all items in the NavigationView
//With no need to re-inject for all items of the navView
.environmentObject(trunk)
}
}
///Has no access to BranchB
struct BranchAView: View {
#StateObject var branchA: BranchA = BranchA()
#EnvironmentObject var trunk: Trunk
var body: some View {
VStack{
Text(trunk.title)
Text(branchA.title)
NavigationLink(
destination: BranchAAView()
//Initial injection
.environmentObject(branchA)
,
label: {
Text("Go to Branch AA")
})
}.navigationTitle("BranchA")
}
}
//Has no access to BranchA
struct BranchBView: View {
#StateObject var branchB: BranchB = BranchB()
#EnvironmentObject var trunk: Trunk
var body: some View {
VStack{
Text(trunk.title)
Text(branchB.title)
NavigationLink(
destination: BranchBBView()
//Initial injection
.environmentObject(branchB),
label: {
Text("Go to Branch BB")
})
}.navigationTitle("BranchB")
}
}
struct BranchAAView: View {
#EnvironmentObject var branchA: BranchA
#EnvironmentObject var trunk: Trunk
var body: some View {
VStack{
Text(trunk.title)
Text(branchA.title)
NavigationLink(
destination: BranchAAAView()
//Needs re-injection because it is a NavigationLink sub-branch
.environmentObject(branchA)
,
label: {
Text("Go to AAA")
})
}.navigationTitle("BranchAA")
}
}
struct BranchAAAView: View {
#EnvironmentObject var branchA: BranchA
#EnvironmentObject var trunk: Trunk
var body: some View {
VStack{
Text(trunk.title)
Text(branchA.title)
}.navigationTitle("BranchAAA")
}
}
struct BranchBBView: View {
var body: some View {
VStack{
Text("I don't need to use the trunk or branch BB")
//No need to re-inject it is the same branch
BranchBBBView()
}.navigationTitle("BranchBB")
}
}
struct BranchBBBView: View {
#EnvironmentObject var branchB: BranchB
#EnvironmentObject var trunk: Trunk
var body: some View {
VStack{
Text("BranchBBBView").font(.title).fontWeight(.bold)
Text(trunk.title)
Text(branchB.title)
}.navigationTitle("BranchBB & BranchBBB")
}
}
struct TreeView_Previews: PreviewProvider {
static var previews: some View {
TreeView()
}
}
class Trunk: ObservableObject {
var title: String = "Trunk"
}
class BranchA: ObservableObject {
var title: String = "BranchA"
}
class BranchB: ObservableObject {
var title: String = "BranchB"
}
You need to make sure that you're passing the same environment object to any views that need to share the same data. That means you should create the object and store it in a variable so that you can pass it to both views. From your comments you have:
NavigationLink(destination: GameView(testUIView:
sampleGameViews[0]).environmentObject(TeamDetails()),
isActive: self.$lineupIsReady { ...
That TeamDetails() constructs a new instance of the TeamDetails class, one that you haven't stored. That means you must also be doing the same for your SetHomeLineup view. Instead, you'll need to create a single instance and keep a reference to it, then pass that reference to any views that you want to share the same data:
var details = TeamDetails()
that should be the only place where you use TeamDetails(); use details when you're setting up your GameView and SetHomeLineup views.
Update: Given your edit, the problem is again clear. You're still instantiating the TeamDetails class twice, so that the two views still get their own separate instances of that class. They need to use the same instance if they're to share information. So there should be only one var details = TeamDetails() line, and the resulting details variable should be used as the environmentObject for both views.
For example, instead of:
var details = TeamDetails()
NavigationLink(destination: SetHomeLineup().environmentObject(details), isActive: self.$lineupIsReady) {...
//...
var details = TeamDetails()
NavigationLink(destination: GameView().environmentObject(details), isActive: self.$lineupIsReady) {
you want:
var details = TeamDetails()
NavigationLink(destination: SetHomeLineup().environmentObject(details), isActive: self.$lineupIsReady) {...
NavigationLink(destination: GameView().environmentObject(details), isActive: self.$lineupIsReady) {
In general, make sure that you have a clear understanding of the difference between reference types and value types, and between a class and an instance of that class. If Mazda Miata is a class, and if I buy a Miata, then the specific car that I've is an instance of the class. If I ask you to wash my car, and I ask someone else to change the oil in my car, I'll end up with one car that's clean and has new oil. What's going on in your code is that you're buying a Miata and asking someone to wash it, and buying another car and asking someone to change the oil. They're two different cars, so they have different states.

Initialize a `#State`ful struct with a `#Binding` in SwiftUI

I have an app where two different structs depend on a separate piece of data, and when I change that data, both structs need to know about the change. I think I could solve the problem with re-factoring, or with #EnvironmentObject, but I want to understand why this code does't work. In SceneDelegate.swift in an iOS project, set the contentView with let contentView = StateTestTopView() and then create a file with this code:
import SwiftUI
struct Person { #Binding var name: String }
struct Character { #Binding var name: String }
struct StateTestTopView: View {
#State private var name: String = "Bilbo"
var body: some View {
VStack {
StateTestPickerView(name: $name)
Spacer()
Text("Global name selection is \(name)").font(.title)
}
}
}
struct StateTestPickerView: View {
#Binding var name: String
#State private var person: Person
#State private var character: Character
init(name: Binding<String>) {
self._name = name
self._person = State(initialValue: Person(name: name))
self._character = State(initialValue: Character(name: name))
}
var body: some View {
VStack{
Picker(selection: $name,
label: Text(verbatim: "Selected name: \(name)")) {
ForEach(["Sam", "Gandalf", "Gollum"], id: \.self) { name in
Text(name)
}
}
Text("You picked \(name)")
ShowPersonAndCharacter(
person: self.$person, character: self.$character)
}
}
}
struct ShowPersonAndCharacter: View {
#Binding var person: Person
#Binding var character: Character
var body: some View {
Text("\(person.name) is great! \(character.name) is the same person!")
}
}
The problem is the ShowPersonAndCharacter has #Bindings to the Person and Character structs, but the bound structs don't update when the underlying data changes. I suspect the problem is in the Picker view initializer:
#Binding var name: String
#State private var person: Person
#State private var character: Character
init(name: Binding<String>) {
self._name = name
self._person = State(initialValue: Person(name: name))
self._character = State(initialValue: Character(name: name))
}
Here I think the Person and Character structs get created with a snapshot of the bound String and don't actually get the binding. I'm not sure how to fix this, or even if my diagnosis is remotely correct.
Any ideas?
Thanks for your time!
I agree with #Asperi, Binding are intended to be used in views and not models. In fact in this case, you don't need your model to have bindings at all. Since #Binding var name: String is a Binding in StateTestPickerView it will update the view by itself as soon as it's update. See the following modification of your code. This works perfectly fine for me:
struct Person { var name: String }
struct Character { var name: String }
struct StateTestTopView: View {
#State private var name: String = "Bilbo"
var body: some View {
VStack {
StateTestPickerView(name: $name)
Spacer()
Text("Global name selection is \(name)").font(.title)
}
}
}
struct StateTestPickerView: View {
#Binding var name: String
var body: some View {
VStack{
Picker(selection: $name,
label: Text(verbatim: "Selected name: \(name)")) {
ForEach(["Sam", "Gandalf", "Gollum"], id: \.self) { name in
Text(name)
}
}
Text("You picked \(name)")
ShowPersonAndCharacter(person: Person(name: self.name),
character: Character(name: self.name))
}
}
}
struct ShowPersonAndCharacter: View {
var person: Person
var character: Character
var body: some View {
Text("\(person.name) is great! \(character.name) is the same person!")
}
}
Note that Binding property wrapper has been removed from the models. Also, StateTestPickerView directly calls the view using the name binding in following code: ShowPersonAndCharacter(person: Person(name: self.name), character: Character(name: self.name))