Updating a @Published Variable Based on Changes in an Observed Variable

Updating a @Published variable based on changes in an observed variable

Working solution:

After two weeks of working with Combine I have now reworked my previous solution again (see edit history) and this is the best I could come up with now. It's still not exactly what I had in mind, because contained is not subscriber and publisher at the same time, but I think the AnyCancellable is always needed. If anyone knows a way to achieve my vision, please still let me know.

class HostViewModel: ObservableObject, Identifiable {

@Published var contained: DisplayableContent
private var containedUpdater: AnyCancellable?

init() {
self.contained = .welcome
setupPipelines()
}

private func setupPipelines() {
self.containedUpdater = AppState.shared.$isLoggedIn
.map { $0 ? DisplayableContent.mainContent : .welcome }
.assign(to: \.contained, on: self)
}

}

extension HostViewModel {

enum DisplayableContent {
case welcome
case mainContent
}

}

Observing multiple published variable changes inside ObservableObject

The simplest thing you can do is listen to objectWillChange. The catch is that it gets called before the object updates. You can use .receive(on: RunLoop.main) to get the updates on the next loop, which will reflect the changed values:

import Combine

class AppModelController: ObservableObject {

@Published var a: String = "R"
@Published var b: CGFloat = 0.0
@Published var c: CGFloat = 0.9

private var cancellable : AnyCancellable?

init() {
cancellable = self.objectWillChange
.receive(on: RunLoop.main)
.sink { newValue in
let _ = self.check()
}
}

// Call this function whenever a, b or c change
func check() -> Bool {
return true
}
}

Modify @Published variable from another class that is not declared in | SwiftUI

Your first form is absolutely fine! You may, though, consider your ContentView using a @ObservedObject instead a @StateObject.

Your second form is flawed, for several reasons:

  1. don't move logic into a view
  2. don't use class variables to keep "state".

The first statement is due to a sane design that keeps your models and views nicely separated.

The second statement is due to how SwiftUI works. If you need to have some "state" in your views, use @State where the value is a struct.

Using @State ensures, that it's value is bound to the actual life time of the "conceptual view", i.e. the thing we human perceive as the view. And this "conceptual view" (managed as some data object by SwiftUI) is distinct from the struct View, which is merely a means to describe how to create and modify this conceptual view - that is, struct view is rather a function that will be used to initially create the "conceptual view" and modify it. Once this is done, it gets destroyed, and gets recreated when the conceptual view needs to be modified. That also means, the life time of this struct is not bound to the life time of its "state". The state's life time is bound to the conceptual view, and thus has usually longer life time than the struct view, where the struct view can be created and destroyed several times.

Now, imagine what happens when you always execute let modify = Modify() whenever the (conceptual) view or its content view is modified and needs to be recalculated and rendered by creating a struct view and then - after it has been rendered - destroying it again.

Also, this "state" is considered private for the view, means it is considered an implementation detail for the view. If you want to exchange data from "outside" and "inside" use a @ObservedObject or a Binding.

SwiftUI Observed Object not updating when published value changes

The ForEach does not detect that any of cards changes because it is uses Equatable which in your case uses only id.

Here is a fix:

struct Card: Identifiable, Equatable {
var isFaceUp: Bool = false
var isMatched: Bool = false
var content: CardContent

var id: Int

static func == (lhs: MemoryGame<CardContent>.Card, rhs: MemoryGame<CardContent>.Card) -> Bool {
lhs.id == rhs.id && lhs.isFaceUp == rhs.isFaceUp // << here !!
}
}

and also needed update for

mutating func choose(_ card: Card) {
let chosenIndex = cards.firstIndex{ $0.id == card.id } // << here !!
cards[chosenIndex!].isFaceUp.toggle()
}

Tested with Xcode 13.4 / iOS 15.5

@Binding on a @Published variable changes - but does not update view unless I go out and come back to screen

@ObservedObject should be used inside SwiftUI view, so your code should rather look like the following...

class AppDelegate: NSObject, NSApplicationDelegate {

var masterkey = MasterKey()
func applicationDidFinishLaunching(_ aNotification: Notification) {
let contentView = ContentView(model: masterkey)

then in

struct ContentView: View {

@ObservedObject var model: MasterKey
@State private var board_history : [[[String]]] = [default_board]

...
Piece(name:self.board_history[self.model.current_index][x][y],

How to trigger an action as soon as a SwiftUI observed variable changes its value

Assuming that listaUtenti is @Published property you can catch its publisher as below

List(self.users.listaUtenti, id: \.self) { user in
Text(user)
}
.onReceive(self.users.$listaUtenti) { newValue in
// do this what's needed
}

SwiftUI: Observe internal changes in published object

Found a elegant way to perform this (see didSet):

final class Patient: ObservableObject {
static var shared: Patient
@Published var medicalData = MedicalData() {
didSet {
subscription = medicalData.objectWillChange.sink { [weak self] _ in
self?.objectWillChange.send()
}
}
}
var subscription: AnyCancellable?

init { ... }
}

This works out of the box if the property was initialized. If not, just write also the didSet code after initialize the medicalData propertie at 'init()`.

Of course, MedicalData must conform the ObservableObject protocol.



Related Topics



Leave a reply



Submit