如何在 SwiftUI 的所有选项卡中使用相同的数据? (ObservableObject 或 EnvironmentObject)

How to use the same data across all tabs in SwiftUI? (ObservableObject or EnvironmentObject)

这是一个非常简单的应用程序示例,其中包含 2 个选项卡。在这两个选项卡上,我都有用于当前月份的简单自定义选择器。此选择器位于单独的 DateView.swift.

在每个选项卡上,我都有一个从选择器读取数据的文本(我将使用它来过滤每个选项卡上的列表)。

我想做的事情:

更改日期应该在全球范围内有效。在一个选项卡上更改月份后,我应该会在另一个选项卡上看到这个月。

如何让它发挥作用?目前我正在使用 ObservableObject。我执行错了吗? ObservableObject 只是一种方式绑定而我需要双向绑定吗?我应该改用 EnvironmentObject 吗?


这是我目前的代码:

ContentView.swift

    import SwiftUI
    
    struct ContentView: View {
        @State private var selection = 0
     
        var body: some View {
            TabView(selection: $selection){
                FirstTabView()
                    .tabItem {
                        VStack {
                            Image("first")
                            Text("First")
                        }
                    }
                    .tag(0)
                SecondTabView()
                    .tabItem {
                        VStack {
                            Image("second")
                            Text("Second")
                        }
                    }
                    .tag(1)
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }

FirstTabView.swift

import SwiftUI

struct FirstTabView: View {
    
    @ObservedObject var selectedMonth = SelectedDate()
    
    var body: some View {
        NavigationView {
            VStack {
                DateView(selectedMonth: selectedMonth)
                
                Text("\(selectedMonth.selectedMonth)")
                .padding()
            }
            .navigationBarTitle("First Tab")
        }
    }
}

struct FirstTabView_Previews: PreviewProvider {
    static var previews: some View {
        FirstTabView()
    }
}

SecondTabView.swift

import SwiftUI

struct SecondTabView: View {
    
    @ObservedObject var selectedMonth = SelectedDate()
    
    var body: some View {
        NavigationView {
            VStack {
                DateView(selectedMonth: selectedMonth)
                
                Text("\(selectedMonth.selectedMonth)")
                .padding()
            }
            .navigationBarTitle("Second Tab")
        }
    }
}

struct SecondTabView_Previews: PreviewProvider {
    static var previews: some View {
        SecondTabView()
    }
}

DateView.swift

import SwiftUI

class SelectedDate: ObservableObject {
    @Published var selectedMonth: Date = Date()
}

struct DateView: View {
    static let dateFormat: DateFormatter = {
        let formatter = DateFormatter()
        formatter.setLocalizedDateFormatFromTemplate("yyyy MMMM")
        return formatter
    }()
    
    @ObservedObject var selectedMonth = SelectedDate()
    
    var body: some View {
        
        HStack {
            
            Image(systemName: "chevron.left")
                .frame(width: 50, height: 50)
                .contentShape(Rectangle())
                .onTapGesture {
                    self.changeMonthBy(-1)
            }
            
            Spacer()

            Text("\(selectedMonth.selectedMonth, formatter: Self.dateFormat)")
            
            Spacer()
            
            Image(systemName: "chevron.right")
                .frame(width: 50, height: 50)
                .contentShape(Rectangle())
                .onTapGesture {
                    self.changeMonthBy(1)
            }
            
        }
        .padding(EdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5))
        .background(Color.yellow)
    }
    
    func changeMonthBy(_ months: Int) {
        if let selectedMonth = Calendar.current.date(byAdding: .month, value: months, to: selectedMonth.selectedMonth) {
            self.selectedMonth.selectedMonth = selectedMonth
        }
    }
}

struct DateView_Previews: PreviewProvider {
    
    struct BindingTestHolder: View {
        @State var testItem: Date = Date()
        var body: some View {
            DateView()
        }
    }
    
    static var previews: some View {
        BindingTestHolder()
    }
}

Should I use EnvironmentObject instead?

是的,如果您需要 全局,那么 EnvironmentObject 是正确的选择。

所以

struct ContentView: View {
    @State private var selection = 0
 
    // declare & create here (or use below alternates)
    var selectedMonth = SelectedDate()

    // alternates 
    //@EnvironmentObject var selectedMonth: SelectedDate
    //@StateObject var selectedMonth = SelectedDate() // << Xcode12/SwiftUI2

    var body: some View {
        TabView(selection: $selection){
            FirstTabView()
                .tabItem {
                    VStack {
                        Image("first")
                        Text("First")
                    }
                }
                .tag(0)
            SecondTabView()
                .tabItem {
                    VStack {
                        Image("second")
                        Text("Second")
                    }
                }
                .tag(1)
        } 
        // << inject here (not needed if above @EnvironmentObject is used
        .environmentObject(selectedMonth) 
    }
}

标签项中的 EnvironmentObject 如果不使用则不需要,因为它会自动通过层次结构传输

struct FirstTabView: View {
    
    var body: some View {
        NavigationView {
            VStack {
                DateView()

        // ..other code
struct SecondTabView: View {
    
    var body: some View {
        NavigationView {
            VStack {
                DateView()

        // ..other code

最后是在哪里使用

struct DateView: View {
    
    // only declared, and will be injected here automatically
    @EnvironmentObject var selectedMonth: SelectedDate

   // .. other code