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
SwiftUI Published updates not refreshing
@Published
publishes value changes, so if you would replace the User object it would update. Since User is a class (Reference type) it will not update the value of GlobalState.user when User.isLoggedIn changes.
Fix for this is this package called NestedPublished which sends the nested ObservableObject.willChange to its parent.
SwiftUI ObservableObject not updating when value is Published
You are likely winding up with different UsersViewModel objects:
@ObservedObject var usersViewModel = UsersViewModel()
Since UsersView is a struct, this creates a new model every time the View is instantiated (which can happen very often, not just when the view appears). In iOS 14 there is @StateObject
to combine State (which preserves information between View instantiations) with ObservedObject, but in iOS 13 I recommend passing in the ObservedObject if it's not a shared instance.
Published property not updating in SwiftUI
for loop will block the main queue so create a queue to run for i in 1...1000000 on another thread
//
// ContentView.swift
// StackOverFlow
//
// Created by Mustafa T Mohammed on 12/31/21.
//
import SwiftUI
public class ViewSearch {
@ObservedObject var viewModel: ViewModel
/// Main initializer for instance.
/// - Parameter viewModel: The view model for searching.
public init(viewModel: ViewModel) {
self.viewModel = viewModel
}
func doSomething() {
let q = DispatchQueue.init(label: "doSomething")
// for loop will block the main queue so create a queue to run for i in 1...1000000
// on another thread
q.async { [weak self] in // weak self to prevent retain cycle
guard let self = self else { return }
for i in 1...1000000 {
if i % 250000 == 0 {
DispatchQueue.main.async { //update UI by coming back to main thread
self.viewModel.label = "value: \(i)"
}
}
}
DispatchQueue.main.async { //update UI by coming back to main thread
self.viewModel.label = "Done!"
}
}
}
}
public class ViewModel: ObservableObject {
@Published public var label = "initial value" {
didSet {
print("\(label)")
self.objectWillChange.send()
}
}
@Published public var searchText = ""
var search: ViewSearch? = nil
}
struct ContentView: View {
@ObservedObject var model: ViewModel
var body: some View {
TextField("Search", text: $model.searchText) { isEditing in
if isEditing {
model.label = "initial value"
}
} onCommit: {
if !model.searchText.isEmpty {
model.search = ViewSearch(viewModel: model)
model.search?.doSomething()
}
}
Text(model.label)
}
}
SwiftUI published variable not updating view
- You're using two different instances of
DatabaseDelegate
, one in the AppDelegate and one in the MainViewDelegate. The boolean is only updated in app delegate's instance.
Move your auth listener into your DatabaseDelegate.
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
class DatabaseDelegate: ObservableObject {
@Published var userDataLoaded = false
private var handle: AuthStateDidChangeListenerHandle?
init() {
self.handle = Auth.auth().addStateDidChangeListener { (auth, user) in
// .. etc
self.getUserInfo(...)
}
}
private func getUserInfo(UID: String, withCompletionHandler completionHandler: @escaping () -> Void) {
database.collection("Users").document(UID).getDocument { (document, error) in
// .. etc
self.userDataLoaded = true
}
}
}
- You need to use
StateObject
instead ofObservedObject
since you are initializing it internally on the view, instead of injecting it from an external source.
struct MainViewDelegate: View {
@StateObject private var databaseDelegate = DatabaseDelegate()
}
If you want to use ObservedObject, you can create it externally and inject into the view like so:
var databaseDelegate = DatabaseDelegate()
MainViewDelegate(databaseDelegate: databaseDelegate)
struct MainViewDelegate: View {
@ObservedObject var databaseDelegate: DatabaseDelegate
}
Related Topics
How to Get a Full List of Running Processes
How to Get Unsaferawpointer on The Swift Object
Using Scenekit for Hittesting Not Returning a Hit with Scnnode
Bar Button Item Tint Color Not Working
Synchronous Request Using Alamofire
Converting Local Realms to Synced Realm
How to Prevent SQL Injections with User-Search-Terms in Vapor 4 (Fluent 4)
Swift: How to Catch Exception When Parsing a Numeric String
How to Create a Custom Chain in Swift Combine
Show Status Bar Only for iPhone X
The "Funk" Sound When Hitting Escape Key in App
Remove Add Calendar Button from Calendar Chooser
How Do We Use of Nsselectorfromstring in Swift
How to Cache Cells and Also Reuse Cells in a Collectionview That Has Avplayers Embedded in Each Cell
How to Set Default Clouse Param in View Method
Is There an Firebase Realtime Database Equivalent to The Increment Method Available in Firestore