サーバー構築不要!スマートフォンアプリ向けの新クラウド

トップ >ドキュメント >チュートリアル(iOS):クイズアプリを作る

チュートリアル(iOS)

クイズアプリを作る

概要

ニフクラ mobile backend SDK for iOS(以下、本SDK)には、データストア機能で取り扱うオブジェクトを、さらに便利に利用するためのサブクラスを提供しています。
このチュートリアルではサブクラスを活用して、「クイズに挑戦する」「クイズを作る」の2つの機能を持ったクイズアプリを作成します。

アプリ概要

チュートリアルのサンプルコードは、以下のリンクからダウンロードできます。

画面構成とアプリの動き

以下の3つの画面から構成されます。

  • メイン画面
  • クイズに挑戦する画面
  • クイズを作成する画面
メイン画面

起動時に最初に表示される画面です。
「クイズに挑戦」「クイズを作る」を選択することができます。

メイン画面

クイズに挑戦する画面

データストアに登録済みのクイズに挑戦する画面です。
クイズは、データストアからランダムに1問選ばれて表示されます。
表示される項目は以下の通りです。

  • 問題文
  • 正答率
  • 選択肢 (選択肢はシャッフルされます)

クイズに挑戦する画面

クイズに回答するとポップアップで正解/不正解が表示されます。
不正解の場合は正しい選択肢も同時に表示されます。
「もっと解く!」をタップすると次の問題に進みます。

クイズへの挑戦を中断する場合は、画面左下の「戻る」をタップします。

正解ポップアップ
不正解ポップアップ

クイズを作る画面

新しくクイズを作成する画面です。
ここで作成されたクイズはデータストアに登録され、登録後はアプリを利用している全ての人がその問題に挑戦する可能性があります。

クイズを作る画面

問題文、正しい選択肢、ダミーの選択肢の全てを入力した後、「登録」をタップするとデータストアに登録されます。以下のポップアップが表示されれば登録成功です。「閉じる」をタップするとメイン画面に戻ります。

登録成功ポップアップ

未入力項目がある場合や、通信エラーなどで登録に失敗するとエラーとなります。

未入力項目エラーポップアップ

サンプルプロジェクトの構成

ダウンロードできるサンプルは以下のような構成になっています。

  • AppDelegate.m (.h)
  • JSONKit-master
  • Images.xcassets
  • Views
    • BaseView.m (.h)
    • MainView.m (.h)
    • ChallengeQuizView.m (.h)
    • MakeQuizView.m (.h)
  • Controllers
    • MainViewController.m (.h)
    • ChallengeQuizViewController.m (.h)
    • MakeQuizViewController.m (.h)
  • Models
    • Quiz.m (.h)

Views以下には画面の見た目に関するコードが配置されています。mobile backendに関連するコードを含まないためチュートリアル内での解説は省略します。

Controllers以下にはボタンがタップされた時や、キーボードが表示された時などイベントが発生した際の処理が記述されたコードが配置されています。ModelsとViewsを繋げる役割も担っています。

Models以下にはクイズを表現するQuizクラスのコードが配置されています。このクラスはSDKのサブクラス機能を利用しており、mobile backendのデータストアのQuizクラスを簡単に利用することができます。また、選択肢のシャッフルや、次のクイズの取得などのメソッドを追加定義しています。

解説

ニフクラ mobile backendを利用する準備

mobile backendのアカウント登録がまだの方は、こちらから無料アカウントの登録を行ってください。

クイズデータをニフクラ mobile backendのデータストアで管理するため、まずはアプリケーションからmobile backendにアクセスできるようにする必要があります。
アプリケーションキーとクライアントキーの取得、SDKやJSONKitの追加などはクイックスタートを参考にしてください。

AppDelegate.mでSDKの初期化を行ってください。

  • NCMB.frameworkのヘッダファイルをインポート
    • AppDelegate.mの冒頭に以下を記載してください
