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:
- View presents a list of players, each with an instance of the stopwatch
- Swipe actions can start/stop the stopwatch for individual players
- Edit mode can be used for starting the stopwatch for multiple players
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.
GamePlayerandPlayer? Why not add the stopwatch (or whatever else you need) to the SwiftData model? They can be@Transientif you SwiftData to ignore them.gameTimein the code in the question. Please edit your question to clarify what your real code looks like.