简体   繁体   中英

Can @EnvironmentObject be used with a plain struct that isn't a View?

I'm having troubles with passing class information from one struct instance to another, so I've been messing trying things out.

This works because I'm passing through Views

// phone_testingApp.swift
import SwiftUI

@main
struct phone_testingApp: App {
    
    @ObservedObject var myObservedObject = MyObservedObject()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(myObservedObject)
        }
    }
}
// ContentView.swift

import SwiftUI
import Combine

struct ContentView: View {
    
    var subtract = MinusToObject()
    @EnvironmentObject var myNumber: MyObservedObject
    
    var body: some View {
        VStack{

            Text("The number is \(myNumber.theObservedObjectToPass)")
            
            MinusToObject()
                .environmentObject(myNumber)
        }
    }
}

class MyObservedObject: ObservableObject {
    @Published var theObservedObjectToPass = 5
}

struct MinusToObject: View {
    
    @EnvironmentObject var theObservedObject: MyObservedObject
    
    var body: some View {
        Text("Minus")
            .onTapGesture {
                theObservedObject.theObservedObjectToPass -= 1
            }
    }
}

but if I try something similar with just a plain struct that doesn't conform to View like this

import SwiftUI
import Combine

struct ContentView: View {
    
    var subtract = MinusToObject()
    @EnvironmentObject var myNumber: MyObservedObject
    
    var body: some View {
        VStack{

            Text("The number is \(myNumber.theObservedObjectToPass)")
            
            Text("Minus")
                .onTapGesture {
                    subtract.subtractIt()
                }
        }
    }
}

class MyObservedObject: ObservableObject {
    @Published var theObservedObjectToPass = 5
}

struct MinusToObject {
    
    @EnvironmentObject var theObservedObject: MyObservedObject
    
        
            func subtractIt() {
                theObservedObject.theObservedObjectToPass -= 1 //Thread 1: Fatal error: No ObservableObject of type MyObservedObject found. A View.environmentObject(_:) for MyObservedObject may be missing as an ancestor of this view.
            }
}

I get a runtime error thats I've put commented in at the calling of the function.

I'm quite confused as to how to pass around an instance of a reference type, so I'm sure I'm doing many things wrong, and any help would be appreciated.

That's not going to work because the EnvironmentObject injection is performed on a SwiftUI View hierarchy to all children view-based objects only.

If you want any old object to access a shared instance, you'll have to create a singleton manually and inject that into the SwiftUI view hierarchy.

class MyObservedObject: ObservableObject {
    static let shared = MyObservedObject()
    private init() { }

    @Published var theObservedObjectToPass = 5
}

/// you can use it in a plain object
struct MinusToObject {
    func subtractIt() {
        MyObservedObject.shared.theObservedObjectToPass -= 1 
    }
}

// you can use it in a view
struct ContentView: View {
    var body: some View {
        SomeOtherView()
            .environmentObject(MyObservedObject.shared)
    }
}
@ObservedObject var myObservedObject = MyObservedObject()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(myObservedObject)
        }
    }

this is enough for you to be able to get the same myObservedObject instance in any other sub view of ContentView as an environmentObject . the only exceptions are .sheet s which you'll need to pass the environemntObject to them manually for each sheet by using the same .environmentObject(envObjInstance) modifier for views inside the sheet .

for struct not conforming to the view, you can just pass the wrapped value of the instance of your environment object them and any changes to this passed instance will be reflected in your views too.

HOW:

declare a variable for your struct :

struct AnyStruct {

var anEnvironmentObject: EnvironmentObject<TheEnvironmentObjectType>.Wrapper

.
.
.
}

then in a SwiftUI view, first initialize your struct:

struct someSwiftUIView: View {

@EnvironmentObject var myEnvObj: TheEnvironmentObjectType

var anyStruct: AnyStruct { AnyStruct.init(anEnvironmentObject: $myEnvObject) } 
// notice the dollar sign `$`

}

then in your AnyStruct , you can change the values in the environmentObject and be sure that they will be reflected in your views too. you just need to remember that you need to use .wrappedValue to assign a new value to a variable that is inside your environment object. assuming your environmentObject has a variable in it named observedInteger :

extension AnyStruct {
func changeValueOfObservedIntegerToZero() {
self.anEnvironmentObject.observedInteger.wrappedValue = 0
// notice the `.wrappedValue`
}
}

note this approach has 100% worked for me, but i'm not sure if can be simpler or not. you might even not need to pass the wrapped value of your environment object to the struct, you might be able to just passed the simple value.

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