#import <NCMB/NCMB.h>

  • SDKの初期化
    • - application:didFinishLaunchingWithOptions: の中に以下を記載してください
    • アプリケーションキーとクライアントキーはご自身で取得したものに変更してください
[NCMB setApplicationKey:@"YOUR_APPLICATION_KEY" clientKey:@"YOUR_CLIENT_KEY"];

ここまでで、アプリケーションからmobile backendにアクセスすることができるようになりました。

サブクラス(Quiz)を利用する準備

このチュートリアルではmobile backendのデータストアを利用する際に、NCMBObjectを直接利用するのではなくQuizというサブクラスを登録して利用します。サブクラス化することにより、MVCデザインパターンにおけるModelのような記述が可能となり、データストア上のオブジェクトをQuizクラスのインスタンスで透過的に利用することができるようになります。

今回は以下のようなスキーマでQuizクラスを構築します。

キー 内容
question 文字列 問題文
answer 文字列 正しい選択肢
options 配列 ダミーの選択肢
true_ansusernum 数値 正答した回数
all_ansusernum 数値 挑戦した回数

キーと型に対応したプロパティを用意することで簡単にアクセスできるようになります。
この場合、ソースコードは以下の通りです。

  • Quiz.h
#import <NCMB/NCMB.h>

@interface Quiz : NCMBObject<NCMBSubclassing>

@property (strong) NSString* question;
@property (strong) NSString* answer;
@property (strong) NSMutableArray* options;
@property (strong) NSNumber* true_ansusernum;
@property (strong) NSNumber* all_ansusernum;

+ (NSString *)ncmbClassName;

@end

  • Quiz.m
#import "Quiz.h"
#import <NCMB/NCMBObject+Subclass.h>

@implementation Quiz

@dynamic question;
@dynamic answer;
@dynamic options;
@dynamic true_ansusernum;
@dynamic all_ansusernum;

+ (NSString *)ncmbClassName {
    return @"Quiz";
}

@end

QuizクラスをNCMBObjectのサブクラスとして利用するにはSDKに登録する必要があります。
AppDelegate.mに記述済みのSDKの初期化コードの前にQuizクラスの登録を追記してください。

  • AppDelegate.m
    • Quiz.hをインポートする必要があります
    • 以下のように[NCMB setApplicationKey:clientKey]の前で、registerSubclassを呼び出してください (呼び出し順を間違えると登録できません)
[Quiz registerSubclass];
[NCMB setApplicationKey:@"YOUR_APPLICATION_KEY" clientKey:@"YOUR_CLIENT_KEY"];

以上で、サブクラスを利用できるようになりました。
ここに追加でメソッドを実装していくことでオブジェクトを便利に扱うことができるようになります。サンプルコード中の追加メソッドについては後ほど説明します。

メイン画面

メイン画面は2つのボタンが配置されており、それぞれ「クイズを作る」「クイズに挑戦」に遷移します。

初期化

initメソッド内ではViewsに配置されているMainViewのインスタンスを生成しています。
MainViewはメイン画面の見た目に関する部分が実装されています。

@implementation MainViewController
{
    MainView *mainView;
}

- (id)init
{
    if (self = [super init]) {
        // ビューの生成
        mainView = [[MainView alloc] init];
    }
    return self;
}

loadViewメソッド内ではこのコントローラが扱うビューとしてinit内で生成したMainViewのインスタンスを設定しています。また、画面内の2つのボタンがタップされた際に呼び出されるメソッドの指定を行っています。

// ビューの登録と、イベントハンドリング
- (void)loadView
{
    [super loadView];

    self.view = mainView;

    // 画面内のボタンのタップイベント時に呼び出されるメソッドの登録
    [mainView.challengeQuizButton addTarget:self action:@selector(onClickChallengeQuizButton:) forControlEvents:UIControlEventTouchUpInside];
    [mainView.makeQuizButton addTarget:self action:@selector(onClickMakeQuizButton:) forControlEvents:UIControlEventTouchUpInside];
}

