I am learning swift ui and made a sudoku game for IOS, now I tried to make it for macOS. Everything worked correctly until I had to make the button in the cells that represented the numbers of the sudoku change their value when they are clicked. For some reason the button is not activated. Here is the swift ui code :
import SwiftUI
// Enumerations used to determine the results form the sudoku.
enum ResultsFromCheckedSudoku {
case notFilled
case correct
case incorrect
case notChecked
}
// Struct that will represent a number
struct Number {
public var value: String = ""
public var isGenerated: Bool = false
}
struct ContentView: View {
// Variable that will keep the sudoku.
var sudoku = Sudoku()
// Variable that will keep the numbers in the grid.
@State var numbers: [[Number]] = Array(repeating: Array(repeating: Number(), count: 9), count: 9)
// Variable that will hold the selected number.
@State var selectedNumber: String = ""
// Variable that will keep the difficulty.
@State var selectedDifficulty: Difficulties = .easy
// Variable that will keep the result from checked sudoku.
@State var resultFromCheckedSudoku: ResultsFromCheckedSudoku = .notChecked
var body: some View {
GeometryReader {geometry in
VStack {
RoundedRectangle(cornerRadius: 25.0)
.fill(Color.blue)
.frame(width: geometry.size.width / 3, height: geometry.size.height / 10)
.overlay(alignment: .center) {
Text("Sudoku")
.foregroundStyle(Color.white)
.font(Font.system(size: geometry.size.height / 20))
}
Grid(size: geometry.size.height / 2, numbers: $numbers, selectedNumber: $selectedNumber)
HStack {
ForEach(1..<10) { i in
let num: String = String(i)
Button(action: {
selectedNumber = num
}, label: {
if selectedNumber == num {
Text(num)
.font(Font.system(size: geometry.size.height / 15))
.foregroundStyle(Color.blue)
.bold()
.underline()
}
else {
Text(num)
.font(Font.system(size: geometry.size.height / 15))
.foregroundStyle(Color.blue)
}
})
.buttonStyle(.plain)
}
}
HStack {
Button(action: {
onStartButtonClicked()
}, label: {
RoundedRectangle(cornerRadius: 25.0)
.fill(Color.blue)
.overlay(alignment: .center) {
Text("Start")
.foregroundStyle(Color.white)
.font(Font.system(size: geometry.size.height / 20))
}
})
.buttonStyle(.plain)
Picker(selection: $selectedDifficulty) {
Text("Easy")
.foregroundStyle(Color.blue)
.tag(Difficulties.easy)
Text("Normal")
.foregroundStyle(Color.blue)
.tag(Difficulties.normal)
Text("Hard")
.foregroundStyle(Color.blue)
.tag(Difficulties.hard)
} label: {
EmptyView()
}
Button(action: {
onCheckButtonCLicked()
}, label: {
RoundedRectangle(cornerRadius: 25.0)
.fill(Color.blue)
.overlay(alignment: .center) {
Text("Check")
.foregroundStyle(Color.white)
.font(Font.system(size: geometry.size.height / 20))
}
})
.buttonStyle(.plain)
}
.frame(width: geometry.size.width / 2, height: geometry.size.height / 10)
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(.all, 10)
.background(Color.white)
}
}
func onStartButtonClicked() -> Void {
let grid: [[Int]] = sudoku.generateSudoku(difficulty: selectedDifficulty)
for i in 0..<9 {
for j in 0..<9 {
if grid[i][j] != 0 {
numbers[i][j].value = String(grid[i][j])
numbers[i][j].isGenerated = true
}
else {
numbers[i][j].value = ""
numbers[i][j].isGenerated = false
}
}
}
}
func onCheckButtonCLicked() -> Void {
}
}
struct Grid: View {
let size: CGFloat
@Binding var numbers: [[Number]]
@Binding var selectedNumber: String
var body: some View {
Rectangle()
.fill(Color.clear)
.frame(width: size, height: size)
.overlay(alignment: .top) {
VStack(spacing: 0) {
ForEach(0..<3) { i in
HStack(spacing: 0) {
ForEach(0..<3) { j in
Box(size: size / 3,
x: i,
y: j,
numbers: $numbers,
selectedNumber: $selectedNumber)
}
}
}
}
}
}
}
struct Box: View {
let size: CGFloat
let x: Int
let y: Int
@Binding var numbers: [[Number]]
@Binding var selectedNumber: String
var body: some View {
Rectangle()
.stroke(Color.black, lineWidth: 2)
.frame(width: size, height: size)
.overlay(alignment: .top) {
VStack(spacing: 0) {
ForEach(0..<3) { i in
HStack(spacing: 0) {
ForEach(0..<3) { j in
InnerBox(size: size / 3,
x: x * 3 + i,
y: y * 3 + j,
numbers: $numbers,
selectedNumber: $selectedNumber)
}
}
}
}
}
}
}
struct InnerBox: View {
let size: CGFloat
let x: Int
let y: Int
@Binding var numbers: [[Number]]
@Binding var selectedNumber: String
var body: some View {
Button(action: {
onButtonClicked()
}, label: {
Rectangle()
.stroke(Color.black)
.frame(width: size, height: size)
.overlay(alignment: .center) {
if numbers[x][y].isGenerated {
Text(numbers[x][y].value)
.foregroundStyle(Color.black)
.font(Font.system(size: size / 2))
.bold()
}
else {
Text(numbers[x][y].value)
.foregroundStyle(Color.black)
.font(Font.system(size: size / 2))
}
}
})
.buttonStyle(.plain)
}
func onButtonClicked() -> Void {
print("Clicked")
}
}
#Preview {
ContentView()
}
And here is the sudoku code:
import Foundation
// Enumeration that will be use to determine the difficulty.
enum Difficulties {
case easy
case normal
case hard
}
// Class that is used to generate sudoku.
class Sudoku {
// Variable that will keep the sudoku.
private var grid: [[Int]]
// Simple initialiser.
init() {
self.grid = Array(repeating: Array(repeating: 0, count: 9), count: 9)
}
// Method that use to see if a number can be placed in a cell.
private func isValid(number: Int, row: Int, col: Int) -> Bool {
for i in 0..<9 {
if grid[i][col] == number || grid[row][i] == number {
return false
}
}
let x: Int = row - row % 3
let y: Int = col - col % 3
for i in 0..<3 {
for j in 0..<3 {
if grid[x + i][y + j] == number {
return false
}
}
}
return true
}
// Method that will return the indexes of an empty cell or nil if there are't any.
private func findEmptyCell() -> [Int]? {
for i in 0..<9 {
for j in 0..<9 {
if grid[i][j] == 0 {
return [i, j]
}
}
}
return nil
}
// Method that will solve the sudoku.
private func solve() -> Bool {
if let indexes: [Int] = findEmptyCell() {
let row: Int = indexes[0]
let col: Int = indexes[1]
for number in [1, 2, 3, 4, 5, 6, 7, 8, 9].shuffled() {
if isValid(number: number, row: row, col: col) {
grid[row][col] = number
if solve() {
return true
}
grid[row][col] = 0
}
}
return false
}
else {
return true
}
}
// Method that will clear the sudoku.
private func clear() -> Void {
for i in 0..<9 {
for j in 0..<9 {
grid[i][j] = 0
}
}
}
// Method that will remove some cells from the sudoku.
private func removeCells(count: Int) -> Void {
var n: Int = 0
while n < count {
let row: Int = Int.random(in: 0..<9)
let col: Int = Int.random(in: 0..<9)
if grid[row][col] != 0 {
grid[row][col] = 0
n += 1
}
}
}
// Method that will generate and return the sudoku.
public func generateSudoku(difficulty: Difficulties) -> [[Int]] {
clear()
_ = solve()
switch difficulty {
case .easy:
removeCells(count: 40)
break
case .normal:
removeCells(count: 50)
break
case .hard:
removeCells(count: 60)
break
}
return grid
}
// Method that will check the sudoku.
public static func checkSudoku(grid: [[Int]]) -> Bool {
for i in 0..<9 {
var inRow: [Bool] = Array(repeating: false, count: 9)
var inCol: [Bool] = Array(repeating: false, count: 9)
for j in 0..<9 {
if inRow[grid[i][j] - 1] {
return false
} else {
inRow[grid[i][j] - 1] = true
}
if inCol[grid[j][i] - 1] {
return false
} else {
inCol[grid[j][i] - 1] = true
}
}
}
for i in stride(from: 0, to: 9, by: 3) {
for j in stride(from: 0, to: 9, by: 3) {
var inSubGrid: [Bool] = Array(repeating: false, count: 9)
for k in 0..<3 {
for z in 0..<3 {
if inSubGrid[grid[i + k][j + z] - 1] {
return false
} else {
inSubGrid[grid[i + k][j + z] - 1] = true
}
}
}
}
}
return true
}
}
The buttons for start or to select a numbers are working. I thought that I should put a function in the action, but that didn't work too. For some reason the buttons are not being clicked. I open the selectable menu to see how each widgets is placed and they looked correct.