skip to Main Content

Swift newbie here.
I want to show i view above the main view whenever an boolean ObservableObject haveOnGoingCall is changed, then in this new menus, if a button click occurs it should exit the new views and only show another one containing couple buttons, which it should appear on the screen when another ObservableObject callPuttedOnHold is changed. I have few conditions in the main view to check if the value of the ObservableObject changed and show the views accordingly.

Here is a all the code to replicate it.

The main view:

import SwiftUI

struct MainView: View {
    @State var presentSideMenu = false
    @ObservedObject var newCall = ClientCallDefinitionsRepro()
                
    var body: some View {
        ZStack{
            if newCall.haveOnGoingCall && !newCall.callPuttedOnHold {
                clientCallViews()
            } else if newCall.callPuttedOnHold {
                clientCallPutOnHold()
                    .padding([.top], 600)
                    .zIndex(2)
            }
        }
    }
    
    @ViewBuilder
    private func clientCallPutOnHold() -> some View{
        CallPutOnHoldButtonsRepro()
    }
    
    @ViewBuilder
    private func clientCallViews() -> some View{
        ClientCallInfoViewRepro()
            .frame(width: 400)
            .background(.white)
            .padding([.bottom], 150)
            
        ClientCallEmergencyInfoViewRepro()
            .background(.white)
            .padding([.top], 310)
        
        ClientCallButtonsViewRepro()
            .padding([.top], 580)
    }
}

struct MainView_Previews: PreviewProvider {
    static var previews: some View {
        MainView()
    }
}

The first 3 views that are called when the haveOnGoingCall boolean is changed:

  • ClientCallInfoViewRepro
import SwiftUI

struct ClientCallInfoViewRepro: View {
    var body: some View {
        VStack(spacing: 20) {
            Text("Client")
                .frame(maxWidth: .infinity, alignment: .leading)
                .font(.system(size: 25, weight: .bold))
                .foregroundColor(.red)

                    HStack {
                        VStack(alignment: .leading, spacing: 3) {
                            Text("Client").font(.headline).bold()
                            Text("Ana Julia").font(.subheadline)
                        }
                        Spacer()
                        VStack(alignment: .leading, spacing: 3) {
                            Text("Device").font(.headline)
                            Text("DCs-DDS.FDGDF").font(.subheadline)
                        }
                    }
                }
                .padding()
    }
}

struct ClientCallInfoViewRepro_Previews: PreviewProvider {
    static var previews: some View {
        ClientCallInfoViewRepro()
    }
}
  • ClientCallEmergencyInfoViewRepro
struct ClientCallEmergencyInfoViewRepro: View {
    var body: some View {
        VStack(spacing: 20){
            Text("Emergency Info")
                .frame(maxWidth: .infinity, alignment: .leading)
                .font(.system(size: 25, weight: .bold))
                .foregroundColor(.red)
            
            HStack {
                VStack(alignment: .leading, spacing: 3) {
                    Text("Type").font(.headline).bold()
                    Text("SOS-BLE").font(.subheadline)
                }
                Spacer()
                VStack(alignment: .leading, spacing: 3) {
                    Text("Contacts").font(.headline)
                    Text("89878766767").font(.subheadline)
                }
            }
        }
        .padding()
    }
}

struct ClientCallEmergencyInfoViewRepro_Previews: PreviewProvider {
    static var previews: some View {
        ClientCallEmergencyInfoViewRepro()
    }
}
  • ClientCallButtonsViewRepro
import SwiftUI

struct ClientCallButtonsViewRepro: View {
    @ObservedObject var callOnHold = ClientCallDefinitionsRepro()

    var body: some View {
        VStack{
            HStack{
                Button (action: {
                    ChangeCallDefinitionsState()
                }) {
                    Text("Put on hold")
                        .padding([.leading], 30)
                        .padding([.trailing], 30)
                }
                .padding()
                .background(.orange)
                .foregroundStyle(.white)
                .controlSize(.large)
            }
        }
        if callOnHold.callPuttedOnHold{
            clientCallPutOnHold()
        }
    }
    
    @ViewBuilder
    private func clientCallPutOnHold() -> some View{
        CallPutOnHoldButtons()
    }

    func ChangeCallDefinitionsState(){
        DispatchQueue.main.async {
            callOnHold.callPuttedOnHold = true
            callOnHold.haveOnGoingCall = false
        }
    }
}