ボタンがタップされた際に呼び出される処理

いずれのメソッドも対応したビューコントローラのインスタンスを生成し、-presentModalViewController:animated:で画面を遷移させています。

// 「クイズに挑戦する」をタップした際に呼び出されるメソッド
- (void)onClickChallengeQuizButton:(UIButton *)button
{
    ChallengeQuizViewController *challengeQuizViewController = [[ChallengeQuizViewController alloc] init];
    challengeQuizViewController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
    [self presentModalViewController:challengeQuizViewController animated:YES];
}

// 「クイズを作る」をタップした際に呼び出されるメソッド
- (void)onClickMakeQuizButton:(UIButton *)button
{
    MakeQuizViewController *makeQuizViewController = [[MakeQuizViewController alloc] init];
    makeQuizViewController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
    [self presentModalViewController:makeQuizViewController animated:YES];
}

クイズを作る

クイズを作る処理はMakeQuizViewControllerクラスが担っています。
このクラスについて順を追って説明します。

初期化

initメソッド内では、Viewsに配置されているMakeQuizViewのインスタンスを生成しています。
MakeQuizViewはクイズを作る画面の見た目に関する部分が実装されています。

- (id)init
{
    if (self = [super init]) {
        // ビューの生成
        makeQuizView = [[MakeQuizView alloc] init];
    }
    return self;
}

loadViewメソッド内ではこのコントローラが扱うビューとしてinit内で生成したMakeQuizViewのインスタンスを設定しています。また、MakeQuizView内で実装されているボタンやテキストフィールドでイベントが発生した場合に呼び出されるメソッドの指定も行っています。例えば、登録ボタンがタップされた場合はonClickRegistButton:が呼ばれるよう登録しています。

// ビューの登録と、イベントハンドリング
- (void)loadView
{
    [super loadView];

    self.view = makeQuizView;

    // 画面内のボタンのタップイベント時に呼び出されるメソッドの登録
    [makeQuizView.registButton addTarget:self action:@selector(onClickRegistButton:) forControlEvents:UIControlEventTouchUpInside];
    [makeQuizView.closeButton addTarget:self action:@selector(onClickCloseButton:) forControlEvents:UIControlEventTouchUpInside];

    // 画面内のテキストフィールドでイベントが発生した際に受け取るデリゲートの登録
    makeQuizView.questionTextField.delegate = self;
    makeQuizView.correctAnswerTextField.delegate = self;
    makeQuizView.dummyAnswer1TextField.delegate = self;
    makeQuizView.dummyAnswer2TextField.delegate = self;
    makeQuizView.dummyAnswer3TextField.delegate = self;

    // キーボードの表示、非表示の通知を受け取った際に呼び出されるメソッドの登録
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}

イベント発生時の処理

次にそれぞれのイベント発生時に呼び出されるメソッドについて説明します。

  • 「戻る」をタップした場合の処理
// 「戻る」をタップした際に、メイン画面に戻る処理
- (void)onClickCloseButton:(UIButton *)button
{
    [self dismissModalViewControllerAnimated:YES];
}

  • キーボードの「次へ」ボタンなどをタップした場合の処理
    • 次のテキストフィールドにフォーカスが移動します
    • 最後のテキストフィールドの場合はキーボードを閉じます
// キーボードの「Next」をタップした際に次のテキストフィールドに移動するための処理
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    switch (textField.tag) {
        case TEXT_FIELD_QUESTION:
            [makeQuizView.correctAnswerTextField becomeFirstResponder];
            break;
        case TEXT_FIELD_CORRECT_ANSWER:
            [makeQuizView.dummyAnswer1TextField becomeFirstResponder];
            break;
        case TEXT_FIELD_DUMMY_ANSWER_1:
            [makeQuizView.dummyAnswer2TextField becomeFirstResponder];
            break;
        case TEXT_FIELD_DUMMY_ANSWER_2:
            [makeQuizView.dummyAnswer3TextField becomeFirstResponder];
            break;
        default:
            [makeQuizView.dummyAnswer3TextField resignFirstResponder];
            break;
    }
    return NO;
}

  • キーボードの表示/非表示が切り替わった場合の処理
    • 入力フィールドなどのコンテンツ部分はスクロールビューになっているので、キーボードの高さに合わせてスクロールビューの高さを調整します
