I have the following code where I expect updates to the Published property label to be reflected in the UI but it is not.
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() {
for i in 1...1000000 {
if i % 250000 == 0 {
viewModel.label = "value: \(i)"
}
}
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)
}
}
Updates are triggered on the commit of the textfield input. I expect the UI to show "value: 250000", "value: 500000", etc. The didSet observer shows the change but the UI is not updated. Why not?
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)
}
}