View does not update

Hi, I'd like to change the name of Player1 ("Marek") when I click save button on a setting view screen. On the main view screen, the new name only appears on a test Text (small yellow font), yet the change is not visible on the purple square (big white font). It seems that player name from the playerView doesn't update. However adding points (which are also displayed from the playerView) work just fine.

Here is the code: https://github.com/Voytsh/GameHelpPls

I'd extremely thankful for your help

Answered by OOPer in 681580022

Thanks for showing your code.

As far as I checked your code, the most critical parts in your code were:

                    PlayerView(Color("C1"), player: mainViewModel.player1)

                    //...

                    PlayerView(Color("C2"), player: mainViewModel.player2)

As you may already know, the parameter player: is of type PlayerModel, which is a struct -- a value type. Meaning the value is copied on assignment.

This sort of duplication may cause some unexpected behaviors in SwiftUI.


One possible solution would be using Binding:

MainView

struct MainView: View {
    
    //...

    var body: some View {

        //...

                //P1
                PlayerView(Color("C1"), player: $mainViewModel.player1) //<-
                    .padding(15)
                
                //settings&info
                midSection
                
                //P2
                PlayerView(Color("C2"), player: $mainViewModel.player2) //<-
                    .padding(15)

        //...

    }
}

PlayerView

struct PlayerView: View {
    //...
    @Binding var player: PlayerModel //<-
    
    init(_ playerBackgroundColor: Color, player: Binding<PlayerModel>) {
        self.playerBackgroundColor = playerBackgroundColor
        self._player = player //<-
    }
    
    //...
}

Please try.

Please post the relevant code directly here, don't force us to go and search in the full GitHub project.

And explain precisely your set up.

  • what are the different screens
  • where is the save buttons and what does it do ?
  • where are the labels…

PlayerView creates a frame, where points and player name are displayed. I have two instances of PlayerView in MainView which is responsible for main screen ui.

SettingsView is second screen (a sheet actually) where user will be able to change players name (I need a help with that). Right now, just for test's sake, there is a "save" button in SettingsView, which should change players name to the "new name test", and the change should be visible on the main screen, but it is not.

import SwiftUI

struct PlayerView: View {
    let hh = UIScreen.main.bounds.height
    let playerBackgroundColor: Color
    @State var player: PlayerModel 
    
    
    init(_ playerBackgroundColor: Color, player: PlayerModel) {
        self.playerBackgroundColor = playerBackgroundColor
        self.player = player
    }
    
    var body: some View {
        ZStack {
            playerBackgroundColor
                .cornerRadius(15)
            
            VStack{
                nameLabel
                pointsLabel
                setPointsLabel
                allPointsLabel
            }
            .aspectRatio(contentMode: .fit)
            .frame(maxWidth: .infinity,
                   maxHeight: .infinity,
                   alignment: .center)
            .onTapGesture {
                player.scorePoint()
            }
        }
    }
}

//MARK: Labels Extension

extension PlayerView {
    
    var nameLabel: some View {
        Text(String(player.name))
            .font(.system(size: hh/20))
            .fontWeight(.ultraLight)
    }
    
    var pointsLabel: some View {
        Text(String(player.points))
            .font(.system(size: hh/6))
            .fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/)
    }
    var setPointsLabel: some View {
        Text(String(player.setPoints))
            .font(.system(size: hh/15))
            .fontWeight(.light)
    }
    
    var allPointsLabel: some View {
        Text(String(player.allPoints))
            .font(.system(size: hh/40))
            .fontWeight(.thin)
    }
}

struct MainView: View {
    var startsTheGame: Bool = false
    @EnvironmentObject var mainViewModel: MainViewModel
    @State var showSettings: Bool = false
    