- (void)keyboardDidShow:(NSNotification *)notification
{
    CGRect keyboardFrame = [[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    CGRect destination = CGRectMake(0, 0, makeQuizView.frame.size.width, makeQuizView.frame.size.height - keyboardFrame.size.height);
    [makeQuizView resizeScrollViewWithDuration:0.7
                                       options:[[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] doubleValue]
                                   destination:destination];
}

- (void)keyboardWillHide:(NSNotification *)notification
{
    CGRect destination = CGRectMake(0, 0, makeQuizView.frame.size.width, makeQuizView.frame.size.height);
    [makeQuizView resizeScrollViewWithDuration:0.25
                                       options:[[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] doubleValue]
                                   destination:destination];
}

  • 「登録」をタップした場合の処理
    • タップすると- onClickRegistButton: が呼ばれます
    • テキストフィールドに入力された内容をもとに新しいQuizクラスのインスタンスを生成します
    • インスタンスの生成に成功した場合は、-saveInBackgroundWithBlock: でmobile backendに対して登録処理を行います
    • 登録結果を表示します
// 登録ボタンをタップした際の処理
- (void)onClickRegistButton:(UIButton *)button
{
    Quiz *quiz = [Quiz quizWithQuestion:makeQuizView.questionTextField.text
                                 answer:makeQuizView.correctAnswerTextField.text
                           dummyOptions:@[makeQuizView.dummyAnswer1TextField.text,
                                          makeQuizView.dummyAnswer2TextField.text,
                                          makeQuizView.dummyAnswer3TextField.text]];

    if (quiz != nil) {
        [makeQuizView startLoadingWithMask:NO];
        [quiz saveInBackgroundWithBlock:^(NSError *error) {
            if (!error) {
                [self alertWithTitle:nil message:@"登録しました!" cancelButtonTitle:@"閉じる" otherButtonTitle:nil];
            } else {
                NSString *message = [NSString stringWithFormat:@"登録できませんでした\n(%@)", [error localizedDescription]];
                [self alertWithTitle:@"エラー" message:message cancelButtonTitle:@"閉じる" otherButtonTitle:nil];
            }
            [makeQuizView stopLoading];
        }];
    } else {
        [self alertWithTitle:@"エラー" message:@"未入力項目があるため登録できませんでした。"
           cancelButtonTitle:@"閉じる" otherButtonTitle:nil];
    }
}

// アラートの共通処理
-(void)alertWithTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitle:(NSString *)otherButtonTitle
{
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
                                                    message:message
                                                   delegate:self
                                          cancelButtonTitle:cancelButtonTitle
                                          otherButtonTitles:otherButtonTitle, nil];
    [alert show];
}

-(void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (alertView.title == nil) {
        [self dismissModalViewControllerAnimated:YES];
    }
}

Quizクラスの新しいインスタンスを生成するメソッド+quizWithQuestion:answer:dummyOptions:は、SDKが提供しているメソッドではないため、Quizクラスに追加実装する必要があります。
  ※以下で説明しているメソッドはQuiz.mに実装されていますのでご注意ください

  • + quizWithQuestion:answer:dummyOptions:

問題文、正しい選択肢、ダミーの選択肢の全てがそろっていることを確認してから、Quizクラスの新しいインスタンスを生成および初期化を行うメソッドです。引数のいずれかの値が想定と異なる場合は、nilを返します。

// 問題文、選択肢を指定してインスタンスを生成
+ (Quiz *)quizWithQuestion:(NSString *)question answer:(NSString *)answer dummyOptions:(NSArray *)dummyOptions
{
    Quiz *quiz = nil;

    if ([question isKindOfClass:[NSString class]] && question.length > 0 &&
        [answer isKindOfClass:[NSString class]] && answer.length > 0 &&
        [dummyOptions isKindOfClass:[NSArray class]] && [dummyOptions count] == 3 &&
        [[dummyOptions objectAtIndex:0] isKindOfClass:[NSString class]] &&
        ((NSString *)[dummyOptions objectAtIndex:0]).length > 0 &&
        [[dummyOptions objectAtIndex:1] isKindOfClass:[NSString class]] &&
        ((NSString *)[dummyOptions objectAtIndex:1]).length > 0 &&
        [[dummyOptions objectAtIndex:2] isKindOfClass:[NSString class]] &&
        ((NSString *)[dummyOptions objectAtIndex:2]).length > 0) {

        quiz = [[Quiz alloc] init];
        quiz.question = question;
        quiz.answer = answer;
        [quiz addObjectsFromArray:dummyOptions
                           forKey:@"options"];
    }

    return quiz;
}

以上で、クイズを作ることができるようになりました。

クイズに挑戦する

クイズに挑戦する処理はChallengeQuizViewControllerクラスが担っています。
このクラスについて順を追って説明します。

初期化

initメソッド内ではViewsに配置されているChallengeQuizViewのインスタンスを生成しています。
ChallengeQuizViewはクイズに挑戦する画面の見た目に関する部分が実装されています。
インスタンス変数として現在挑戦中のクイズを保持するためのcurrentQuizを定義しています。

@implementation ChallengeQuizViewController
{
    ChallengeQuizView *challengeQuizView;
    Quiz *currentQuiz;
}

- (id)init
{
    if (self = [super init]) {
        // ビューの生成と、現在挑戦中のクイズ情報の初期化
        challengeQuizView = [[ChallengeQuizView alloc] init];
        currentQuiz = nil;
    }
    return self;
}

loadViewメソッド内ではこのコントローラが扱うビューとしてinit内で生成したChallengeQuizViewのインスタンスを設定しています。また、4つの選択肢のボタンや「戻る」ボタンをタップした際に呼び出されるメソッドの指定を行っています。

// ビューの登録と、イベントハンドリング
- (void)loadView
{
    [super loadView];

    self.view = challengeQuizView;

    // 画面内のボタンのタップイベント時に呼び出されるメソッドの登録
    [challengeQuizView.option1 addTarget:self action:@selector(onClickOptionButton:) forControlEvents:UIControlEventTouchUpInside];
    [challengeQuizView.option2 addTarget:self action:@selector(onClickOptionButton:) forControlEvents:UIControlEventTouchUpInside];
    [challengeQuizView.option3 addTarget:self action:@selector(onClickOptionButton:) forControlEvents:UIControlEventTouchUpInside];
    [challengeQuizView.option4 addTarget:self action:@selector(onClickOptionButton:) forControlEvents:UIControlEventTouchUpInside];

    [challengeQuizView.closeButton addTarget:self action:@selector(onClickCloseButton:) forControlEvents:UIControlEventTouchUpInside];
}

viewDidLoadメソッド内では画面表示が完了後の最初の挑戦のために、初回のクイズ表示を処理するfirstChallengeメソッドを呼び出しています。

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self firstChallenge];
}

