An equivalent to computed properties using @Published in Swift Combine? An equivalent to computed properties using @Published in Swift Combine? ios ios

An equivalent to computed properties using @Published in Swift Combine?


You don't need to do anything for computed properties that are based on @Published properties. You can just use it like this:

class UserManager: ObservableObject {  @Published  var currentUser: User?  var userIsLoggedIn: Bool {    currentUser != nil  }}

What happens in the @Published property wrapper of currentUser is that it will call objectWillChange.send() of the ObservedObject on changes. SwiftUI views don't care about which properties of @ObservedObjects have changed, it will just recalculate the view and redraw if necessary.

Working example:

class UserManager: ObservableObject {  @Published  var currentUser: String?  var userIsLoggedIn: Bool {    currentUser != nil  }  func logOut() {    currentUser = nil  }  func logIn() {    currentUser = "Demo"  }}

And a SwiftUI demo view:

struct ContentView: View {  @ObservedObject  var userManager = UserManager()  var body: some View {    VStack( spacing: 50) {      if userManager.userIsLoggedIn {        Text( "Logged in")        Button(action: userManager.logOut) {          Text("Log out")        }      } else {        Text( "Logged out")        Button(action: userManager.logIn) {          Text("Log in")        }      }    }  }}


Create a new publisher subscribed to the property you want to track.

@Published var speed: Double = 88lazy var canTimeTravel: AnyPublisher<Bool,Never> = {    $speed        .map({ $0 >= 88 })        .eraseToAnyPublisher()}()

You will then be able to observe it much like your @Published property.

private var subscriptions = Set<AnyCancellable>()override func viewDidLoad() {    super.viewDidLoad()    sourceOfTruthObject.$canTimeTravel.sink { [weak self] (canTimeTravel) in        // Do something…    })    .store(in: &subscriptions)}

Not directly related but useful nonetheless, you can track multiple properties that way with combineLatest.

@Published var threshold: Int = 60@Published var heartData = [Int]()/** This publisher "observes" both `threshold` and `heartData` and derives a value from them. It should be updated whenever one of those values changes. */lazy var status: AnyPublisher<Status,Never> = {    $threshold       .combineLatest($heartData)       .map({ threshold, heartData in           // Computing a "status" with the two values           Status.status(heartData: heartData, threshold: threshold)       })       .receive(on: DispatchQueue.main)       .eraseToAnyPublisher()}()


How about using downstream?

lazy var userIsLoggedInPublisher: AnyPublisher = $currentUser                                          .map{$0 != nil}                                          .eraseToAnyPublisher()

In this way, the subscription will get element from upstream, then you can use sink or assign to do the didSet idea.