0

I'm using MVVM architecture with SwiftUI.

I'm trying to refactor my code to use ForEach() in my view to display a number of subviews that all have the same basic format, a string (asking a question) and a button to press to display a sheet to enter the value.

struct EntryView: View {
    @ObservedObject var viewModel = EntryViewModel()
    var body: some View {
        VStack (spacing: 50) {
            ForEach(viewModel.questions.indices) { index in
                HStack (spacing: 50) {
                    Text(viewModel.questions[index].text)
                        .sheet(isPresented: $viewModel.questions[index].sheetIsPresented,
                                     content: {
                                        viewModel.questions[index].sheet
                                     })
                    Button(action: {
                        viewModel.questions[index].sheetIsPresented = true
                    }, label: {
                        Text("Enter")
                    })
                }
            }
        }
    }
}



class EntryViewModel: ObservableObject {
    @Published var questions: [Question] = []
    @Published var firstQuestion = Question()
    @Published var secondQuestion = Question()
    
    init() {
        questions.append(firstQuestion)
        questions.append(secondQuestion)
    }
}

class Question: ObservableObject {
    @Published var sheetIsPresented = false
    let text = "Foo"
    var sheet = AnyView(InitialView())
}

The problem is that although the button changes the correct property in the correct element at the correct index in the array, this doesn't then cause the view to update.

I'm aware that this is because when the sheetIsPresented property of the Question element of the questions Array is changed to true, it doesn't actually mutate the Array itself and so the publisher doesn't update the view.

How do I fix it please?

I'm still struggling to get my head around SwiftUI and Combine, so any pointers will be gratefully received.

Cod3rMax
  • 878
  • 3
  • 18
Kramer
  • 338
  • 2
  • 15

1 Answers1

1

You can't "chain" ObservableObjects. You have to Observe them directly.

import SwiftUI

struct EntryView: View {
    @ObservedObject var viewModel = EntryViewModel()
    var body: some View {
        VStack (spacing: 50) {
            ForEach(viewModel.questions.indices) { index in
                SingleEntryView(viewModel: viewModel.questions[index])
            }
        }
    }
}
struct SingleEntryView: View {
    @ObservedObject var viewModel: Question
    var body: some View {
        HStack (spacing: 50) {
            Text(viewModel.text)
                .sheet(isPresented: $viewModel.sheetIsPresented,
                             content: {
                                viewModel.sheet
                             })
            Button(action: {
                viewModel.sheetIsPresented = true
            }, label: {
                Text("Enter")
            })
        }
    }
}


class EntryViewModel: ObservableObject {
    @Published var questions: [Question] = []
    @Published var firstQuestion = Question()
    @Published var secondQuestion = Question()
    
    init() {
        questions.append(firstQuestion)
        questions.append(secondQuestion)
    }
}

class Question: ObservableObject {
    @Published var sheetIsPresented = false
    let text = "Foo"
    var sheet = AnyView(Text("Sheet"))
}

struct EntryView_Previews: PreviewProvider {
    static var previews: some View {
        EntryView()
    }
}
lorem ipsum
  • 21,175
  • 5
  • 24
  • 48