2回目以降の挑戦ではnextChallengeメソッドが呼び出されますが、これらの違いはローディング中の見た目の違いだけでクイズ表示の処理の実体はrefreshQuizメソッドが担っています。

クイズの表示

refreshQuizメソッドがクイズの取得と画面の更新を処理しています。
Quizクラスの + getNextQuiz: で次の問題を取得し、ChallengeQuizViewの - updateQuizWithQuestion:correctRate:options: で画面を更新します。

// クイズを取得して、画面表示を更新する処理
- (void)refreshQuiz
{
    Quiz *quiz = [Quiz getNextQuiz:currentQuiz];

    if (quiz != nil) {
        currentQuiz = quiz;
        [challengeQuizView updateQuizWithQuestion:currentQuiz.question
                                      correctRate:[currentQuiz getCorrectRate]
                                          options:[currentQuiz getOptions]];
    } else {
        currentQuiz = nil;
        [self alertWithTitle:@"エラー" message:@"クイズの取得に失敗しました"
           cancelButtonTitle:@"再読み込み" otherButtonTitle:@"閉じる"];
    }
    [challengeQuizView stopLoading];
}

ここで利用しているQuizクラスの「+getNextQuiz:」「-getCorrectRate」「-getOptions」は、サブクラスとして作ったQuizクラスに追加定義したメソッドです。
  ※以下で説明している3メソッドはQuiz.mに実装されていますのでご注意ください

  • +getNextQuiz:

