0

I can't find way to delete from a dynamic array that is being used in a ForEach loop. I've been looking with no luck. Many answers use List or don't have a binding in their ForEach. And I don't want to use list because it's hard to fully customize its design.

Below is a sample code that adds and remove elements from an array. This array is used to display a dynamic list of players.

Removing a player produces an index out of range after unwrapping the optional in ForEach loop.

import SwiftUI

struct GameRecapView: View {

    @State private var game = Game(players: [Player(name: "Steph"),Player(name: "Kim")])
    @State private var shape = Shape.circle

    var body: some View {
        VStack {
            ForEach(self.game.players ,id: \.id) { player in
                PlayerView(
                    player: self.$game.players[self.game.players.firstIndex(where: {$0.id == player.id})!],
                    shape: self.$shape)
            }
            Spacer()
            Button(action: {
                self.toggleShape()
            }) {
                Text("Change shape")
            }
            HStack {
                Button(action: {
                    self.addPlayer(player: Player(name: "Eddye"))
                }) {
                    Text("+")
                }
                Button(action: {
                    self.removeLastPlayer()
                }) {
                    Text("-")
                }
            }
        }
    }

    func toggleShape(){
        if self.shape == .circle {
            self.shape = .square
        } else {
            self.shape = .circle
        }
    }

    func addPlayer(player : Player) {
        self.game.players.append(player)
    }

    func removeLastPlayer(){
        self.game.players.removeLast()
    }

    func removeItems(at offsets: IndexSet) {
        self.game.players.remove(atOffsets: offsets)
    }

}

struct PlayerView: View {

    @Binding var player : Player
    @Binding var shape : Shape

    var letters = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"]

    var body: some View {

        VStack {
            ZStack{
                Text(String(self.player.name.first!.uppercased()))
                if shape == .square {
                    Rectangle().stroke().frame(width: 50, height: 50)
                } else {
                    Circle().stroke().frame(width: 50, height: 50)
                }

            }

            Button(action: {
                self.player.name = self.letters.randomElement()!
            }) {
                Text("Change Name")
            }

        }
    }
}

struct Game {
    var players : [Player]
}

struct Player : Identifiable {
    var id = UUID()
    var name : String
}

enum Shape {
    case
    circle ,
    square
}

struct GameRecapView_Previews: PreviewProvider {
    static var previews: some View {
        GameRecapView()
    }
}

1 Answer 1

1

check this out: (tested and works)

always work on the single source of truth (as apple calls it) and not on copies. make sure your changes will be done by ObservableObject.

class Data : ObservableObject {

    @Published var game = Game(players: [Player(name: "Steph"),Player(name: "Kim")])
}

struct GameRecapView: View {

    @EnvironmentObject var data : Data

    @State private var shape = Shape.circle

    var body: some View {
        VStack {
            ForEach(self.data.game.players ,id: \.id) { player in
                PlayerView(
                    player: self.data.game.players[self.data.game.players.firstIndex(where: {$0.id == player.id})!],
                    shape: self.$shape)
            }
            Spacer()
            Button(action: {
                self.toggleShape()
            }) {
                Text("Change shape")
            }
            HStack {
                Button(action: {
                    self.addPlayer(player: Player(name: "Eddye"))
                }) {
                    Text("+")
                }
                Button(action: {
                    self.removeLastPlayer()
                }) {
                    Text("-")
                }
            }
        }
    }

    func toggleShape(){
        if self.shape == .circle {
            self.shape = .square
        } else {
            self.shape = .circle
        }
    }

    func addPlayer(player : Player) {
        self.data.game.players.append(player)
    }

    func removeLastPlayer(){
        self.data.game.players.removeLast()
    }

    func removeItems(at offsets: IndexSet) {
        self.data.game.players.remove(atOffsets: offsets)
    }

}

struct PlayerView: View {

    @EnvironmentObject var data : Data

    var player : Player
    @Binding var shape : Shape

    var letters = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"]

    var body: some View {

        VStack {
            ZStack{
                Text(String(self.player.name.first!.uppercased()))
                if shape == .square {
                    Rectangle().stroke().frame(width: 50, height: 50)
                } else {
                    Circle().stroke().frame(width: 50, height: 50)
                }

            }

            Button(action: {
                let playerIndex = self.data.game.players.firstIndex(where: {$0.id == self.player.id})
                self.data.game.players[playerIndex!].name = self.letters.randomElement()!
            }) {
                Text("Change Name")
            }

        }
    }
}

struct Game {
    var players : [Player]
}

struct Player : Identifiable {
    var id = UUID()
    var name : String
}

enum Shape {
    case
    circle ,
    square
}

struct ContentView: View {

    var body : some View {
        Text("wtf")
    }
}

struct GameRecapView_Previews: PreviewProvider {
    static var previews: some View {
        GameRecapView().environmentObject(Data())
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Ok will try that (I’m away from computer or I go crazy ;) But why was my architecture not based in single source of truth ? Ohhhh was it because ForEach enumerated a copy of game?
Yes, it makes a copy
Damn 3 month into SwiftUI and it’s today that I get it ... I knew something wasn’t right ... turns out it’s just me lol

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.