Writing my first iPhone app – Day 6 – Its all coming together
Finally managed to unpack everything after moving flats so no more boxes, yey! Unfortunately somehow managed to lose the box with my socks and underwear tho. Oh well, you win some you lose some. Anyway, had some time today to work on this app of mine.
First of all I wanted to pretty it up a little bit. I have no sense of style at all (don’t think any coder does) but I do not like working with anything too messy. So here are a couple of things I did to make me feel more comfortable:
I’ve reordered my tabs in my TabBarController to make the “Play” item to be in the center. For much of my surprise the storyboard editor doesn’t let me select the default tab. I realized that I have to extend the TabBarController. So after much research I found a code snippet that did the trick
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class TabBarController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() self.selectedIndex = 1 let barbuttonFont = UIFont(name: "Ubuntu-Light", size: 11) ?? UIFont.systemFontOfSize(11) UITabBar.appearance().tintColor = UIColor.whiteColor() UITabBarItem.appearance().setTitleTextAttributes([NSFontAttributeName: barbuttonFont, NSForegroundColorAttributeName:UIColor.whiteColor()], forState: UIControlState.Normal) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } |
I’ve also made the tab bar’s color scheme inverse so the above code takes care of the font colors as well. I also wanted to display a heading on all pages with a title so dragged in a Navigation Controller and connected it to my Tab Bar Controller. It displayed the heading placeholder but I couldn’t find a way in the storyboard editor to set the text for each tab element. Clever people of stack overflow said that i can set the title from the ViewDidAppear method of each controller. Here is an example of how:
1 2 3 4 5 |
override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) self.tabBarController?.title = "Play BillionQuestions" } |
Since I really don’t like the UIColor API (coming from web development background I think it’s understandable) I was looking for an easy to use alternative. Thats how i found UIColorExtension. Its a simple extension that allows me to use RGB(a) hex codes in my color definitions. So I can write stuff like this:
1 |
UITabBar.appearance().tintColor = UIColor(rgba: "#ffffff") |
Winning (except for the 15 minutes it took me how to do # on a mac keyboard)
Also replaced my label that was used for the countdown with a Progress View. Progress View’s are really straight forward to use:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
func subtractTime(){ self.seconds-- countDownTimerLabel.text = "\(self.seconds)s" let fractionalProgress = Float(self.seconds) / 30.0 let animated = self.seconds != 0 self.progressBar.setProgress(fractionalProgress, animated: animated) if (self.seconds == 0) { self.timer.invalidate() self.disableButtons() self.highlightCorrectAnswer() countDownTimerLabel.text = "Out of Time" nextQuestionButton.hidden = false self.logResult(-1, correct: 0) } } func startCountDown(){ self.seconds = 30 countDownTimerLabel.text = "\(self.seconds)s" countDownTimerLabel.textColor = UIColor.darkGrayColor() countDownTimerLabel.font = UIFont(name: countDownTimerLabel.font.fontName, size: 20) self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("subtractTime"), userInfo: nil, repeats: true) } |
All right. I think I’m done with prettying stuff up. It is still ugly as fuck but bit easier on the eye:
I hooked it up with the question API I made in my previous post and it worked like a charm. Except one thing: Since the API was using random sometimes it gave me the same question twice. In order to avoid that I needed to log the user’s answers. So created a new table:
1 2 3 4 5 6 7 8 9 10 11 12 |
CREATE TABLE `user_answers` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `question_id` int(11) NOT NULL, `answer` tinyint(4) NOT NULL, `correct` tinyint(4) NOT NULL, PRIMARY KEY (`id`), KEY `user_id` (`user_id`), KEY `question_id` (`question_id`), KEY `answer` (`answer`), KEY `correct` (`correct`) ) DEFAULT CHARSET=utf8 |
And a new class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class Answer extends Model { public $db = null; public $schema = array( "id" => array("type" => "int", "unique" => true, "notnull" => true, "nice_name" => "Id"), "question_id" => array( "type" => "int", "unique" => false, "required" => true, "notnull" => true, "nice_name" => "QuestionId"), "user_id" => array( "type" => "int", "required" => true, "notnull" => true, "nice_name" => "UserId"), "answer" => array( "type" => "int", "required" => true, "nice_name" => "Answer"), "correct" => array( "type" => "int", "required" => true, "nice_name" => "Correct") ); public $table = "user_answers"; public $nice_name = "Answer"; function __construct($db){ global $baseurl, $basepath; $this->db = $db; } } |
The PHP API has been modified a little as well to able to record answers + avoid sending the same question to the same user again:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public function getUserQuestions($user_id){ $questions = array(); $res = $this->answer_api->get(array("user_id" => $user_id), 0); foreach($res as $row_id => $row){ $questions[] = $row['question_id']; } return $questions; } public function answer(){ $errors = $this->answer_api->validate($this->params); if (!empty($errors)){ $this->raiseError(json_encode($errors)); } $this->answer_api->add($this->params); } public function question(){ if (!isset($this->params['user_id']) || !$this->params['user_id']){ $this->raiseError("missing param: user_id"); } $question_ids = $this->getUserQuestions($this->params['user_id']); if (empty($question_ids)){ $query = "SELECT * FROM questions ORDER BY rand() LIMIT 1"; } else { $query = "SELECT * FROM questions WHERE id NOT IN (".implode(",", $question_ids).") ORDER BY rand() LIMIT 1"; } $res = $this->db->query($query) or die($this->db->error." on line #".__LINE__); if ($res->num_rows){ $row = $res->fetch_assoc(); $ret = array(); $ret['id'] = (int) $row['id']; $ret['question'] = $row['question']; $ret['category'] = $row['category']; $ret['correct'] = (int) $row['correct_answer']; $ret['current_post'] = 100000000; // TODO $ret['answers'] = array( $row['answer1'], $row['answer2'], $row['answer3'], $row['answer4'] ); print(json_encode($ret)); } } |
Great, now the only thing left to do is to make the app send home the user’s answer for logging. In my PlayViewController I modified the logic that happens when the user clicks one of the answer buttons:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
func logResult(answer: Int, correct: Int){ var url : String = "http://billion.lepunk.co.uk/api/" var request : NSMutableURLRequest = NSMutableURLRequest() var bodyData = "method=answer&user_id=\(self.userId)&question_id=\(self.currentQuestionId)&answer=\(answer)&correct=\(correct)" println(bodyData) request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding); request.URL = NSURL(string: url) request.HTTPMethod = "POST" NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in }) } func highlightCorrectAnswer(){ switch(self.currentCorrectAnswer){ case 0: self.answer1Button.backgroundColor = UIColor(rgba: "#00aa00") case 1: self.answer2Button.backgroundColor = UIColor(rgba: "#00aa00") case 2: self.answer3Button.backgroundColor = UIColor(rgba: "#00aa00") case 3: self.answer4Button.backgroundColor = UIColor(rgba: "#00aa00") default: self.answer1Button.backgroundColor = UIColor(rgba: "#00aa00") } } @IBAction func sendAnswer(sender: UIButton) { let buttonIndex = sender.tag var correct = 0 self.timer.invalidate() self.disableButtons() if (buttonIndex == self.currentCorrectAnswer){ correct = 1 sender.backgroundColor = UIColor(rgba: "#00aa00") } else { sender.backgroundColor = UIColor(rgba: "#aa0000") self.highlightCorrectAnswer() } nextQuestionButton.hidden = false logResult(buttonIndex, correct: correct) } |
When the user hits the button it just checks against self.currentCorrectAnswer and depending on the result it sends home the questionId, userId and 0 or 1 for logging.
Finally I decided to make this project fully open source. Please feel free to fork / do a pull request / whatever, but keep in mind it is very much a work in progress and I have literally no idea what am I doing in Swift
Repo for the app | Repo for the PHP Api and scraper(s)
The game is actually playable now and it is moderately entertaining. Will need to find a new source of questions because the current ones are way too British.
What is next? The gameplay is pretty much ready now. It needs some refactoring and some security on the API but its ok for now. Next time I’m going to work on the leaderboard view. Deal with some TableViews in xcode and probably implement Facebook friend list somehow.