lacking-cypher
lacking-cypher

Reputation: 580

SwiftUI binding bool outside view - 'Accessing State's value outside of being installed on a View'

I'm trying to separate the logic from my UI in a simple quiz like example. But I am struggling with how to navigate away when I am finished with the questions.

In Xcode 12 I get error:

Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update.

I have seen from other answers that @State can only be used in struct, and suggest published. But I can't seem to figure out how to use a published property with a binding with it not "being installed on a View"

class ViewModel: ObservableObject {
    @Published var currentQuestion: String!

    private var questions = ["one", "two", "three"]
    private var index = 0

    init() {
        currentQuestion = questions[index]
    }

    func button(title: String) -> some View {
        Button(action: { [self] in
            if index < questions.count {
                index += 1
                currentQuestion = questions[index]
            } else {
                print("go to results view")
            }
        }) {
            Text(title)
        }
    }
}

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        NavigationView {
            VStack {
                viewModel.button(title: viewModel.currentQuestion)
            }
        }
    }
}

struct ResultsView: View {
    var body: some View {
        Text("Results")
    }
}

Any suggestions on what approach to take would be appreciated!

Upvotes: 6

Views: 11029

Answers (1)

pawello2222
pawello2222

Reputation: 54436

You can try having all the logic in the ViewModel:

class ViewModel: ObservableObject {
    @Published var currentQuestion: String!
    @Published var showResults = false

    private var questions = ["one", "two", "three"]
    private var index = 0

    init() {
        currentQuestion = questions[index]
    }
    
    func nextQuestion() {
        if index < questions.count {
            index += 1
            currentQuestion = questions[index]
        } else {
            showResults = true
        }
    }
}

and UI components (eg. buttons) in the View:

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        NavigationView {
            VStack {
                Text(viewModel.currentQuestion)
                Button(action: viewModel.nextQuestion) {
                    Text("Next")
                }
            }
            // if viewModel.showResults == true -> navigate to results
            .background(NavigationLink(destination: ResultsView(), isActive: $viewModel.showResults) { EmptyView() } .hidden())
        }
    }
}

In the above example you go to the ResultsView using a NavigationLink but you can easily replace the current view with results. The presentation is up to you.

Upvotes: 6

Related Questions