Tutorial details

Swifting with BaasBox | App Code for Sale | Preview

Swifting with BaasBox - Part 2 | Building a quiz app with BaasBox | iOS Tutorial

Part 2 | Building a quiz app with BaasBox

Overview PAGE TOP

In the first part of this series we have seen how to setup a project with the BaasBox SDK and how to use it in Swift. We have created the first screen of the QuizWorldCup application, which allows users to login and signup.

In this second part we are gonna build the game screen, which allows the user to play a game. Let's get started.

The Game Screen PAGE TOP

We already have a class "HomeViewController.swift". This is the view that will allow the user to start a new game, check the rankings and tweak the settings. We are going to build the first functionality: start a new game.

Delete the label in "HomeViewController.swift" and its corresponding outlet in code, we don't need it anymore. Now drag a new button, place it in the center and change its label to "Play". Next open the assistant editor and control-drag from the play button to the code in HomeViewController. Create a new IBAction and name it "playButtonTapped". Put a simple "println" statement in the newly created method just to check if it works. At this point HomeViewController.swift should look like this.

import UIKit



class HomeViewController: UIViewController {



    init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {

        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)

    }



    init(coder aDecoder: NSCoder!) {

        super.init(coder: aDecoder)

    }



    override func viewDidLoad() {

        super.viewDidLoad()

    }



    override func viewDidAppear(animated: Bool)  {

        super.viewDidAppear(animated)

        let client = BAAClient.sharedClient()

        if !client.isAuthenticated() {

            navigationController.performSegueWithIdentifier("showLogin", sender: nil)



        }



    }



    @IBAction func playButtonTapped(sender: UIButton) {

        println("play button tapped")

    }



    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

    }

}

Build and run the application, login/signup if needed, tap the play button and make sure the log statement shows up in the debug console. Once you are sure the UI is hooked up correctly let's move on to the next step: building the game view controller.

Building the Game View Controller PAGE TOP

The Game View Controller will include the following functionalities:

  • load 3 random questions from the backend
  • show each question along with its related possible answers
  • show a countdown timer
  • keep track of the score

Looks like a lot to do, so let's dive in!
The first step is to prepare the UI. Drag a new view controller on the storyboard and populate it with the following:

  • a 2-3 lines label to show the question
  • 4 buttons to show the possible answers
  • one label to show the countdown
  • one label to show the score
  • an activity indicator to show while the questions are loaded

This is the layout that we are looking for:

Gameviewcontroller
Once you have created the UI create a new class named "GameViewController.swift" and assign it to the view controller newly created on the storyboard. Now use the assistant editor to create outlets for each UI element.

At this point the "GameViewController" should look like this:

import UIKit







class GameViewController: UIViewController {



    @IBOutlet var questionLabel: UILabel

    @IBOutlet var button1: UIButton

    @IBOutlet var button2: UIButton

    @IBOutlet var button3: UIButton

    @IBOutlet var button4: UIButton

    @IBOutlet var timerLabel: UILabel

    @IBOutlet var pointsLabel: UILabel

    @IBOutlet var spinner: UIActivityIndicatorView



    init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {

        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)

    }



    override func viewDidLoad() {

        super.viewDidLoad()        

    }   

    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

    }

}

If you run the application now you'll get an error due to the lack of an initializer. This is probably an Xcode bug that's gonna be fixed in the future. For now you should add an extra initializer as follows:

init(coder aDecoder: NSCoder!) {

     super.init(coder: aDecoder)

}

Next we need to add a few variables to keep track of the game state. Insert this code below the @IBOutlet declarations:

var questions: [Question] = []

var buttons: [UIButton] = []

var timer: NSTimer?

var timerValue: Int = 0 {

        didSet {

            timerLabel.text = String(timerValue)

        }

    }



    var points: Int = 0 {

        didSet {

            pointsLabel.text = "Points: \(String(points))"

        }

    }

There are two arrays, one for the questions and one for the buttons. There's also a timer, a timerValue and an integer variable to keep track of points. Notice that for timerValue and points we are exploiting property observers, a very powerful feature of Swift. To some extend it is very similar to Objective-c key-value observing, but it requires way less code to be implemented. This allows to declare a property along with a closure, which can be executed right before or after a property value has changed. In our case we are triggering a refresh of their respective labels when timerValue or points change value.

The next step is to hide all the UI elements when the controller is created, so that we can show the spinner right away. Change the implementation of viewDidLoad as follows:

override func viewDidLoad() {

        super.viewDidLoad()

        buttons = [button1, button2, button3, button4]

        var tag = 0

        for button in buttons {

            button.setTitle("", forState: .Normal)

            button.titleLabel.lineBreakMode = .ByWordWrapping

            button.titleLabel.textAlignment = .Center

            button.backgroundColor = UIColor.lightGrayColor()

            button.addTarget(self, action: "answerTapped:", forControlEvents: .TouchUpInside)

            button.tag = tag

            button.hidden = true

            tag++

        }



        questionLabel.hidden = true

        points = 0

        spinner.hidesWhenStopped = true;

    }

This code keeps all the buttons in an array and sets a few properties like an empty title, label alignment, background color, the selector triggered when the button is tapped and the tag, to simplify the retrieval of the right question.
Finally we need to show the game view controller when the user taps play. Open the storyboard and control-click from the navigation controller on to the game view controller and pick "Show Detail" from the menu. Then select the segue and name it "showGame". Now open "HomeViewControlle.swift" and change playButtonTapped as follows.

