As my first project with Swift, intended only for exploration, I'm doing a simple memory game. 3x3 grid of doors, when you click on a door it transforms to show the symbol behind it. After a second or two the symbol reverts to being a door. Eventually I will have it so that it waits until two doors are open so that you can see if you successfully matched, then closes both at the same time, then make it remove successfully matched doors, etc.
My current problem is getting the door to reset. I had thought I could do this by running an async function inside the Button initializer but that fails.
Here is my ContentView with the errors and issues marked in comments:
struct ContentView: View {
@Environment(ModelData.self) var modelData
var body: some View {
@Bindable var modelData = modelData
VStack {
ForEach(modelData.boardState, id: \.self) { row in
// NB: If I remove "id: \self" from the previous line
// I get "Referencing initializer 'init(_:content:)' on 'ForEach' requires that '[Item]' conform to 'Identifiable'"
// which surprises me. Shouldn't an array be
// Identifiable based on its contents?
HStack {
ForEach(row) { item in
Button { // ERROR: Cannot pass function of type '() async -> Void' to parameter expecting synchronous function type
item.revealed = !item.revealed
await resetButton(door: item)
} label: {
Text(item.revealed ? item.value : door)
.font(.system(size: 72))
}
}
}
}
}
.padding()
}
private func resetButton(door : Item) async -> Void {
sleep(1)
door.revealed = false
}
}
After staring at this for a bit I think I see the issue; the Button initializer expects that it will be able to complete immediately so that it can render immediately, which means it can't pause for an indeterminate time to do an await
. I experimented with various other options but couldn't find anything that worked.
Can someone point me in the right direction?
For the record, ModelData looks like this:
import Foundation
let door = "🚪"
extension Hashable where Self: AnyObject {
func hash(into hasher : inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}
@Observable
class Item : Identifiable, Hashable {
let id : UUID
var revealed : Bool
var value: String
init(revealed: Bool = false, value : String) {
self.id = UUID()
self.revealed = revealed
self.value = value
}
}
@Observable
class ModelData {
// Manually specifying the icon will work
var boardState : [[Item]] = [
["😀", "😴", "😲"].map { Item.init(value: $0) },
["😀", "😴", "😲"].map { Item.init(value: $0) },
["😈", "❤️🩹", "👋"].map { Item.init(value: $0) },
]
}