Let’s turn our Tic Tac Toe game into an iOS app! We already have a Javascript Port which can be played on any platform, but lets make a native iOS app!

This is a perfect starter project for mobile development. It’s small, visual, and easy to test. Plus, it teaches us how to separate logic from UI—something every good app needs.

We’ll build this app using Swift and SwiftUI, which make iOS development surprisingly friendly and fun.


✅ What We’ll Build

  • A playable Tic Tac Toe game on iPhone
  • Using SwiftUI for layout and interaction
  • Keeping game logic clean and separate
  • A reset button and win detection

By the end of this post, you’ll be able to play a full game between two players on one device.


🧰 Step 1: Set Up the Project

Tools Needed:

  • Xcode (free from the Mac App Store)
  • A Mac (physical or cloud-based)

Create the App:

  1. Open Xcode → Create a new project → Choose App under iOS.
  2. Product Name: TicTacToe
  3. Interface: SwiftUI
  4. Language: Swift

Done! You’re ready to start coding.

Tic Tac Toe XCode Project


🧠 Step 2: The Game Model

Let’s write the logic that controls the board, checks for a winner, and keeps track of whose turn it is.

Create a new Swift file named GameViewModel.swift and paste this in:

import Foundation

enum Player {
    case x
    case o
}

struct Move {
    let player: Player
    let index: Int
}

class GameViewModel: ObservableObject {
    @Published var board: [Move?] = Array(repeating: nil, count: 9)
    @Published var isXTurn = true
    @Published var winner: Player?
    @Published var isGameOver = false

    func makeMove(at index: Int) {
        guard board[index] == nil, winner == nil else { return }

        board[index] = Move(player: isXTurn ? .x : .o, index: index)
        isXTurn.toggle()
        checkForWinner()
    }

    func checkForWinner() {
        let winPatterns = [
            [0, 1, 2], [3, 4, 5], [6, 7, 8], // rows
            [0, 3, 6], [1, 4, 7], [2, 5, 8], // columns
            [0, 4, 8], [2, 4, 6]             // diagonals
        ]

        for pattern in winPatterns {
            if let p1 = board[pattern[0]]?.player,
               p1 == board[pattern[1]]?.player,
               p1 == board[pattern[2]]?.player {
                winner = p1
                isGameOver = true
                return
            }
        }

        if board.allSatisfy ({ $0 != nil }) {
            winner = nil
            isGameOver = true
        }
    }

    func resetGame() {
        board = Array(repeating: nil, count: 9)
        isXTurn = true
        winner = nil
        isGameOver = false
    }
}

This keeps the rules and state out of the UI. Smart move!


🖼️ Step 3: Building the Interface

Open ContentView.swift and update it like this:

import SwiftUI

struct ContentView: View {
    @StateObject var viewModel = GameViewModel()
    let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 3)

    var body: some View {
        VStack {
            Text(viewModel.isGameOver ?
                    (viewModel.winner != nil ?
                        "Winner: \(viewModel.winner == .x ? "X" : "O")" :
                        "It's a draw!") :
                    "Turn: \(viewModel.isXTurn ? "X" : "O")")
                .font(.largeTitle)
                .padding()

            LazyVGrid(columns: columns, spacing: 15) {
                ForEach(0..<9) { index in
                    ZStack {
                        Rectangle()
                            .foregroundColor(.blue)
                            .frame(width: 100, height: 100)
                            .cornerRadius(15)

                        Text(viewModel.board[index]?.player == .x ? "X" :
                             viewModel.board[index]?.player == .o ? "O" : "")
                            .font(.system(size: 60))
                            .foregroundColor(.white)
                    }
                    .onTapGesture {
                        viewModel.makeMove(at: index)
                    }
                }
            }
            .padding()

            Button("Reset Game") {
                viewModel.resetGame()
            }
            .padding()
            .disabled(!viewModel.isGameOver)
        }
    }
}

Run this in the simulator. Play a game. See it work. Magic 🎉

Tic Tac Toe in the Simulator


🚀 What’s Next

In the next post, we’ll polish things up:

  • Animations when you make a move
  • Show win/tie message with a pop-up
  • Play sounds
  • Add a simple computer opponent?

Want to keep going? Let’s make this app feel more like a real game!