mobile backendのデータストアからランダムにクイズを1件取得して返すメソッドです。
本来はページングのために利用するskipに乱数を設定することでランダムに取得する処理を実現しています。

// 次のクイズを取得して返す(引数に渡されたクイズは直前に挑戦したものなので除外する)
+ (Quiz *)getNextQuiz:(Quiz *)previousQuiz
{
    // Quizクラスの検索を行う
    NCMBQuery *query = [Quiz query];

    // 直前に挑戦したクイズが存在する場合は、notEqualで除外する
    if (previousQuiz != nil && previousQuiz.objectId != nil) {
        [query whereKey:@"objectId" notEqualTo:previousQuiz.objectId];
    }

    // 現時点で設定されたクエリにヒットするオブジェクトの件数を取得する
    NSInteger quizCount = [query countObjects];
    if (quizCount <= 0) {
        // 1件も無い場合はnilを返す
        return nil;
    }

    // skipにクイズ件数を上限とした乱数を設定(ここで設定した数だけ検索結果がスキップされる)
    query.skip = arc4random_uniform(quizCount);

    // ここまでに設定されたクエリにヒットする最初のオブジェクトを取得する
    NSError *error;
    Quiz *quiz = (Quiz *)[query getFirstObject:&error];

    return quiz;
}

  • -getCorrectRate

正答率を計算し、すぐに表示できる形式で返却するメソッドです。
挑戦者数が0の問題の場合は「初めての挑戦者です!」という文字列を返します。

// 正答率を計算して文字列として返す
- (NSString *)getCorrectRate
{
    CGFloat hundred = 100.0;
    CGFloat correctRate = (hundred * [self.true_ansusernum floatValue]) / [self.all_ansusernum floatValue];
    if (!isnan(correctRate)) {
        return [NSString stringWithFormat:@"%.1f%%", correctRate];
    } else {
        return @"初めての挑戦者です!";
    }
}

  • -getOptions

クイズの選択肢をシャッフルして配列型で返すメソッドです。
Quizクラスのスキーマ上は正しい選択肢とダミーの選択肢は別々に保持されていますが、このメソッドはそれらをシャッフルして返してくれます。このため、このメソッドの戻り値をそのまま選択してとして利用することができます。また、シャッフルする際に正しい選択肢の位置をインスタンス変数のansNumberに保存しておくので、後で答え合わせする際にも困りません。

※インスタンス変数ansNumberの定義や初期化を追加する必要があります。

@implementation Quiz {
    NSInteger ansNumber;
}

/***** (中略) *****/

// 初期化処理
- (id)init{
    if ((self = [super init])) {
        ansNumber = -1;  // 正解index未設定を表すためansNumberに負値を設定する
    }

    return self;
}

/***** (中略) *****/

