@Published property not triggering anything
Try Observing you model
@StateObject var myModel = MyModel()
Without a Minimal Reproducible Example we are going in pieces here.
You aren't observing title
and value
either. put an @Published
on them and then observe them in their own View
protocol MyProtocol:ObservableObject {
var codeDetected:CodeDetected { get set }
//var codeDetectedPublished: Published<CodeDetected> { get }
//var codeDetectedPublisher: Published<CodeDetected>.Publisher { get }
}
class CodeDetected:ObservableObject {
@Published var title: String
@Published var value:String
init(title:String = "init", value:String = "init"){
self.title = title
self.value = value
}
}
class MyModel: MyProtocol {
@Published var codeDetected:CodeDetected = CodeDetected()
//var codeDetectedPublished: Published<CodeDetected> { _codeDetected}
//var codeDetectedPublisher: Published<CodeDetected>.Publisher { $codeDetected }
}
struct CodeDetectedView: View {
@StateObject var myModel = MyModel()
var body: some View {
VStack{
//Can't see updates here unless you ask for them/tell the parent model there are changes
Text(myModel.codeDetected.title)
.onReceive(myModel.codeDetected.$title, perform: { _ in
myModel.objectWillChange.send()
})
Text(myModel.codeDetected.value)
.onReceive(myModel.codeDetected.$value, perform: { _ in
myModel.objectWillChange.send()
})
CodeDetectedSubView(codeDetected: myModel.codeDetected)
}
}
}
struct CodeDetectedSubView: View {
@ObservedObject var codeDetected: CodeDetected
var body: some View {
//Can see here
Text(codeDetected.title)
Text(codeDetected.value)
TextField("title", text: $codeDetected.title)
TextField("value", text: $codeDetected.value)
}
}
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 trigger function when model updates inside @Published property
Firstly - simplifying this code. Most of this code is unnecessary to reproduce the problem, and can't even be compiled. The code below is not the working code, but rather what we have to start and change later:
ContentView
struct ContentView: View {
var body: some View {
NavigationView {
PackagesView()
}
}
}
PackageService
class PackageService: ObservableObject {
let package: Package
init(package: Package) {
self.package = package
}
// Get placemarks from locations
func getPlacemarks() {
print("getPlacements called")
}
}
PackagesViewModel
class PackagesViewModel: ObservableObject {
@Published var results = [Package]()
}
PackagesView
struct PackagesView: View {
@StateObject var packagesViewModel = PackagesViewModel()
var body: some View {
VStack {
Button("Add new package") {
let number = packagesViewModel.results.count + 1
let new = Package(title: "title \(number)", description: "description \(number)")
packagesViewModel.results.append(new)
}
Button("Change random title") {
guard let randomIndex = packagesViewModel.results.indices.randomElement() else {
return
}
packagesViewModel.results[randomIndex].title = "new title (\(Int.random(in: 1 ... 100)))"
}
List(packagesViewModel.results, id: \.self) { package in
NavigationLink(destination: PackageView(packageService: PackageService(package: package))) {
Text(package.title)
}
}
}
}
}
PackageView
struct PackageView: View {
@ObservedObject var packageService: PackageService
var body: some View {
VStack {
Text("Title: \(packageService.package.title)")
Text("Description: \(packageService.package.description)")
}
}
}
Package
struct Package: Identifiable, Hashable {
let id = UUID()
var title: String
let description: String
}
Now, solving the problem. I fixed this issue by detecting the results
changing with onChange(of:perform:)
. However, from here there is no way to access the PackageService
s used in the view body.
To prevent this issue, the PackageService
s are actually stored in PackagesViewModel
, which logically makes more sense for the data flow. Now with PackageService
also being a struct
so the @Published
works on the array for results
, this now works.
See the code below:
PackageService (updated)
struct PackageService: Hashable {
var package: Package
init(package: Package) {
self.package = package
}
// Get placemarks from locations
mutating func getPlacemarks() {
print("getPlacements called")
// This function is mutating, feel free to set any properties in here
}
}
PackagesViewModel (updated)
class PackagesViewModel: ObservableObject {
@Published var results = [PackageService]()
}
PackagesView (updated)
struct PackagesView: View {
@StateObject var packagesViewModel = PackagesViewModel()
var body: some View {
VStack {
Button("Add new package") {
let number = packagesViewModel.results.count + 1
let new = Package(title: "title \(number)", description: "description \(number)")
packagesViewModel.results.append(PackageService(package: new))
}
Button("Change random title") {
guard let randomIndex = packagesViewModel.results.indices.randomElement() else {
return
}
let newTitle = "new title (\(Int.random(in: 1 ... 100)))"
packagesViewModel.results[randomIndex].package.title = newTitle
}
List($packagesViewModel.results, id: \.self) { $packageService in
NavigationLink(destination: PackageView(packageService: $packageService)) {
Text(packageService.package.title)
}
}
}
.onChange(of: packagesViewModel.results) { _ in
for packageService in $packagesViewModel.results {
packageService.wrappedValue.getPlacemarks()
}
}
}
}
PackageView (updated)
struct PackageView: View {
@Binding var packageService: PackageService
var body: some View {
VStack {
Text("Title: \(packageService.package.title)")
Text("Description: \(packageService.package.description)")
}
}
}
@Published property not updating view from nested view model - SwiftUI
Alternate solution is to use separated view for your sub-model:
struct FilterView: View {
@ObservedObject var filterVM: FilterViewModel
var body: some View {
Button(action: { filterVM.changeSelected()}, label: {
Text(filterVM.name)
.background(Color(filterVM.backgroundColour))
})
}
}
so in parent view now we can just use it as
struct ContentView: View {
@ObservedObject var filterListVM = FilterListViewModel()
// ...
FilterView(filterVM: filterListVM[0])
}
@Published property wrapper not working on subclass of ObservableObject
Finally figured out a solution/workaround to this issue. If you remove the property wrapper from the subclass, and call the baseclass objectWillChange.send() on the variable the state is updated properly.
NOTE: Do not redeclare let objectWillChange = PassthroughSubject<Void, Never>()
on the subclass as that will again cause the state not to update properly.
I hope this is something that will be fixed in future releases as the objectWillChange.send()
is a lot of boilerplate to maintain.
Here is a fully working example:
import SwiftUI
class MyTestObject: ObservableObject {
@Published var aString: String = ""
}
class MyInheritedObject: MyTestObject {
// Using @Published doesn't work on a subclass
// @Published var anotherString: String = ""
// If you add the following to the subclass updating the state also doesn't work properly
// let objectWillChange = PassthroughSubject<Void, Never>()
// But if you update the value you want to maintain state
// of using the objectWillChange.send() method provided by the
// baseclass the state gets updated properly... Jaayy!
var anotherString: String = "" {
willSet { self.objectWillChange.send() }
}
}
struct MyTestView: View {
@ObservedObject var myTestObject = MyTestObject()
@ObservedObject var myInheritedObject = MyInheritedObject()
var body: some View {
NavigationView {
VStack(alignment: .leading) {
TextField("Update aString", text: self.$myTestObject.aString)
Text("Value of aString is: \(self.myTestObject.aString)")
TextField("Update anotherString", text: self.$myInheritedObject.anotherString)
Text("Value of anotherString is: \(self.myInheritedObject.anotherString)")
}
}
}
}
Related Topics
How to Get All Characters of the Font with Ctfontcopycharacterset() in Swift
Contextual Member Has No Associated Value in Swift 3
Value of Optional Type 'Nsurl' Not Unwrapped; Did You Mean to Use '!' or ''
How to Create a Dynamic Link in Firebase Programmatically? Swift
Cannot Use Unarchivefromfile to Set Gamescene in Spritekit
How to Stop Tokbox Screen Sharing in Swift
Swift How to Design Uiwebview Auto Resize Full Screen in Story Board
Uicollectionviewcell Not Forcing Cell Size
Testing If Double from Textfield Has a Value or Not
Macos Playground: How Can Change the Background Color
Swift 3 - How to Insert Tableviewcells from Another View
@Published Property Not Triggering Anything
Getting a String Value from Nsorderedset Using Swiftui Foreach
How to Define an Array of a Particular Enum Case in Swift
Update All Value in One Attribute Core Data