struct ClientCallButtonsViewRepro_Previews: PreviewProvider {
    static var previews: some View {
        ClientCallButtonsViewRepro()
    }
}

The observable object class where i store the booleans:

import Foundation

class ClientCallDefinitionsRepro: ObservableObject{
    @Published var haveOnGoingCall: Bool = true
    @Published var callPuttedOnHold: Bool = false
}
  • CallPutOnHoldButtonsRepro
import SwiftUI

struct CallPutOnHoldButtonsRepro: View {
    var body: some View {
        HStack(spacing: 50) {
            Button(action: {
                
            }) {
                VStack{
                    Image(systemName: "phone.fill")
                                .resizable()
                                .frame(width: 50, height: 50)
                                .foregroundColor(.yellow)
                    Text("Call Client")
                        .foregroundColor(.black)
                }
            }
        }
        .padding()
        .background()    }
}

struct CallPutOnHoldButtonsRepro_Previews: PreviewProvider {
    static var previews: some View {
        CallPutOnHoldButtonsRepro()
    }
}

The issue is, whenever i click the button and it changes the ObservableObject value, and the views are suppose to not appear, only the new one CallPutOnHoldButtons should appear, the new one appears in the screen, but the other ones does not close, altho the value of the ObservableObject changed correctly.

In the console i’m getting a warning: "Publishing changes from within view updates is not allowed".
Tried using DispachQueue and Task{…} but no results.
Tried these couple solutions to make it go away but didn’t seem to work. Like using the dismiss() function, using a condition in the main view where i show all the views.

How can i fix this?
If more information is needed to understand or replicate i can provide.

2

Answers


  1. It seems that the model ClientCallDefinitions is being created in both ContentViews and ClientCallButtonsView. Shouldn’t there just be one model?

    • The view that creates it should use the attribute @StateObject, not @ObservedObject.
    • Other views that observe it should use the attribute @ObservedObject.

    So you could try these changes:

    1. In ContentViews, change the attribute on newCall to @StateObject:
    // @ObservedObject var newCall = ClientCallDefinitions()
    @StateObject var newCall = ClientCallDefinitions()
    
    1. In ClientCallButtonsView, don’t create a new instance of ClientCallDefinitions:
    // @ObservedObject var callOnHold = ClientCallDefinitions()
    @ObservedObject var callOnHold: ClientCallDefinitions
    
    1. In the function clientCallViews, pass the model from ContentViews to ClientCallButtonsView:
    // ClientCallButtonsView()
    ClientCallButtonsView(callOnHold: newCall)
    
    Login or Signup to reply.
  2. I solved it by injecting the ClientCallDefinitionRepro in the topmost view using the environmentObjectModifier like so:

    struct ContentView: View {
        
        @StateObject var clientCall = ClientCallDefinitionsRepro()
      
        var body: some View {
            MainView()
                .environmentObject(clientCall)
        }
    }
    

    Then in the other views you just need to reference the ClientCallDefinitionsRepro instance like this: @EnvironmentObject var callOnHold: ClientCallDefinitionsRepro:

    struct MainView: View {
    
        @State var presentSideMenu = false
        @EnvironmentObject var newCall: ClientCallDefinitionsRepro
                    
        var body: some View {
            ZStack{
                if newCall.haveOnGoingCall && !newCall.callPuttedOnHold {
                    clientCallViews()
                } else if newCall.callPuttedOnHold {
                    clientCallPutOnHold()
                        .padding([.top], 600)
                        .zIndex(2)
                }
            }
        }
        
        @ViewBuilder
        private func clientCallPutOnHold() -> some View{
            CallPutOnHoldButtonsRepro()
        }
        
        @ViewBuilder
        private func clientCallViews() -> some View{
            /// View Code here
        }
    }
    

    This way you don’t even have to pass it down like an argument and the instance used is always the same as only one injected class of that type can exist. Your issue was that you were always creating new instances of the ClientCallDefinitionsRepro in your views, that’s why it didn’t work as expected. Here’s the result: Client call

    Note: If previews don’t work just inject the dependency in them too:

    #Preview {
        ContentView()
            .environmentObject(ClientCallDefinitionsRepro())
    }
    

    Let me know if that worked for you!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search