Building a Quiz App in SwiftUI

SwiftUI, Apple’s declarative UI framework, makes building intuitive and interactive user interfaces easier than ever. One exciting project to showcase SwiftUI’s capabilities is building a quiz app. This project combines UI design with logic implementation to create an engaging and educational mobile application.

What is SwiftUI?

SwiftUI is a UI toolkit that allows you to design user interfaces using a declarative syntax. Instead of defining UI elements programmatically, you describe how the UI should look and behave, and SwiftUI takes care of the rendering.

Why Build a Quiz App?

  • Enhance your understanding of SwiftUI’s core concepts.
  • Practice state management and data handling.
  • Develop an interactive and user-friendly application.

Setting Up Your SwiftUI Project

Step 1: Create a New Xcode Project

Open Xcode and select “Create a new Xcode project”. Choose “App” under the iOS tab and click “Next”.

Enter your project details:

  • Product Name: QuizApp
  • Interface: SwiftUI
  • Language: Swift

Click “Next” and choose a location to save your project.

Step 2: Define the Quiz Data Model

Create a struct to represent a question in your quiz. Each question will have a text, a list of possible answers, and the correct answer’s index.

import SwiftUI

struct Question {
    let text: String
    let answers: [String]
    let correctAnswerIndex: Int
}

Step 3: Implement the Quiz View

Create a new SwiftUI View that will display the quiz questions and handle user interactions. This view will manage the current question, the selected answer, and the user’s score.

import SwiftUI

struct QuizView: View {
    @State private var currentQuestionIndex = 0
    @State private var selectedAnswerIndex: Int? = nil
    @State private var score = 0
    @State private var showAlert = false

    let questions: [Question] = [
        Question(text: "What is the capital of France?", answers: ["Berlin", "Madrid", "Paris", "Rome"], correctAnswerIndex: 2),
        Question(text: "What is 2 + 2?", answers: ["3", "4", "5", "6"], correctAnswerIndex: 1),
        Question(text: "Which planet is known as the 'Red Planet'?", answers: ["Earth", "Mars", "Jupiter", "Venus"], correctAnswerIndex: 1)
    ]

    var currentQuestion: Question {
        questions[currentQuestionIndex]
    }

