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
It seems that the model
ClientCallDefinitions
is being created in bothContentViews
andClientCallButtonsView
. Shouldn’t there just be one model?@StateObject
, not@ObservedObject
.@ObservedObject
.So you could try these changes:
ContentViews
, change the attribute onnewCall
to@StateObject
:ClientCallButtonsView
, don’t create a new instance ofClientCallDefinitions
:clientCallViews
, pass the model fromContentViews
toClientCallButtonsView
:I solved it by injecting the ClientCallDefinitionRepro in the topmost view using the environmentObjectModifier like so:
Then in the other views you just need to reference the ClientCallDefinitionsRepro instance like this:
@EnvironmentObject var callOnHold: ClientCallDefinitionsRepro
: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:
Note: If previews don’t work just inject the dependency in them too:
Let me know if that worked for you!