// 選択肢をシャッフルして配列として返す(正答のindexが未設定の場合はansNumberプロパティに設定する)
- (NSArray *)getOptions
{
    // 正答のindexを決定する
    if (ansNumber < 0) {
        ansNumber = [[NSNumber numberWithInt:arc4random() % 4] integerValue];
    }

    // 誤答の選択肢をシャッフルする
    NSMutableArray *shuffledOptions = [[NSMutableArray alloc] initWithArray:self.options];
    for (NSInteger i = 0; i < [self.options count]; i++) {
        int randomNumber = arc4random() % i;
        [shuffledOptions exchangeObjectAtIndex:i withObjectAtIndex:randomNumber];
    }

    // シャッフルされた誤答に対して、予め決定しておいたindexに正答を挿入する
    NSArray *options = [NSArray arrayWithObjects:nil];
    NSInteger j=0;
    for (NSInteger i = 0; i < 4; i++) {
        if (ansNumber == i) {
            options = [options arrayByAddingObject:self.answer];
        } else {
            options = [options arrayByAddingObject:shuffledOptions[j]];
            j++;
        }
    }

    return options;
}

イベント発生時の処理

選択肢をタップした際は、- onCickOptionButton: が呼び出されます。このメソッドは答え合わせを処理する - checkingAnswer: を呼び出します。

// 選択肢をタップした際の処理
- (void)onClickOptionButton:(UIButton *)button
{
    [challengeQuizView startLoadingWithMask:NO];
    [self performSelector:@selector(checkingAnswer:) withObject:button afterDelay:0.1];
}

checkingAnswerメソッドは答え合わせと正答率の更新を行います。
選択肢のボタンにはtagにindex番号が設定されています。Quizクラスに追加した-checkAnswer:メソッドに選択したボタンの番号を渡すことで、正否をBOOL型で返してくれます。結果はアラートビューでお知らせし、アラートビューを閉じると次の問題が表示されます。

// 答え合わせと結果の保存処理
- (void)checkingAnswer:(UIButton *)button
{
    int choiceNumber = button.tag;

    if ([currentQuiz checkAnswer:choiceNumber]) {
        [self alertWithTitle:@"正解です!" message:nil cancelButtonTitle:@"もっと解く!" otherButtonTitle:nil];
    } else {
        NSString *message = [NSString stringWithFormat:@"答えは%@でした", currentQuiz.answer];
        [self alertWithTitle:@"不正解です、、" message:message cancelButtonTitle:@"もっと解く!" otherButtonTitle:nil];
    }

    [challengeQuizView stopLoading];
}

Quizクラスに追加定義した-checkAnswer:メソッドは引数の値とインスタンス変数のansNumberの値を比較し、一致した場合にYESを返却します。また、正解した場合はtrue_ansusernumbをインクリメントします。また、正解/不正解に関わらずall_ansusernumをインクリメントします。これらの値は次にこのクイズに挑戦する際に正答率を計算するために利用されます。
  ※以下で説明しているメソッドはQuiz.mに実装されていますのでご注意ください

// 答え合わせと挑戦数/正答数の更新
- (BOOL)checkAnswer:(NSInteger)choiceNumber
{
    BOOL result = NO;
    NSError *error = nil;

    if (ansNumber == choiceNumber) {
        [self incrementKey:@"true_ansusernum"];
        result = YES;
    }
    [self incrementKey:@"all_ansusernum"];
    [self save:&error];

    if (error != nil) {
        NSLog(@"[Error] %@", error);
    }

    return result;
}

おわりに

以上で簡単なクイズアプリが動くようになりました。
サブクラスを使うと、mBaaS上のオブジェクトを透過的に扱うことができるようになります。
ソースコードがすっきりとしますので是非活用してください。

このアプリは単純な機能しか持っていませんので、
他にも様々な機能を追加してみてはいかがでしょうか。

例えば・・・

  • クイズの回答に時間制限を設け、平均回答時間を表示する
  • 会員管理と組み合わせて、利用者毎の正答率などを扱えるようにする
  • クイズに登録すると、自分以外の利用者にプッシュ通知が届いてすぐに挑戦できる

お探しの内容が見つからなかった場合はユーザーコミュニティ もご活用ください。(回答保証はいたしかねます)
なお、 Expertプラン以上のお客様はテクニカルサポートにてご質問を承らせて頂きます。

推奨画面サイズ1024×768px以上

ページの先頭へ