简体   繁体   中英

SwiftUI @State variable does not change view

Using HalfASheet ( https://github.com/franklynw/HalfASheet ).

I have a View called ProjectsView , and in the ZStack in ProjectsView I have ProjectSorting and SortingView (both injected with the EnvironmentObject ). I want the Text () in ProjectSorting to be changed, and the HStack () in SortingView to have a checkmark, both depending on the value of the sorting variable in SortingValues . Users can change the value of the sorting by pressing the Button in SortingView .

For whatever reason, the Text () in ProjectSorting does not change at all. And the HStack () in SortingView only gets the checkmark when its ancestor stack has another Text () which includes the @State variable from the environment , which I find very weird.

What should I change? Is there any way I can make this work using @EnvironmentObject ? I'm a newbie and couldn't really understand other wrappers so I'd like to make this work within @State , @Binding , @EnvirionmentObject . Thanks in advance.

SortingValues.swift

import Combine

class SortingValues: ObservableObject {

    @Published var sorting = "Top Rated"

}

ProjectsView.swift

struct ProjectsView: View {

    @Binding var isPresented: Bool

    @State var showSortingSheet = false

    var body: some View {

        ZStack {
            NavigationView {
                VStack(spacing: 0) {
                    ProjectsTopView(isPresented: $isPresented)
                    ProjectSorting(showSortingSheet: $showSortingSheet)
                        .environmentObject(SortingValues())
                    ProjectList()
                }
                .navigationBarHidden(true)
            }

            SortingView(showSortingSheet: $showSortingSheet)
                .environmentObject(SortingValues())
        }

    }
}

ProjectSorting.swift

import SwiftUI
struct ProjectSorting: View {
    @EnvironmentObject var sortingValues: SortingValues
    @Binding var showSortingSheet: Bool
    @State var sortingValue = ""

    var body: some View {
        VStack {
            HStack {
                Text("Projects")

                Spacer()

                Button {
                    showSortingSheet.toggle()
                } label: {
                    HStack(spacing: 3) {
                        Image("sortingArrows")
                        Text(sortingValue)  // < 🟩 this is the Text I want to be changed
                    }
                }
            }

            // Another HStack goes here
        }
        .onReceive(sortingValues.$sorting) { sorting in
            print("This is ProjectSorting. sorting:", sorting)  // < this does not print when I close the half sheet
            sortingValue = sorting
        }
    }
}

SortingView.swift

import SwiftUI
import HalfASheet

struct SortingView: View {
    @EnvironmentObject var sortingValues: SortingValues

    @Binding var showSortingSheet: Bool

    @State var sortingValue = ""

    var body: some View {
        VStack {

            HalfASheet(isPresented: $showSortingSheet) {
                let sorting = ["Most Recent", "Most Reviewed", "Top Rated", "Lowest Price", "Highest Price"]

                VStack(alignment: .leading) {

                    ForEach(sorting, id: \.self) { sorting in

                            VStack(alignment: .leading, spacing: 14) {

                                Button (action: {
                                    sortingValues.sorting = sorting
                                }, label: {
                                    HStack {  // 🟦
                                        Text(sorting)
                                        Spacer()
                                        if sorting == sortingValue {  // < this is where I add the checkmark
                                            Image(systemName: "checkmark")
                                        }
                                    }
                                    .foregroundColor(.primary)
                                })

                                if sorting != "Highest Price" {
                                    Divider()
                                }
                            }

                    }

                }
            }
            .height(.fixed(325))

            // Text("Inside VStack, outside HalfASheet")  // adding this Text DOES NOT make the HStack have a checkmark
            Text("Inside VStack, outside HalfASheet: \(sortingValue)")  // 🟨 adding this Text DOES make the HStack have a checkmark
        }
        .onReceive(sortingValues.$sorting) { sorting in
            // the two printing lines below print correctly every time I tap the Button
            print("This is SortingView. sorting:", sorting)
            print("sortingValues.sorting: \(sortingValues.sorting)")
            sortingValue = sorting
        }
    }
}

Your SortingView and ProjectSorting both access an environment object of type SortingValues , but you're passing new, separate instances to each. So the change you make in one place isn't being reflected in the other, because each view is communicating with one of two completely different objects of the same type.

If you want them to interact with the same object instance, you need to declare it at a point that's above both in the object hierarchy and make sure that that single instance is passed into both. For example:

struct ProjectsView: View {
    @Binding var isPresented: Bool
    @State var showSortingSheet = false
    @StateObject var sortingValues = SortingValues()

    var body: some View {

        ZStack {
            NavigationView {
                VStack(spacing: 0) {
                    ProjectsTopView(isPresented: $isPresented)
                    ProjectSorting(showSortingSheet: $showSortingSheet)
                        .environmentObject(sortingValue)
                    ProjectList()
                }
                .navigationBarHidden(true)
            }

            SortingView(showSortingSheet: $showSortingSheet)
                .environmentObject(sortingValues)
        }
    }
}

But you can go one step further. Because environment objects and values propagate down the view hierarchy automatically, you can replace two separate .environmentObject calls with one:

struct ProjectsView: View {
    @Binding var isPresented: Bool
    @State var showSortingSheet = false
    @StateObject var sortingValues = SortingValues()

    var body: some View {

        ZStack {
            NavigationView {
                VStack(spacing: 0) {
                    ProjectsTopView(isPresented: $isPresented)
                    ProjectSorting(showSortingSheet: $showSortingSheet)
                    ProjectList()
                }
                .navigationBarHidden(true)
            }
            SortingView(showSortingSheet: $showSortingSheet)
        }
        .environmentObject(sortingValues)
    }
}

There are probably better ways of dealing with reacting to changes in your observed model rather than duplicating variable values in a local state variable -- but ensuring that all your views are using the same shared environment object should get you on your way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM