I have the following view which works as I want with an array of objects and now I want to refactor it so that the array of objects is created by iterating over the results of a Swift data query.
The reason for this is because I need to access the functions in the observable stopwatch object from the Edit view. I could create the Player objects in the ForEach by passing the model and stopwatch objects to a child view, however, I am then unable to access the required functions in stopwatch object from the parent view when in Edit mode.
To summarise functionality:
struct ContentView: View {
@State private var editMode = EditMode.inactive
@State private var selectedPlayers: Set<Player.ID> = []
@State var players = [
Player(name: "Player 1", stopwatch: Stopwatch(timeElapsed: 0)),
Player(name: "Player 2", stopwatch: Stopwatch(timeElapsed: 0)),
Player(name: "Player 3", stopwatch: Stopwatch(timeElapsed: 0)),
]
var body: some View {
NavigationStack {
VStack {
List(selection: $selectedPlayers) {
ForEach(players) { player in
HStack {
Text(player.name)
Spacer()
StopwatchView(stopwatch: player.stopwatch)
.swipeActions(edge: .leading) {
Button(action: {
player.stopwatch.start()
}) {
Image(systemName: "play.circle.fill")
}
}
.tint(.green)
.swipeActions(edge: .trailing) {
Button(action: {
player.stopwatch.stop()
}) {
Image(systemName: "stop.circle.fill")
}
}
.tint(.red)
}
}
}
}
.navigationTitle("Players")
.toolbar {
if editMode.isEditing == true && !selectedPlayers.isEmpty {
Button(
action: {
selectedPlayers.forEach { playerId in
let playerInstance = players.filter {
$0.id == playerId
}
playerInstance.first?.stopwatch.start()
}
}) {
Image(systemName: "play.circle.fill")
}
}
EditButton()
}
.environment(\.editMode, $editMode)
}
}
}
Example model:
@Model
class GamePlayer: Identifiable {
var id: String
var name: String
var gameTime: Double
@Transient var timer: Stopwatch = Stopwatch()
init(id: String = UUID().uuidString, name: String, gameTime: Double = 0) {
self.id = id
self.name = name
self.gameTime = gameTime
}
}
extension GamePlayer {
static var defaults: [GamePlayer] {
[
GamePlayer(name: "Jake"),
GamePlayer(name: "Jen"),
GamePlayer(name: "Ben"),
GamePlayer(name: "Sam"),
GamePlayer(name: "Tim"),
]
}
}
In content view, I need to generate an array of Player objects for each object in the GamePlayer model, with a stopwatch.
@Environment(\.modelContext) var modelContext
@Query() var gamePlayer: [GamePlayer]
So my question is, how do I iterate over the query results to create the required array of Player objects?
It would also be good to know if I'm approaching this in the correct way. I'm only a couple of months into learning Swift so please go easy on my code.
Make the players
property into a @Query
property instead
@Query private var players: [GamePlayer]
and then update the Stopwatch for each player in onAppear
.onAppear {
players.forEach { $0.stopwatch.gameTime = $0.gameTime }
}
You could also consider making the stopwatch
property optional and assign a new instance in onAppear
instead if it doesn't make sense for a GamePlayer
object to always have one assigned.
.onAppear {
players.forEach { $0.stopwatch = Stopwatch(gameTime: $0.gameTime) }
}