    var body: some View {
        VStack {
            Text("Question \(currentQuestionIndex + 1) / \(questions.count)")
                .font(.headline)
                .padding()

            Text(currentQuestion.text)
                .font(.title)
                .multilineTextAlignment(.center)
                .padding()

            ForEach(0.. Bool {
        return selectedAnswerIndex == currentQuestion.correctAnswerIndex
    }

    func nextQuestion() {
        selectedAnswerIndex = nil
        if currentQuestionIndex < questions.count - 1 {
            currentQuestionIndex += 1
        } else {
            currentQuestionIndex = 0
            score = 0
        }
    }
}

In this view:

  • @State variables manage the UI state: currentQuestionIndex, selectedAnswerIndex, score, and showAlert.
  • The questions array holds the quiz questions.
  • currentQuestion is a computed property that returns the question at the current index.
  • The body property defines the UI layout using a VStack to arrange the elements vertically.
  • A ForEach loop generates buttons for each possible answer.
  • The "Submit Answer" button checks the selected answer and updates the score.
  • The alert displays whether the answer was correct or incorrect.
  • The "Next Question" button advances to the next question or restarts the quiz if completed.

Step 4: Integrate QuizView in ContentView

Update the ContentView to display the QuizView.

import SwiftUI

struct ContentView: View {
    var body: some View {
        QuizView()
    }
}

Enhancements

Adding Styling

Improve the visual appeal by adding more styling to the views.

import SwiftUI

struct QuizView: View {
    @State private var currentQuestionIndex = 0
    @State private var selectedAnswerIndex: Int? = nil
    @State private var score = 0
    @State private var showAlert = false

    let questions: [Question] = [
        Question(text: "What is the capital of France?", answers: ["Berlin", "Madrid", "Paris", "Rome"], correctAnswerIndex: 2),
        Question(text: "What is 2 + 2?", answers: ["3", "4", "5", "6"], correctAnswerIndex: 1),
        Question(text: "Which planet is known as the 'Red Planet'?", answers: ["Earth", "Mars", "Jupiter", "Venus"], correctAnswerIndex: 1)
    ]

    var currentQuestion: Question {
        questions[currentQuestionIndex]
    }

    var body: some View {
        ZStack {
            LinearGradient(gradient: Gradient(colors: [.blue, .purple]), startPoint: .top, endPoint: .bottom)
                .edgesIgnoringSafeArea(.all)

            VStack {
                Text("Question \(currentQuestionIndex + 1) / \(questions.count)")
                    .font(.headline)
                    .foregroundColor(.white)
                    .padding()

                Text(currentQuestion.text)
                    .font(.title)
                    .fontWeight(.bold)
                    .foregroundColor(.white)
                    .multilineTextAlignment(.center)
                    .padding()

                ForEach(0.. Bool {
        return selectedAnswerIndex == currentQuestion.correctAnswerIndex
    }

    func nextQuestion() {
        selectedAnswerIndex = nil
        if currentQuestionIndex < questions.count - 1 {
            currentQuestionIndex += 1
        } else {
            currentQuestionIndex = 0
            score = 0
        }
    }
}

Changes include:

  • Background gradient using LinearGradient.
  • Updated text colors for better contrast.
  • Revised button styles with rounded corners and borders.

Dynamic Questions

Fetch questions from an API or a local JSON file to make the quiz dynamic.

import SwiftUI
import Combine

struct Question: Decodable {
    let text: String
    let answers: [String]
    let correctAnswerIndex: Int
}

class QuizViewModel: ObservableObject {
    @Published var questions: [Question] = []

    init() {
        loadQuestions()
    }

    func loadQuestions() {
        guard let url = Bundle.main.url(forResource: "questions", withExtension: "json") else {
            print("JSON file not found")
            return
        }

        do {
            let data = try Data(contentsOf: url)
            let decoder = JSONDecoder()
            questions = try decoder.decode([Question].self, from: data)
        } catch {
            print("Error decoding JSON: \(error)")
        }
    }
}

struct QuizView: View {
    @ObservedObject var viewModel = QuizViewModel()
    @State private var currentQuestionIndex = 0
    @State private var selectedAnswerIndex: Int? = nil
    @State private var score = 0
    @State private var showAlert = false

    var currentQuestion: Question? {
        guard currentQuestionIndex < viewModel.questions.count else {
            return nil
        }
        return viewModel.questions[currentQuestionIndex]
    }

    var body: some View {
        ZStack {
            LinearGradient(gradient: Gradient(colors: [.blue, .purple]), startPoint: .top, endPoint: .bottom)
                .edgesIgnoringSafeArea(.all)

            VStack {
                if let question = currentQuestion {
                    Text("Question \(currentQuestionIndex + 1) / \(viewModel.questions.count)")
                        .font(.headline)
                        .foregroundColor(.white)
                        .padding()

                    Text(question.text)
                        .font(.title)
                        .fontWeight(.bold)
                        .foregroundColor(.white)
                        .multilineTextAlignment(.center)
                        .padding()

                    ForEach(0.. Bool {
        return selectedAnswerIndex == viewModel.questions[currentQuestionIndex].correctAnswerIndex
    }

    func nextQuestion() {
        selectedAnswerIndex = nil
        if currentQuestionIndex < viewModel.questions.count - 1 {
            currentQuestionIndex += 1
        } else {
            currentQuestionIndex = 0
            score = 0
        }
    }
}

Conclusion

Building a quiz app in SwiftUI is a fantastic way to enhance your understanding of SwiftUI's declarative syntax, state management, and UI design. With a simple project structure and easy-to-understand code, you can create an engaging and educational mobile application. This guide covered the basic steps of creating a quiz app, including setting up the project, defining the data model, implementing the quiz view, and adding enhancements for dynamic content and styling.