1
votes

I’m trying to change a view based on a selection made with a button. I’m using Combine and SwiftUI.

I create a view router class with a @Published var:

import Foundation
import SwiftUI
import Combine

class ViewRouter: ObservableObject {

    @Published var currentView = "folder"

}

I have my button struct:

import SwiftUI
import Combine

struct MultiButton: View {
    @ObservedObject var viewRouter = ViewRouter()
    @State var showButton = false

    var body: some View {
        GeometryReader { geometry in
            ZStack{

                // open multi button
                Button(action: {

                    withAnimation {
                        self.showButton.toggle()
                    }
                }) {
                    Image(systemName: "gear")
                        .resizable()
                        .frame(width: 40, height: 40, alignment: .center)
                }
                if self.showButton {

                    Multi()
                    .offset(CGSize(width: -30, height: -100))
                }
            }


        }
    }
}

struct MultiButton_Previews: PreviewProvider {
    static var previews: some View {
        MultiButton()
    }
}

struct Multi: View {

 @ObservedObject var viewRouter = ViewRouter()

    var body: some View {
        HStack(spacing: 10) {
            ZStack {

                Button(action: {
                    self.viewRouter.currentView = "folder"
                     debugPrint("\(self.viewRouter.currentView)")
                }) {
                    ZStack{

                    Circle()
                        .foregroundColor(Color.blue)
                        .frame(width: 70, height: 70)
                    Image(systemName: "folder")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .padding(20)
                        .frame(width: 70, height: 70)
                        .foregroundColor(.white)
                    }.shadow(radius: 10)
                }
            }

            Button(action: {
                self.viewRouter.currentView = "setting"
                debugPrint("\(self.viewRouter.currentView)")
            }, label: {
                ZStack {
                    Circle()
                        .foregroundColor(Color.blue)
                        .frame(width: 70, height: 70)
                    Image(systemName: "gear")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .padding(20)
                        .frame(width: 70, height: 70)
                        .foregroundColor(.white)
                }.shadow(radius: 10)
            })
        }
            .transition(.scale)
    }
}

And the contentView:

import SwiftUI
import Combine

struct ContentView: View {
    @ObservedObject var viewRouter = ViewRouter()
    var body: some View {
        VStack{

            MultiButton()

            if viewRouter.currentView == "folder" {
                Text("folder")
            } else if viewRouter.currentView == "setting"{
                Text("setting")
            }
        }
    }
}

Why is my text is not changing from the folder to setting the base when the button is pressEd?

The var is @Published; it should publish the change and update the main view.

Did I miss something?

1

1 Answers

3
votes

You have multiple instances of ViewRouter with each view having its own instance. If you change that instance, the other instances are not going to change.

To solve this, you either have to pass one instance around manually:

struct MultiButton {
    @ObservedObject var viewRouter: ViewRouter
    ...
}

struct ContentView {
    @ObservedObject var viewRouter = ViewRouter()
    var body: some View {
        VStack {
            MultiButton(viewRouter: self.viewRouter)
            ...
        }
    }
}

Or you can declare it as an environment object:

struct MultiButton {
    @EnvironmentObject var viewRouter: ViewRouter
    ...
}

And then inject it for example when you create your view hierarchy in your scene delegate:

let contentView = ContentView()
    .environmentObject(ViewRouter())

Note that when injecting the environment object in the scene delegate, it will not be available in every preview of a view that uses the ViewRouter, unless you manually inject it there as well:

static var previews: some View {
    ContentView()
    .environmentObject(ViewRouter())
}