UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: How do I get my View to update when my data changes

Forums > SwiftUI

Hi, I am new to swiftui, trying to make a chess app that shows a board 8x8 Rectangles. Chess position is given as a dictionary with each square as key a1-8, b1-8...h1-h8 and a letter describing the piece on that square. The dictionary needs to change as moves are made and the board view should update.

But I cannot see how to do it. Here is my view (only one line etc shwon for brevity)

import SwiftUI

struct BoardView: View {

    // Sizes
    private let squareSize: CGFloat = 65
    private let labelSize: CGFloat = 22

    // Squares on the chess board
    private var a1 = Rectangle(), a2 = Rectangle(), a3 = Rectangle(), a4 = Rectangle()
    ... others for rest of the board

    // Controller
    @ObservedObject var boardController: BoardController

    init(boardController: BoardController) {
        self.boardController = boardController
    }

    // Chess piece overlay
    @ViewBuilder func pieceOverlay(square: String) -> some View {

        if $boardController.position[square] == "K" {
            Image("white_king")
        } else if boardController.position[square] == "Q" {
            Image("white_queen")
        } else if boardController.position[square] == "R" {
            Image("white_rook")
        } else if boardController.position[square] == "B" {
            Image("white_bishop")
        } else if boardController.position[square] == "N" {
            Image("white_knight")
        } else if boardController.position[square] == "P" {
            Image("white_pawn")
        } else if boardController.position[square] == "k" {
            Image("black_king")
        } else if boardController.position[square] == "q" {
            Image("black_queen")
        } else if boardController.position[square] == "r" {
            Image("black_rook")
        } else if boardController.position[square] == "b" {
            Image("black_bishop")
        } else if boardController.position[square] == "n" {
            Image("black_knight")
        } else if boardController.position[square] == "p" {
            Image("black_pawn")
        }
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            HStack(alignment: .center, spacing: 0) {
                Text("8").bold().font(.system(size: labelSize)).frame(width: squareSize, height: squareSize)
                a8.fill(boardController.whiteColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "a8")}
                b8.fill(boardController.blackColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "b8")}
                c8.fill(boardController.whiteColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "c8")}
                d8.fill(boardController.blackColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "d8")}
                e8.fill(boardController.whiteColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "e8")}
                f8.fill(boardController.blackColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "f8")}
                g8.fill(boardController.whiteColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "g8")}
                h8.fill(boardController.blackColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "h8")}
            }
            ... more rows
            HStack(alignment: .center, spacing: 0) {
                Text("1").bold().font(.system(size: labelSize)).frame(width: squareSize, height: squareSize)
                a1.fill(boardController.blackColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "a1")}
                b1.fill(boardController.whiteColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "b1")}
                c1.fill(boardController.blackColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "c1")}
                d1.fill(boardController.whiteColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "d1")}
                e1.fill(boardController.blackColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "e1")}
                f1.fill(boardController.whiteColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "f1")}
                g1.fill(boardController.blackColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "g1")}
                h1.fill(boardController.whiteColor).frame(width: squareSize, height: squareSize).overlay{pieceOverlay(square: "h1")}
            }
        }
        .padding()
    }

}

I used a controller as various parts of UI will change the position (toolbar buttons, menuitems etc):

import Foundation
import SwiftUI

class BoardController: ObservableObject {

    // Piece position
    @Published var position: Position

    // Board colors
    @Published var whiteColor: Color = .white
    @Published var blackColor: Color = .green

    init() {
        self.position = Position(Position.startingPosition)
    }

    func clearBoard() -> Void {
        position = Position()
    }

    func setupBoard() -> Void {
        position = Position(Position.startingPosition)
    }
}

In func pieceOverlay if I use a leading $ then I get a compile error:

    if $boardController.position[square] == "K" {

"Referencing subscript 'subscript(_:)' on 'Binding' requires that 'Position' conform to 'MutableCollection'"

If I don't use the $ then code compiles but then when I update the controller's position dictionary the view does not change.

I am confused around when I need @State, @Binding, @StateModel or whether I don't need any of them. Any hwelp would be appreciated. Sorry for the length of my question

Quartz64

1      

@Quartz64 has an extraordinary request:

I am confused around when I need @State, @Binding, @StateModel
or whether I don't need any of them. Any hwelp help would be appreciated.

@twoStraws produced a high-quality twenty-three minute video explaining this. I cannot explain in a forum post what he illustrates brilliantly in a twenty-three minute video.

His video literally starts with "I am going to answer one of the most common questions people have when using SwiftUI..."

See -> Dear Quartz64...

Also, are you trying to learn on your own? I sense you are guessing at how to assemble these parts, rather than following a plan? True?

Some of your code is, uh, ummmm, rather beginner-ish. For instance, you seem to build your board using brute-force design. SwiftUI is very modular, and could help simplify your design immensely.

@twoStraws has a terrific course titled 100 Days of SwiftUI. I think you'd learn a lot from following his course. Learning lessons on sub-views, looping, encapsulation, and the like. Give it a shot! Your chess program will be much easier to code if you care to follow along.

Keep Coding

Good luck!

1      

I see you use an @ObservedObject for the board controller.

@ObservedObject var boardController: BoardController

Do you have a @StateObject for the board controller in another view that you pass to the board view? If not, you should use @StateObject for the board controller.

@StateObject var boardController: BoardController

When you use classes for your app's data, the view that owns the object should use @StateObject. If you pass that object to other views, those views should use @ObservedObject.

When you use structs for your app's data, the view that owns the object should use @State. If you pass that object to other views, those views should use @Binding.

1      

Thank you all, very helpful

   

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.