0
votes

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

1 Answers

1
votes

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()
    }
}