Reputation: 1958
Say I have UserSettings
EnviornmentObject
and one of it's properties is a class, the problem is when I change a value of that class, the EnviornmentObject
won't publish these changes. I understand why, but I can't seem to find a workaround.
Here is a simplified code to show the problem:
struct TestView: View {
@EnvironmentObject var settings: UserSettings
var body: some View {
ZStack {
Text("test: \(self.settings.ob.val)")
VStack {
// This is the only one that works and that makes sense, it changes the entire object
Button(action: {
self.settings.changeOb(to: testOb(val: "1"))
}) {
Text("Change object")
}
// From here on nothing works, I tried different ways to change the object value
Button(action: {
self.settings.ob.changeVal(to: "2")
}) {
Text("Change object's val")
}
Button(action: {
self.settings.changeVal(to: "3")
}) {
Text("Change object's val V2")
}
Spacer()
}
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
return ZStack {
TestView().environmentObject(UserSettings(ob: testOb("abc")))
}
}
}
class testOb: ObservableObject {
@Published private(set) var val: String
init(val: String) {
self.val = val
}
func changeVal(to: String) {
self.val = to
}
}
class UserSettings: ObservableObject {
@Published private(set) var ob: testOb
init(ob: testOb) {
self.ob = ob
}
func changeOb(ob: testOb) {
self.ob = ob
}
func changeVal(to: String) {
self.ob.val(to: to)
}
}
Upvotes: 0
Views: 203
Reputation: 699
try something like this:
class UserSettings: ObservableObject {
@Published var ob: testOb{
willSet{
observer.cancel()
}
didSet{
observer = ob.objectWillChange.sink(){self.objectWillChange.send()}
}
}
var observer: AnyCancellable!
init(ob: testOb) {
self.ob = ob
self.observer = nil
self.observer = ob.objectWillChange.sink(){self.objectWillChange.send()}
}
func sendChange(){
}
func changeOb(ob: testOb) {
self.ob = ob
}
func changeVal(to: String) {
self.ob.changeVal(to: to)
}
deinit {
observer.cancel()
}
}
@Published
is for sending notifications, not for listening.
Or you can store a weak link to parent in child object and call parent.objectWillChange.send()
in child's willSet{}
.
Upvotes: 1
Reputation: 1958
I finally was able to fix it, the problem is that Apple doesn't support nested ObesrvableObject
s along with many important features (which is sad but whatever) yet but as I heard, it's on their agenda.
The solution is that I have to manually notify everytime my object will change and this could be painful the more objects you have.
To notify we first need to import Combine
so we can use AnyCancellable
which is responsible for notifications.
Here is the updated code:
// FIRST CHANGE
import Combine
struct TestView: View {
@EnvironmentObject var settings: UserSettings
var body: some View {
ZStack {
Text("test: \(self.settings.ob.val)")
VStack {
// This is the only one that works and that makes sense, it changes the entire object
Button(action: {
self.settings.changeOb(to: testOb(val: "1"))
}) {
Text("Change object")
}
// From here on nothing works, I tried different ways to change the object value
Button(action: {
self.settings.ob.changeVal(to: "2")
}) {
Text("Change object's val")
}
Button(action: {
self.settings.changeVal(to: "3")
}) {
Text("Change object's val V2")
}
Spacer()
}
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
return ZStack {
TestView().environmentObject(UserSettings(ob: testOb("abc")))
}
}
}
class testOb: ObservableObject {
@Published private(set) var val: String
init(val: String) {
self.val = val
}
func changeVal(to: String) {
self.val = to
}
}
class UserSettings: ObservableObject {
@Published private(set) var ob: testOb
// SECOND CHANGE, this is responsible to notify on changes
var anyCancellable: AnyCancellable? = nil
init(ob: testOb) {
self.ob = ob
// THIRD CHANGE, initialize our notifier
anyCancellable = self.ob.objectWillChange.sink { (_) in
self.objectWillChange.send()
}
}
func changeOb(ob: testOb) {
self.ob = ob
}
func changeVal(to: String) {
self.ob.val(to: to)
}
}
Upvotes: 0