@IBAction func playButtonTapped(sender: UIButton) {

        navigationController.performSegueWithIdentifier("showGame", sender: nil)

    }

Build the application, tap the play button and make sure the game view controller is shown. The next step is to load questions from the server. Let's see how it is easy with the BaasBox SDK.

Uploading questions to the server PAGE TOP

Once the user taps the play button the application will trigger a request to the server to fetch 3 random questions related to the world cup. A question looks like the following, in JSON format:

{



  "question": "When was the last time a footballer scored two goals in the first match of the World Cup?",

  "correctAnswerIndex": 0,

  "answers": [

    "1950",

    "1936",

    "1970",

    "1998"

  ]

}

There is a field for the actual questions, an array with possible answers and the index of the correct answer in the array. You could create questions using the BaasBox console. That would mean entering data in a form for each questions.
Populating the back end is out of the scope of this tutorial so I created a script to automatically populate the backend with a set of questions. It is a python script that can be downloaded from here.Before running it make sure the variables url_base and app_code (lines 7 and 8) are set correctly. Next you can run it by typing "python populate.py". The script will create a collection to hold questions and create a list of 28 questions with related answers. Now let's see how we can model a question in our app using the BaasBox SDK.

Modelling a question PAGE TOP

The backend is now populated with questions. Our app needs to know about the structure of a questions to load and display it. Create a new class, name it "Question" and make it a subclass of BAAObject. Then implement it as follows:

import UIKit



class Question: BAAObject {

    var question: String

    var answers: [String]    

    var correctAnswerIndex: Int



    init(dictionary: [NSObject : AnyObject]!) {

        self.question = dictionary["question"]! as String

        self.correctAnswerIndex = dictionary["correctAnswerIndex"]! as Int

        self.answers = dictionary["answers"]! as [String]

        super.init(dictionary: dictionary)

     }



    override func collectionName() -> String {

        return "document/questions"

    }

}

The class has three properties: question (the body), an array of answers and the index of the correct answer in the array. Two are the methods to implement: init and collectionName. The first parses the fields of the JSON returned by the server.

Notice that the field names correspond exactly to the field names of the JSON on the server. The second method simply returns the server endpoint from which to fetch questions. In this case the name, "questions" corresponds to the one provided in the populate script illustrated above. That's it, modelling a BaasBox object is that easy. Now let's see how to load questions from the server.

Fetching questions from the server PAGE TOP

The fetch operation will be triggered when the game view controller appears on screen. Open GameViewController.swift and add the following method.

override func viewDidAppear(animated: Bool) {

        super.viewDidAppear(animated)

        spinner.startAnimating()

        Question.getRandomObjectsWithParams(nil, bound: 3, {(questions: [AnyObject]!, error: NSError!) -> () in

            self.spinner.stopAnimating()

            if (error == nil) {

                self.questions = questions as [Question]

                self.play()

            } else {

                println("error loading questions")

            }

            })

    }

The BaasBox SDK comes with a handy method getRandomObjectsWithParams, inherited from BAAObject, which allows to fetch a set of elements from the back end. Once we get the response back we cache the result in a property and call the play() method, which is defined like this:

func play() {

        if (questions.count == 0) {

            println("round is over")

            dismissViewControllerAnimated(true, completion: nil)

            return

        }



        let question = questions[0] as Question

        questionLabel.text = question.question

        questionLabel.hidden = false

        var i = 0;

        for button in buttons {

            button.setTitle(question.answers[i], forState: .Normal)

            button.hidden = false

            i++

        }



        timerValue = 10

        timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "update", userInfo: nil, repeats: true)



    }

This method is the core of the game. It checks the elements of the questions property. When it's empty, it means the turn is over and we can dismiss the controller. While if there are still questions, it pops the first, updates the UI by setting labels and buttons and starts the countdown via a timer. The callback associated to the timer is defined as follows:

func update() {



        if (timerValue == 0) {

            println("time is up")

            timer!.invalidate()

            questions.removeAtIndex(0)

            play()

        } else {

            timerValue--

        }

    }

This method decreases the value of the timer each time it's called and, when the value is zero, it calls the play method again. Notice that, thanks to observable properties, we just need to update timerValue without writing any code to update the UI. Finally we need to implement the method associated to the tap of a button:

func answerTapped (sender:UIButton) {



        let buttonIndex = sender.tag

        let correctAnswerIndex = questions[0].correctAnswerIndex

        timer!.invalidate()

        if (buttonIndex == correctAnswerIndex) {

            println("answer is correct")

            points++

        } else {

            println("answer is wrong")

        }

        questions.removeAtIndex(0)

        play()

    }

Here we use the tag attached to the button to fetch the answer and check whether it's correct.

Here we are finally! Run the application and tap play. The app will fetch three questions from the backend and show each with a countdown. The score will update accordingly. Once the round is over the home view will be shown.

Conclusion PAGE TOP

In this second part you have learned how to create the Game view and how to populate it with data coming from the server. You have learned the power of observable properties and the simplicity of data modelling in BaasBox. In the next part you will learn how to save the scores of a user on the backend and how to build a view to show the rankings. Until then, enjoy swifting!

0 Comments Leave a comment

Please login in order to leave a comment.

Newest first
!

Sign-in to your Chupamobile Account.

The Easiest way to Launch your next App or Game.

Join Chupamobile and get instant access to thousands of ready made App and Game Templates.

Creating an account means you’re okay with Chupamobile’s Terms of Service and Privacy Policy.