Reputation: 33
I have an app with a navigation view list that doesn't update when new elements get added later on in the app. the initial screen is fine and everything get triggered at this moment no matter how I code them, but beyond that, it stays that way. At some point I had my "init" method as an .onappear, and dynamic elements wouldn't come in, but the static ones would get added multiple times when I would go back and forth in the app, this is no longer part of my code now though.
here what my content view look like, I tried to move the navigation view part to the class that has the published var, in case it help, visually it dint change anything, dint help either.
struct ContentView: View {
@ObservedObject var diceViewList = DiceViewList()
var body: some View {
VStack{
Text("Diceimator").padding()
diceViewList.body
Text("Luck Selector")
}
}
}
and the DiceViewList class
import Foundation
import SwiftUI
class DiceViewList: ObservableObject {
@Published var list = [DiceView]()
init() {
list.append(DiceView(objectID: "Generic", name: "Generic dice set"))
list.append(DiceView(objectID: "Add", name: "Add a new dice set"))
// This insert is a simulation of what add() does with the same exact values. it does get added properly
let pos = 1
let id = 1
self.list.insert(DiceView(objectID: String(id), dice: Dice(name: String("Dice"), face: 1, amount: 1), name: "Dice"), at: pos)
}
var body: some View {
NavigationView {
List {
ForEach(self.list) { dView in
NavigationLink(destination: DiceView(objectID: dView.id, dice: dView.dice, name: dView.name)) {
HStack { Text(dView.name) }
}
}
}
}
}
func add(dice: Dice) {
let pos = list.count - 1
let id = list.count - 1
self.list.insert(DiceView(objectID: String(id), dice: dice, name: dice.name), at: pos)
}
}
I'm working on the latest Xcode 11 in case it matter
EDIT: Edited code according to suggestions, problem didnt change at all
struct ContentView: View {
@ObservedObject var vm: DiceViewList = DiceViewList()
var body: some View {
NavigationView {
List(vm.customlist) { dice in
NavigationLink(destination: DiceView(dice: dice)) {
Text(dice.name)
}
}
}
}
}
and the DiceViewList
class
class DiceViewList: ObservableObject {
@Published var customlist: [Dice] = []
func add(dice: Dice) {
self.customlist.append(dice)
}
init() {
customlist.append(Dice(objectID: "0", name: "Generic", face: 1, amount: 1))
customlist.append(Dice(objectID: "999", name: "AddDice", face: 1, amount: 1))
}
}
Upvotes: 1
Views: 2553
Reputation: 397
@NewDev's answer is correct for the specific question, but I'd also like to provide an answer for what my problem was.
I had nested @ObservableObject
classes with @Published
properties:
class InnerObject: ObservableObject {
@Published var myBool: Bool = false
}
class ContentViewModel: ObservableObject {
@Published var innerObject: InnerObject = InnerObject()
}
struct ContentView: View {
@StateObject var vm: ContentViewModel = ContentViewModel()
var body: some View {
Text(vm.innerObject.myBool ? "True" : "False")
Button("Toggle") {
vm.innerObject.myBool.toggle()
}
}
}
and whenever I—in this example—clicked the button to toggle the inner object's myBool
value, the view did not refresh.
TLDR: Nested @ObservableObject
situations like can be solved by passing the inner reference to a child view which should store it as a @ObservedObject
property:
struct ContentView: View {
@StateObject var vm: ContentViewModel = ContentViewModel()
var body: some View {
Text(vm.innerObject.myBool ? "True" : "False")
MyButtonView(innerObject: vm.innerObject)
}
}
struct MyButtonView: View {
@ObservedObject var innerObject: InnerObject
var body: some View {
Button("Toggle") {
innerObject.myBool.toggle()
}
}
}
Read more here
Upvotes: 0
Reputation: 49580
SwiftUI is a paradigm shift from how you would build a UIKit app.
The idea is to separate the data that "drives" the view - which is the View model, from the View presentation concerns.
In other words, if you had a ParentView
that shows a list of ChildView(foo:Foo)
, then the ParentView
's view model should be an array of Foo
objects - not ChildView
s:
struct Foo { var v: String }
class ParentVM: ObservableObject {
@Published let foos = [Foo("one"), Foo("two"), Foo("three")]
}
struct ParentView: View {
@ObservedObject var vm = ParentVM()
var body: some View {
List(vm.foos, id: \.self) { foo in
ChildView(foo: foo)
}
}
}
struct ChildView: View {
var foo: Foo
var body = Text("\(foo.v)")
}
So, in your case, separate the logic of adding Dice
objects from DiceViewList
(I'm taking liberties with your specific logic for brevity):
class DiceListVM: ObservableObject {
@Published var dice: [Dice] = []
func add(dice: Dice) {
dice.append(dice)
}
}
struct DiceViewList: View {
@ObservedObject var vm: DiceListVM = DiceListVM()
var body: some View {
NavigationView {
List(vm.dice) { dice in
NavigationLink(destination: DiceView(for: dice)) {
Text(dice.name)
}
}
}
}
If you need more data than what's available in Dice
, just create a DiceVM
with all the other properties, like .name
and .dice
and objectId
.
But the takeaway is: Don't store and vend out views. - only deal with the data.
Upvotes: 1
Reputation: 33
While testing stuff I realized the problem. I Assumed declaring @ObservedObject var vm: DiceViewList = DiceViewList()
in every other class and struct needing it would make them find the same object, but it doesn't! I tried to pass the observed object as an argument to my subview that contain the "add" button, and it now work as intended.
Upvotes: 0