        var body: some View {
            ZStack{
                //background
                Color(#colorLiteral(red: 0.06274510175, green: 0, blue: 0.1921568662, alpha: 1))
                .edgesIgnoringSafeArea(.all)
                
                //Text content
                VStack(spacing: 0){
                    //P1
                    PlayerView(Color("C1"), player: mainViewModel.player1)
                        .padding(15)
                    
                    //settings&info
                    midSection
                    
                    //P2
                    PlayerView(Color("C2"), player: mainViewModel.player2)
                        .padding(15)
                }
                .sheet(isPresented: $showSettings, content: {
                    SettingsView(showSettings: $showSettings)
                })
                //showSettingsLayer
            }
        }
}

extension MainView {
    var midSection: some View {
        HStack{
            Button(action: {
                showSettings.toggle()
            }, label: {
                Image(systemName: "gearshape")
                    .padding(.all, 2)
                    .foregroundColor(Color(#colorLiteral(red: 1, green: 0.9843137255, blue: 0, alpha: 1)))
            })
            .padding(.horizontal)
            
            Text(String(mainViewModel.player1.name))
                .foregroundColor(Color(#colorLiteral(red: 1, green: 0.9843137255, blue: 0, alpha: 1)))
            
            Button(action: {
                //showingSettings.toggle()
            }, label: {
                Image(systemName: "info.circle")
                    .padding(.all, 2)
                    .foregroundColor(Color(#colorLiteral(red: 1, green: 0.9843137255, blue: 0, alpha: 1)))
            })
            .padding(.horizontal)
        }
    }
}

struct SettingsView: View {
    let hh = UIScreen.main.bounds.height
    @State var p1TF: String = ""
    @State var savedTF1: String = ""
    @Binding var showSettings: Bool
    @EnvironmentObject var mainViewModel: MainViewModel


    var body: some View {
        ZStack{
            //background
            Color(#colorLiteral(red: 0.06274510175, green: 0, blue: 0.1921568662, alpha: 1))
                .edgesIgnoringSafeArea(.all)
            
            Color(#colorLiteral(red: 0.8712900281, green: 0.8510302901, blue: 0.1147780493, alpha: 1))
                //.frame(maxWidth: hh*0.9, maxHeight: hh*0.9)
                .cornerRadius(25)
                .padding()
            
            VStack {
                TextField("New Player1 Name", text: $p1TF)
                    .foregroundColor(.primary)
                    .padding()
                    .background(Color.gray.opacity(0.6))
                    .cornerRadius(15)
                
                Button(action: {
                    savedTF1 = p1TF
                    //mainViewModel.player1.name = savedTF1
                    mainViewModel.newNames()
                    //mainViewModel.player2.name = savedTF1
                    
                }, label: {
                    Text("Save")
                        .padding()
                        .foregroundColor(.white)
                        .background(Color.blue)
                        .cornerRadius(15)
                })
                Text(savedTF1)
                
                Button(action: {
                    showSettings = false
                }, label: {
                    Text("Dismiss")
                        .padding()
                        .foregroundColor(.red)
                        .font(.largeTitle)
                        .background(Color.white)
                        .cornerRadius(15)
                })
            }
            .padding(50)
        }
    }
}

class MainViewModel: ObservableObject {
    @Published var player1 = PlayerModel(name: "Marek", serves: true)
    @Published var player2 = PlayerModel(name: "Marta", serves: false)
    
    func newNames() {
        player1.name = "new name test"
        player2.name = "new name test"
    }
}

struct PlayerModel {
    var name: String = "Gracz"
    var points: Int = 0
    var setPoints: Int = 0
    var allPoints: Int = 0
    var serves: Bool
    
    mutating func scorePoint() {
        points += 1
        allPoints += 1
        winsSet()
    }
    
    mutating func winsSet() {
        if points == 11 {
            setPoints += 1
            points = 0
            serves = false
        }
    }
}

Accepted Answer

Thanks for showing your code.

As far as I checked your code, the most critical parts in your code were:

                    PlayerView(Color("C1"), player: mainViewModel.player1)

                    //...

                    PlayerView(Color("C2"), player: mainViewModel.player2)

As you may already know, the parameter player: is of type PlayerModel, which is a struct -- a value type. Meaning the value is copied on assignment.

This sort of duplication may cause some unexpected behaviors in SwiftUI.


One possible solution would be using Binding:

MainView

struct MainView: View {
    
    //...

    var body: some View {

        //...

                //P1
                PlayerView(Color("C1"), player: $mainViewModel.player1) //<-
                    .padding(15)
                
                //settings&info
                midSection
                
                //P2
                PlayerView(Color("C2"), player: $mainViewModel.player2) //<-
                    .padding(15)

        //...

    }
}

PlayerView

struct PlayerView: View {
    //...
    @Binding var player: PlayerModel //<-
    
    init(_ playerBackgroundColor: Color, player: Binding<PlayerModel>) {
        self.playerBackgroundColor = playerBackgroundColor
        self._player = player //<-
    }
    
    //...
}

Please try.

View does not update
 
 
Q