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

Observing model in stack of models

Forums > SwiftUI

I'm trying to learn SwiftUI by writing a simple chess browser.

I have written a model that reflects the rules of the game. Hopefully its design will be relatively clear from usage, please let me know if not!

I'm trying to write a "column view" like the one in the Finder (called NSBrowser in Cocoa).

Here's my code so far:

struct ChessPositionColumnView: View {
    var gamePosition: ChessGamePosition
    @State var selection: ChessMove?

    var moves: [ChessMove] {
        let gameState = gamePosition.gameState
        let boardState = gameState.boardState
        return gameState.allMoves().sorted {
            return boardState.description(move: $0) < boardState.description(move: $1)
        }
    }

    var body: some View {
        let boardState = gamePosition.gameState.boardState
        List {
            ForEach(moves, id: \.self) { move in
                HStack {
                    Image(boardState.imageName(move: move))
                        .resizable()
                        .frame(width: 22, height: 22)
                    Text(boardState.description(move: move))
                }
                .listRowBackground(Color(white: move == selection ? 0.9 : 1.0))
                .onTapGesture {
                    if selection == move {
                        selection = nil
                    } else {
                        selection = move
                    }
                }
            }
        }
        .overlay(Divider(), alignment: .trailing)
    }
}

struct ChessPositionBrowserView: View {
    let gamePositions: [ChessGamePosition]
    let selections: [ChessMove]

    var body: some View {
        ScrollView(.horizontal) {
            HStack {
                ForEach(0..<gamePositions.count, id: \.self) { index in
                    let selection = index < selections.count ? selections[index] : nil
                    ChessPositionColumnView(gamePosition: gamePositions[index], selection: selection)
                        .frame(width:120)
                }
            }
        }
    }
}

struct ChessPositionBrowserView_Previews: PreviewProvider {
    static var previews: some View {
        let initialPosition = ChessGamePosition.initial
        let firstMove = ChessMove.standard(fromSquare: ChessSquare(.e, .two), toSquare: ChessSquare(.e, .four))
        let secondPosition = initialPosition.applyMove(firstMove)
        ChessPositionBrowserView(gamePositions: [initialPosition, secondPosition], selections: [firstMove])
    }
}

The preview looks like this:

Screenshot 2021-06-02 at 14 37 16

The idea being that when a move is selected, the next column appears showing what moves may be made in response to that game state.

So, each ChessPositionColumnView has a ChessGamePosition (from which it derives the possible moves) and a ChessMove? (for selection state). And the ChessPositionBrowserView is basically an HStack of those columns in a ScrollView.

The browser's state is an array of ChessGamePositions (the stack of columns) and an array of ChessMoves (the selections within that). Which is to say, the browser's state properties are array versions of the column's state properties.

Everything is values rather than objects.

I have the code for setting and changing the selection in ChessPositionColumnView. What I would like is for the ChessPositionBrowserView's selection array to be effectively a proxy for the selections of its ChessPositionColumnViews. For instance, when a column halfway down the stack changes its selection, the browser should remove everything from the stack above that column and add a new column to the top of the stack showing the moves for that selection.

Could anyone please advise me on the idiomatic SwiftUI way to achieve this?

(BTW I'd be very happy to discuss generalising this browser component as well, but I don't think I'm anywhere near that level yet!)

2      

its hard to totally follow that, does the model with the information on the game moves live outside the view, I assume it does.

2      

Hi Eoin,

Thank you for your reply!

Yes, the model is outside of the view. For example in the PreviewProvider, initialPosition is set to ChessGamePosition.initial. That's a static constant corresponding to the initial setup of a chess game (FEN rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1).

Then, in the example firstMove is a standard move from e2 to e4 (King's pawn advances two squares). This move is applied (using applyMove()) to initialPosition to get secondPosition.

On either such ChessGamePosition I can call allMoves() which is how I'm getting the array of ChessMoves to display in each column.

Beyond chess, though, what I'm really trying to ask is how to model an NSBrowser-like control in SwiftUI. I'm not yet thinking idiomatically enough in SwiftUI to have a proper grasp of that.

Thanks, Hamish

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

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

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.