第05講 クイズ

印刷を御希望の方はページ一番下の「印刷用ページ」より印刷してください。
PDFファイルとして保存したい場合も「印刷用ページ」より保存できます。
見出しに「★」マークがついた箇所は説明動画がございます。

目次

  1. 1 ★第5講のポイント
  2. 2 はじめに
  3. 3 プロジェクトの立ち上げと設定
    1. 3.1 新規プロジェクトの立ち上げ
    2. 3.2 アプリの設定
      1. 3.2.1 外部素材のインポート
    3. 3.3 アイコン設定とサポートするデバイスの向きの設定
    4. 3.4 Bundle display nameの設定
  4. 4 画面のデザイン
    1. 4.1 Autolayoutの無効化
    2. 4.2 スタート画面のデザイン
      1. 4.2.1 背景色指定・ラベルの設置
      2. 4.2.2 TextViewの設置
      3. 4.2.3 ボタンの設置
      4. 4.2.4 異なる画面サイズの対応
    3. 4.3 クイズ画面のデザイン
      1. 4.3.1 背景色の指定・ラベルの配置
      2. 4.3.2 TextViewの配置
      3. 4.3.3 ボタンの配置
      4. 4.3.4 異なる画面サイズの対応
    4. 4.4 結果表示画面のデザイン
      1. 4.4.1 背景色の指定・ラベルの配置
      2. 4.4.2 Image Viewの配置
      3. 4.4.3 ボタンの設置
    5. 4.5 異なる画面サイズの対応
  5. 5 新規View Controllerの作成とアサイン
    1. 5.1 新規View Controllerの作成
      1. 5.1.1 QuizViewControllerクラスファイルの作成
      2. 5.1.2 ResultViewControllerクラスファイルの作成
    2. 5.2 新規View Controllerのアサイン
  6. 6 Storyboard Segueによる画面遷移
    1. 6.1 Storyboard Segueの設定
      1. 6.1.1 スタート画面からクイズ画面への遷移
      2. 6.1.2 クイズ画面から結果表示画面への遷移
      3. 6.1.3 結果表示画面からスタート画面への遷移
  7. 7 スタート画面のコーディング
  8. 8 結果表示画面のコーディング
    1. 8.1 Result View Controllerのメンバー変数
    2. 8.2 メンバー変数を外部クラスから参照する準備
    3. 8.3 Result View Controllerの初期処理
  9. 9 クイズ画面のコーディング
    1. 9.1 クイズ問題のデータベース
    2. 9.2 Problemクラスの生成
      1. 9.2.1 新規クラスの作成
      2. 9.2.2 Problemクラスの実装ファイル
      3. 9.2.3 Problemクラスのインターフェースファイル
    3. 9.3 Quiz View Controllerの実装
      1. 9.3.1 Quiz View Controllerのメンバー変数
      2. 9.3.2 CSVファイルの読み込み
      3. 9.3.3 CSVの解析と可変配列への要素追加
      4. 9.3.4 提供する問題のシャッフルと乱数の利用
      5. 9.3.5 Quiz View Controllerの初期処理
      6. 9.3.6 新しい問題の出題と結果表示画面への遷移
    4. 9.4 Segueを発動時のメソッド実装
      1. 9.4.1 「○」ボタンと「×」ボタン向けのメソッド
  10. 10 UI画面とコードの関連付け
    1. 10.1 クイズ画面の関連付け
    2. 10.2 結果表示画面の関連付け
  11. 11 ビルドと動作試験
  12. 12 まとめ

★第5講のポイント

第5講のポイント

  • CSVファイルを用いたデータベースとその読み込み
  • 可変配列(Mutable Array)の利用
  • 乱数の生成と利用
  • 複数の画面を持つアプリの制作とStoryboard Segueによる画面遷移

はじめに

前回の「手拍子」では、Objective-Cの基本概念となるクラスやインスタンス、メソッド等を学びました。また、配列やループなど、プログラミングにおける基本的なテクニックのいくつかも学習しました。さらに、アプリ上から音を再生する手法も学んだかと思います。

第5講の課題アプリは「クイズ」となります。このアプリは始めて複数の画面を持つアプリとなります。スタート画面から始まり、ボタン操作によってクイズが開始されます。ユーザーは10問の一般常識問題(2択方式)に挑戦し、全問題を解き終わると、成績に応じて「一般常識レベル」が表示されます。



それでは、早速開発を始めて行きましょう! 

プロジェクトの立ち上げと設定

新しいアプリを作る際は、まず、新規プロジェクトを立ち上げとアプリの設定を行います。


新規プロジェクトの立ち上げ

第1講と同じ要領で、新規プロジェクトを立ち上げます。その際、使用するテンプレートは「Single View Application」となります。


Product NameQuiz
Company Identifiercom.rainbowapps
Device FamilyiPhone
Use Storyboardチェックを入れる
Use Automatic Reference Countingチェックを入れる
Include Unit Testsチェックを入れる


アプリの設定

新規プロジェクトを立ち上げたら、アプリの設定を行います。ここで、アプリのアイコンやアプリの表示タイトル(Bundle Display Name)、サポートするデバイスの向き(Device Orientation)を以下のように、設定します。

外部素材のインポート

今回使用する外部素材ファイルを一気にインポートしてしまいましょう。


今回使用する素材は以下の通りです。


icon.png アイコン画像
 start.png 「クイズ開始」ボタン用画像
 back.png 「タイトルへ戻る」ボタン用画像
 true.png 」ボタン用画像
 false.png ×」ボタン用画像
 god.png 「神レベル」画像
 human.png 「人間レベル」画像
 monkey.png 「猿レベル」画像
 quiz.csv クイズ問題リスト


アイコン設定とサポートするデバイスの向きの設定

素材のインポートが完了したら、Project Editorを開き、アイコンをRetina Displayと書かれたエリアの上にドラッグします。
サポートするデバイスの向きとアプリの名前は以下の通り設定します。


Bundle display nameの設定

アプリの表示タイトルを決めている設定項目「Bundle Display Name」を変更します。
Project Editorの「info」タブをクリックして「Bundle Display Name」の項目を次のように変更していきます。

 Bundle Display Name クイズ



画面のデザイン

アプリの設定が完了したら、画面のデザインを行なっていきます。今回は初めて3つの画面(スタート画面・クイズ画面・結果表示画面)を持つアプリを作成します。Storyboardを開き、以下の手順に従って画面のデザインを行いましょう。

Autolayoutの無効化

画面レイアウトを3.5インチ(iPhone4S以前)と4インチ(iPhone5以降)に対応させるための設定を行います。
まずはXcode4.5からデフォルトで有効になっているAutolayoutを無効にします。
※Autolayoutはまだ不安定ですので本講座はAutolayoutを無効にして画面デザインを行っていきます。
下記のように①View Controller全体を選択し、②「File Inspector」に変更して、③「Use Autolayout」のチェックを外してください。

スタート画面のデザイン

まず、スタート画面からデザインしていきます。初期状態で、1画面分のViewは用意されているので、それをスタート画面とします。

背景色指定・ラベルの設置

これまでの手順どおり、背景色をデフォルトの白から黒へ変更し、画面上部にタイトル「目指せ!一般常識クイズ王」を記したラベルを配置します。


TextViewの設置

次にクイズの説明を画面上に配置します。説明文は複数行に及ぶので、Labelではなく、Text Viewを利用します。ライブラリーエリアからText Viewを選び、画面中央部に配置します。


次に、画面全体の背景色に合うよう、Text Viewのインスペクターを開き、テキストの色を「白」に、Text Viewの背景色を「黒」に変更します。



Text Viewは、ユーザーによる複数行のテキスト入力も取り扱うことができます。しかし、今回はユーザーによるテキストの編集は必要の無い場面となります。そこで、以下のようにインスペクターの中の「Behavior」の右側にある「Editable」のチェックを外し、編集機能を「切」にします。


Text Viewの設定変更が終わったら、テキストの内容を以下のような説明文に書き換えます。
<入力文
『与えられた問題が「○」か「×」かを選んでください。すべての問題に答え終わると、あなたの「一般常識レベル」が表示されます。』



ボタンの設置

最後に、画面下部に「スタート」ボタンを配置し、プロパティインスペクタを開いてType属性を「Custom」にしてImage属性を「start.png」「start.png」に置き換えます。

異なる画面サイズの対応

3.5インチ及び4インチ画面にて画面のレイアウトが崩れないよう対応していきます。
まず下記画面上部のLabel要素を選択した状態で「Size Inspector」を選択しAutosizingの箇所を上の部分だけ赤色実践になるように変更します。これでLabel要素が上とセンター揃えで固定されました。


ボタン要素とボタン要素を選択してから「Size Inspector」を選択しAutosizingの箇所を下の部分だけ赤色実践になるように変更します。これでLabel要素が画面下とセンター揃えで固定されました。


これにて、スタート画面のデザインは完成となります。


クイズ画面のデザイン

スタート画面のデザインが完了したら、クイズ画面のデザインに移ります。先ほど述べたとおり、初期状態では1画面分のViewしか含んでいません。そこで、クイズ画面用のViewをStoryboard上に新規追加します。ライブラリーエリアよりView Controllerを選び、Storyboard上に配置します。


背景色の指定・ラベルの配置

スタート画面同様、まず背景色を「薄黄色」にし、画面上部に以下のとおり、ラベルを配置します。



TextViewの配置

次に画面中央部にクイズの問題文を表示するText Viewを配置します。配置が終わったら、初期テキストを「問題文」とし、スタート画面の説明文と同様に、編集機能を「切」にしてください。



ボタンの配置

最後に、画面下部に「○」ボタンと「×」ボタンを配置します。2つのボタンを配置した上で、外観をプロパティインスペクタを開いてType属性を「Custom」、Image属性をそれぞれ「true.png」と「false.png」 にしてください。


異なる画面サイズの対応

3.5インチ及び4インチ画面にて画面のレイアウトが崩れないよう対応していきます。
まず下記画面上部のLabel要素とTextView要素を選択した状態で「Size Inspector」を選択しAutosizingの箇所を上の部分だけ赤色実践になるように変更します。これでLabel要素が上とセンター揃えで固定されました。


ボタン要素とPicker View要素を選択してから「Size Inspector」を選択しAutosizingの箇所を下の部分だけ赤色実践になるように変更します。これでLabel要素が画面下とセンター揃えで固定されました。


これにて、クイズ画面のデザインは完成となります。


結果表示画面のデザイン

3つの画面のうち、最後の結果表示画面のデザインに移ります。クイズ画面同様、結果表示画面用のViewをStoryboard上に新規追加します。


背景色の指定・ラベルの配置

スタート画面やクイズ画面同様、まず背景色を「薄緑色」にし、画面上部に以下のとおり、2つラベルを配置します。これらは、コード上の処理から一切変更されない静的なラベルとなります。


次に、コード上の処理から変更を受ける動的なラベルを2つ追加します。1つ目の正答率を表示すラベルを画面上部に配置し、ユーザーの「一般常識レベル」を表示する2つ目のラベルを画面下部に配置します。


Image Viewの配置

ラベルの配置が終わったら、「一般常識レベル」に応じた画像を表示するImage ViewというUIオブジェクトを配置します。ライブラリーエリアからImage Viewを選び、画面中央部に配置してください。Image Viewは、コード上で指定した任意の画像を表示することのできるエリアとなっています。現状では無地のグレーの箱となっていますが、心配しないでください。


ボタンの設置

最後に、画面下部に「タイトルへ戻る」ボタンを配置し、外観をプロパティインスペクタを開いてType属性を「Custom」、Image属性を「back.png」とします。

異なる画面サイズの対応

3.5インチ及び4インチ画面にて画面のレイアウトが崩れないよう対応していきます。
まず下記画面上部のLabel要素とImabeView要素を選択した状態で「Size Inspector」を選択しAutosizingの箇所を上の部分だけ赤色実践になるように変更します。これでLabel要素が上とセンター揃えで固定されました。


ボタン要素とPicker View要素を選択してから「Size Inspector」を選択しAutosizingの箇所を下の部分だけ赤色実践になるように変更します。これでLabel要素が画面下とセンター揃えで固定されました。


これにて、すべての画面のデザインは完了となります。すべて正しく行った場合、Storyboard上には以下のように3つの画面が並んでいるはずです。

新規View Controllerの作成とアサイン

前回までに製作したアプリはすべて単一画面のみのアプリでした。故に、コードを記述したView Controllerクラスも1つのみでした。しかし、今回の「クイズ」では画面が3つあります。基本的に、画面を複数用意する場合、1つの画面につき、View Controllerクラスも1つずつ必要となります。

まず、スタート画面に関してですが、これは初期状態で予め用意されていたViewを使ったため、既存のViewController.hとViewContorller.mをそのまま流用できます。しかし、今回新たにViewを追加してデザインした、クイズ画面と結果表示画面のView Controllerはまだありません。

このセクションでは新たにView Controllerを作成し、それぞれの画面にアサインする作業を行います。

新規View Controllerの作成

QuizViewControllerクラスファイルの作成

まずは、クイズ画面用のView Controllerクラスから作成します。ナビゲーターエリアの中にある、「Quiz」と書かれたフォルダを右クリックし、「New File…」メニューをクリックします。


ここで新規ファイル作成のダイアログが表示されるので、「 Objective-C class」を選択し、「Next」をクリックします。
以下のような画面が現れ、クラス名とオプションの適用有無を聞かれます。


以下の表の通り、クラス名とオプションを設定し、「Next」をクリックし、既存のView Controllerとフォルダに保存します

 Class QuizViewController
 Subclass of UIViewController
 Targeted for iPad チェックを外す
 With XIB for user interface チェックを外す

そうすると、ナビゲーターエリアの一覧に今回作成したQuiz View Controllerクラスのインターフェースファイル(QuizViewController.h)と実装ファイル(QuizViewController.m)が表示されます。


ResultViewControllerクラスファイルの作成

同様の手順で、結果表示画面用のView Controllerクラスも作成します。同じ手順で「Objective-C class」を新たに作成し、以下の表の通り、クラス名とオプションを設定します。

 Class ResultViewController
 Subclass of UIViewController
 Targeted for iPad チェックを外す
 With XIB for user interface チェックを外す


ここまでの手順を正しく行った場合、ナビゲーターエリアに合計4つのファイルが新たに作成されたはずです。



新規View Controllerのアサイン

ここまでの手順で、3つの画面用のView Controllerのひな形が作成されました。次に、新たに作ったQuiz View ControllerとResult View Controller をそれぞれのViewにアサインしなければいけません。

まずは、Storyboard上のクイズ画面の下部にある、黒いバーをクリックし、画面全体を選択します。次に、インスペクターを開き、以下のメニューから「QuizViewController」を選びます。


同様の手順で、結果表示画面についても「ResultViewController」を選びます。


すべて正しくアサインできた場合、Storyboardをズームアウトして全体を見渡すと、各画面下部の黒いバーに新しくアサインしたView Controllerの名前が表示されているはずです。

Storyboard Segueによる画面遷移

3つの画面のデザインとView Controllerのアサインが終わったところで、それぞれの画面を遷移する手法について検討します。iOS SDK 4以前(Storyboard登場以前)は、画面遷移を行う場合、必ずコード上から命令を呼び出す必要がありました。しかし、Storyboardの登場によって、画面遷移の処理が大幅に簡略化されました。本コースでも第12講以降はStoryboardを使わない従来の手法での画面遷移を取り扱いますが、それまではStoryboardの機能を存分に活用していきます。

複数の画面間を遷移されるにあたり、iOS 5 SDKより、Storyboard Segue(ストーリーボード・セグエー)と呼ばれる機能が登場しました。前述したとおり、これまでの煩雑な画面遷移処理を大幅に簡略するものであり、単純な画面遷移ならばコードを一切記載することなく、画面遷移の流れを構築できる機能となっています。

今回の「クイズ」では、このStoryboard Segueを積極的に活用していきます。

Storyboard Segueの設定

Storyboard Segueは、遷移元の画面と遷移先の画面を「線」で結ぶことによって設定します。今回は、以下の3つの画面遷移があるので、それぞれに対してStoryboard Segueを設定していきます。
  • スタート画面 → クイズ画面
  • クイズ画面 → 結果表示画面
  • 結果表示画面 → スタート画面

スタート画面からクイズ画面への遷移

スタート画面からクイズ画面へ遷移は、「スタート」ボタンが押させることによって起こるものとします。前述したように、このような単純な画面遷移は、コードを記述することなく、Storyboard上で設定できます。

まず、スタート画面中にある「スタート」ボタンを右クリックし、インスペクターを開きます。上部に「Triggered Segues」のセクションがあること確認してください。


このうち、「action」の右にある小さな円から、クイズ画面へとマウスをドラッグすると以下のように3つの選択肢が表示されますので、modalを選択してください。


クイズ画面の中でマウスを離すと、スタート画面とクイズ画面がSegueによって結ばれ、2つの画面を繋ぐ矢印が表示されます。この設定により、「スタート」ボタンが押させると、このSegueが発動され、スタート画面からクイズ画面へと遷移します。


このように、ボタン操作によって画面遷移を発動させる設定は、非常に簡単なものとなっています。

クイズ画面から結果表示画面への遷移

スタート画面からクイズ画面への遷移は非常に簡単でしたが、クイズ画面から結果発表画面への推移はそう簡単には行えません。

そもそもクイズ画面から結果表示画面に遷移するのは、ユーザーが全問解き終わっていることが前提となります。また、遷移時にクイズの正答率を結果表示画面に渡す必要があります。このように場合分けや、データの受け渡しを伴う画面遷移はコード上から呼び出す必要があります。

しかし、コード上から画面遷移を呼び出す場合も、Storyboard Segueと組み合わせることで、処理を比較的に簡単に実装することができます。これまで、ボタンやラベル等を配置する際、それらをコード上のメソッドやインスタンスと関連付けることをやってきました。Storyboard Segueも同じ要領で、予めInterface Builderを用いてStoryboard上に設定し、コード上から任意のタイミングでそれを発動させることができるのです。

それでは、早速作業を進めていきます。ボタンに直接関連付いていない、独立したStoryboard Segueを作成するためには、まず、キーボード上の「Control」キーを押しながら、遷移元の画面(クイズ画面)下部の黒いバーにあるView Controllerのアイコンをクリックします。


そのまま「Control」キーを押し続けながら、マウスを遷移先の画面(結果表示画面)にドラッグし、マウスを離します。その後現れるポップアップから「Modal」を選択します。


ここまでの作業を正しく行った場合、以下のようにクイズ画面と結果表示画面を結ぶ矢印が表示されます。このSegue(矢印)をクリックし、インスペクターを開き、Identifier(識別子)を付けましょう。識別子は「toResultView」とします。


コード上からこの画面遷移(Segue)を発動させる際は、先程設定した識別子である「toResultView」を参照することで呼び出せます。

結果表示画面からスタート画面への遷移

最後に、ユーザーが結果を見終わったら、スタート最後へ戻れるように、Segueを設定します。結果表示画面上に「タイトルへ戻る」ボタンを設置したかと思いますが、このボタンに対してSegueを設定します。スタート画面からクイズ画面へのSegueを設定した時と同じ要領で、「タイトルへ戻る」ボタンを右クリックし、インスペクターを表示さます。

その後、Triggred Seguesの中にあるaction欄を右手にある小さな円からスタート画面へマウスをドラッグし、modalを選択しSegueを作成します。



これにて、全てのStoryboard Segue(画面遷移)の設定は完了となります。すべて正しく行った場合、以下のように、すべての画面がそれぞれSegueを示す矢印で結ばれているはずなので、確認してください。

スタート画面のコーディング

まずは、スタート画面のView Controllerから見ていきます。スタート画面に関しては、予め用意されていた1画面分のViewをもとにデザインしました。よって、スタート画面のView Controllerも予め用意されていたViewController.hとViewController.mを流用します。

スタート画面は基本的に説明文と「スタート」ボタンしかありません。説明文はコード上の処理の影響を一切受けない静的なテキストなので、実質画面上にあるものは、クイズを開始する「スタート」ボタンのみと言えるでしょう。

これまでのアプリでは、ボタン操作に関係する処理はIBAction型のメソッドとして、コード上に実装してきました。しかし、「スタート」ボタンに割り当てる機能は画面遷移の発動のみです。これは、すでにInterface Builderを用いて直接Storyboard上にSegueとして設定済みなので、独自のメソッド等は不要となります。故に、スタート画面のView Controller上には別途コーディングは一切不要となります。

結果表示画面のコーディング

クイズ画面から結果表示画面へ遷移する際、データの受け渡しを行う都合上、次は、結果表示画面のコーディングを行なっていきます。

Result View Controllerのメンバー変数

まずはメンバー変数から宣言していきます。以下のとおり、メンバー変数を宣言してください。

ResultViewController.m
@implementation ResultViewController {

        

    //各種ラベル

    IBOutlet UILabel *percentageLabel;

    IBOutlet UILabel *levelLabel;

    

    //一般常識レベルの画像を扱うImage View

    IBOutlet UIImageView *levelImage;

    

}


メンバー変数を外部クラスから参照する準備

今回、Result View Controllerでは、クイズ画面から受け渡される値を格納する必要があります。その際、Quiz View Controllerから、この参照できる変数が必要となります。そこで、その変数を「correctPercentage」とし、外部クラスからでもをアクセスできるためにプロパティ宣言を行います。
以下のとおり、ResultViewController.hに記述してください。

ResultViewController.h

@interface ResultViewController : UIViewController


@property int correctPercentage; 


@end



Result View Controllerの初期処理

結果表示画面では、呼び出された段階で、「correctPercentage」に正答率が格納されています。よって、今回は立ち上がり時に呼ばれる「-(void)viewDidLoad」メソッド内で、正答率に応じて画面をつくり上げていきます。なお。データを受け渡す実際の処理はクイズ画面のコーディングを行う際に解説します。

以下のとおり、ResultViewController.mにコードを記述します。

ResultViewController.m

- (void)viewDidLoad

{

    [super viewDidLoad];

    

    //正答率に応じて「一般常識レベル」の位と画像を設定

    if (_correctPercentage < 30) {

        levelLabel.text = @"猿レベル";

        levelImage.image = [UIImage imageNamed:@"monkey.png"];

    } else if (_correctPercentage >= 30 && _correctPercentage < 90) {

        levelLabel.text = @"一般人レベル";

        levelImage.image = [UIImage imageNamed:@"human.png"];

    } else if (_correctPercentage >= 90){

        levelLabel.text = @"神レベル";

        levelImage.image = [UIImage imageNamed:@"god.png"];

    }

    

    //実際の正答率を表示

    percentageLabel.text = [NSString stringWithFormat:@"%d %%", _correctPercentage];

    

    

}


ここで、「_correctPercentage」とありますが、これは、先程宣言した@property による記述があるために利用が可能になります。

Objective-Cでは、このように、クラス内で扱う変数をメンバ変数を表現しますが、事前にプロパティ宣言されている場合は、メンバ変数を改めて宣言する必要はなく、自動的に「_(プロパティ名)」となります。

ここでは、正答率に応じて「一般常識レベル」の位とアイコンの場合分けを行なっています。正答率が30%未満なら「猿レベル」、30%以上、90%未満なら「人間レベル」、90%以上なら「神レベル」となっています。

アイコンに関しては、それぞれのレベルに応じたアイコン画像を「levelImage」に設定します。「levelImage」で表示する画像は、UIImageViewクラスに定義されている、「imageNamed」メソッドによって指定します。

クイズ画面のコーディング

次に、本アプリの中心となるクイズ画面のコーディングを行なっていきます。

クイズ問題のデータベース

まず、クイズ問題のデータベースから解説していきます。データベースとは、複数のデータを格納するための箱のようなものです。

冒頭で、「quiz.csv」というファイルをインポートしたかと思いますが、これはクイズ問題の問題文と答えを、CSV(Comma Separated Values)形式でまとめたものです。CSVはエクセルなどの表をテキストファイルとして書き出したものとして認識するとわかりやすいと思います。CSVでは、それぞれのデータのまとまりは行毎に記録し、それぞれの列はカンマで区切って表現します。その例を以下に示します。

2012年現在、日本の総理大臣は野田佳彦である,0

2012年現在、アメリカのオバマ大統領は2人目黒人大統領である,1
世界一の経済大国は中国である,1
iPhoneはアップル社の製品である,0
------
【問題文】,【答え:「○」の場合は「0」、「☓」の場合は「1」】
------

このような形式で、quiz.csvには全部で20個の問題が格納されています。

Problemクラスの生成

まずは、それぞれのクイズ問題を扱うための「Problemクラス」を新規に実装します。Problemクラスは、前回の「手拍子」で実装したClapクラスと同じ要領で実装していきます。

新規クラスの作成

まずはClapクラスと同じ要領で「Problem」という名前の新規クラスを作成します。独自のクラスの新規作成方法に関しては第4講のテキストを見直してください。正しく作成が行えた場合、 以下のようにナビゲーターエリアにProblem.hとProblem.mができているはずです。



このProblemクラスは1つのクイズ問題の問題文と答え、を格納するとともに、それらを参照するためのメソッドを提供しています。

Problemクラスの実装ファイル

それでは早速実装を進めていきましょう。まずはProblem.mから実装します。以下通り、メンバー変数を宣言し、初期化処理用のメソッドを実装します。

Problem.m

@implementation Problem {

    //問題文

    NSString *question;

  //答え(「」なら「0」、「×」なら「1」)

    int answer;

}

 

//初期化処理

+ (id)initProblem {

    return [[self allocinit];

}


次に問題文と答え格納するメソッドを記述します。

Problem.m

//問題文と答えを格納

- (void)setQ:(NSString *)q withA:(int)a {

    question = q;

    answer = a;

}


最後に、問題文と答えをそれぞれ参照するためのメソッドを記述します。

Problem.m

//問題文を読み出し

- (NSString *)getQ {

    return question;

}


//答えを読み出し

- (int)getA {

    return answer;

}


Problemクラスのインターフェースファイル

実装ファイルの記述がおわったら、インターフェースファイル(Problem.h)上に先ほど記述した各メソッドのプロトタイプを宣言します。

Problem.h

+ (id)initProblem;

- (void)setQ:(NSString *)q withA:(int)a;

- (NSString *)getQ;

- (int)getA;


Quiz View Controllerの実装

次に、Quiz View Controllerの実装に移ります。

Quiz View Controllerのメンバー変数

まずはメンバー変数から宣言していきます。以下のとおり、メンバー変数を宣言してください。

QuizViewController.m

@implementation QuizViewController {

  //問題(Problemクラスのインスタンス)を格納する配列

    NSMutableArray *problemSet;

  //出題する問題数

    int totalProblems;

  //現在の進捗(出題済み問題数)を記録

    int currentProblem;

    //正答数

    int correctAnswers;

    //問題文を表示するText View

    IBOutlet UITextView *problemText;

}


ここにある「NSMutableArray」とは可変配列を提供するクラスです。可変配列とは、配列の大きさを予め定義することなく、自由自在に要素を追加や削除ができる配列となります。quiz.csvに格納されている問題数は常に変化する可能性があります。そこで、要素数を予め確定させることが困難な場合に役立つのが、この可変配列となるのです。

CSVファイルの読み込み

ここでは、quiz.csvから問題文と答えを読み込み、それぞれの問題をProblemクラスのインスタンスに格納した上で、可変配列である「problemSet」に追加する「- (void)loadProblemSet」というメソッドを実装します。

まずは、その前半のファイルの読み込み部分を実装します。以下のとおり、QuizViewController.mに記述してください。

QuizViewController.m

//問題の読み込み

- (void)loadProblemSet {

    // ファイルの読み込み

    NSString* path = [[NSBundle mainBundlepathForResource:@"quiz" ofType:@"csv"];

    NSError* error = nil;

    int enc = NSUTF8StringEncoding;

    NSString* text = [NSString stringWithContentsOfFile:path encoding:enc error:&error];

    // 行ごとに分割し、配列「lines」に格納

    NSArray* lines = [text componentsSeparatedByString:@"\n"];


}

 
ここでは、quiz.csvが読み込まれ、そのテキストの内容はすべて、NSString型のローカル変数である、「text」に格納されます。その後、改行文字によって分割され、1行ごと「lines」というNSArray型の配列に格納されます。

CSVの解析と可変配列への要素追加

引き続き、「- (void)loadProblemSet」というメソッドを実装します。以下のとおりProblem.mに記述してください。

QuizViewController.m

//問題の読み込み

- (void)loadProblemSet {

    

    【中略】

    

    // 問題を格納する可変配列のproblemSetを初期化

    problemSet = [[NSMutableArray allocinit];

    

  // 問題の数だけ繰り返し

    for(int i=0; i<[lines count]; i++){

        

   //問題をカンマで区切って、要素を配列「items」に格納

        NSArray* items = [[lines objectAtIndex:i] componentsSeparatedByString:@","];

        

   //Problemクラスのインスタンスを生成・初期化し、問題文と答えを格納

        Problem *p = [Problem initProblem];

        NSString *q = [items objectAtIndex:0];

        int a = [[items objectAtIndex:1intValue];

        [p setQ:q withA:a];

        

        //新たに生成したProblemクラスのインスタンスをproblemSetに追加

        [problemSet addObject:p];

    }    

}

 
ここで、Problemクラスのインスタンスを生成するにあたって、Quiz View Controllerのインターフェースファイル(QuizViewController.h)上で以下のコードを追加する必要があります。

QuizViewController.h

#import <UIKit/UIKit.h>

#import "Problem.h"


まずは、問題(Problemクラスのインスタンス)を格納するための可変配列である、「problemSet」を初期化します。

その後、問題の数(「lines」に格納されている要素数)の分だけ繰り返し処理が行われるループを構築します。コード上のfor文にある通り、NSArrayクラスの「count」というメソッドを呼び出すと、要素数の合計が返されます。

繰り返し処理の中では、1行をさらにカンマによって分割し、「items」という配列に格納します。そして、「p」というProblemクラスのインスタンスを問題毎に新しく生成し、問題文の「q」と答えの「a」をそれぞれ「p」に格納します。その際、「- (void)setQ:(NSString *)q withA:(int)a」というメソッドを用います。最後に、「problemSet」に新しく生成した「p」を追加します。

提供する問題のシャッフルと乱数の利用

ここまでで、問題をquiz.csvから取り込む処理が完了しました。一方で、現状の「problemSet」はquiz.csvの順番通りに問題が格納されています。現状の「problemSet」の順番通りにクイズ問題を出題すると、毎回クイズを始める際、常に同じ問題が同じ順番で出題されてしまいます。そこで、新たな工夫が必要となります。

今回は、この「problemSet」の要素そのものをランダムに並び替えることによって、出題される問題をその都度変えるものとします。そのためのメソッドである、「- (void)shuffleProblemSet」を実装していきます。以下のとおり、コードを記述してください。

QuizViewController.m

//問題文をシャッフル

- (void)shuffleProblemSet {


    //problemSetに格納された全問題の数を取得

    int totalQuestions = [problemSet count];

    //Fisher-Yatesアルゴリズム用のカウンターを取得

    int i = totalQuestions;


  //Fisher-Yatesアルゴリズムによって配列の要素をシャッフル

    while (i > 0) { 

        //乱数を発生

        srand((unsigned int)time(0));

        int j = rand() % i;

        //要素を並び替え

        [problemSet exchangeObjectAtIndex:(i-1withObjectAtIndex:j];

   //カウンターを減少させる

        i = i - 1;

    } 

}


今回は「Fisher−Yatesアルゴリズム」という手法を用いて、配列の要素をランダムに並び替えます。「Fisher−Yatesアルゴリズム」の詳細は割愛しますが、簡単に言うと非常に高速かつ簡潔に配列の要素をランダムに並び替えることのできる手法です。詳しい原理が気になる方は、Googleで検索してみてください。

このメソッドでは「Fisher−Yatesアルゴリズム」を用いて、「problemSet」を並び替えるので、その際乱数を発生させています。以下にその例を示します。

乱数を発生させる場合は、まず、「乱数の種」を指定します。この乱数の種を元に、コンピュータはランダムな値を生成すのです。特に断りが無い限り、上の例の通り、現在の時刻を「種」とします。

//乱数の「種」を設定
srand((unsigned int)time(0));
//「0」から「19」までの乱数を発生
int number = rand() % 20;

次に「rand()」が呼ばれると、ランダムな整数が生成されます。ここで0から19までの範囲内で、ランダムな数字が欲しいとします。この時、「rand()」が返す値は、この範囲内にあるという保証はどこにもありません。そこで、この数を20で割った時の「余り」を見るようにします。割り算の結果の「余り」を得るためには、3講で説明した通り、「%」演算子を用います。

int number = 5 % 3; 
//このとき、numberは「2」となる

どのような数字でも、20で割った時のあまりは0から19の間に収まります。よって、この手法をもちいることで、指定範囲内のランダムな数字を生成することができます。

Quiz View Controllerの初期処理

問題の読み込みと変えを行うメソッドを実装したところで、クイズ画面が呼ばれた際に実装される初期処理を実装します。

以下のとおり、QuizViewController.mの「- (void)viewDidLoad」メソッドを書き換えます。

QuizViewController.m

- (void)viewDidLoad {

    [super viewDidLoad];

    

    //クイズ問題を読み込み

    [self loadProblemSet];

    //クイズ問題をランダムに並び替え(シャッフル)

    [self shuffleProblemSet];

    

    //提示問題数を10問とする

    totalProblems = 10;

    

    //現在の問題番号と正答数を0にする

    currentProblem = 0;

    correctAnswers = 0;

    

    //problemSetの最初の要素の問題文をクイズ画面にセット

    problemText.text = [[problemSet objectAtIndex:currentProblem] getQ];    

}


ここでは、先程実装した「- (void)loadProblemSet」と- (void)shuffleProblemSet」をそれぞれ呼び出し、クイズ問題の読み込みと並び替えを行なっています。その上で、最初の問題の問題文を画面上に表示させています。

新しい問題の出題と結果表示画面への遷移

ここまでで、クイズ提供の最初の段階が完成しました。次はクイズの進行について考えていきます。

まずは、以下のとおり、新しい問題を提示するメソッドを実装します。

QuizViewController.m

//次の問題提示 or 全問時終わっていたら結果表画面へ

- (void)nextProblem {


    //currentProblemを繰り上げ

    currentProblem++;


  //これまで出題した問題が、提示問題数に達していない場合

    if (currentProblem < totalProblems) {

        //次の問題の問題文を提示 

        problemText.text = [[problemSet objectAtIndex:currentProblemgetQ];

        

    //全問題解き終わった場合

    } else {

        

        //結果表示画面へのSegueを始動

        [self performSegueWithIdentifier:@"toResultView" sender:self]; 

    }

}


回答済みの問題数は「currentProblem」で管理しています。このメソッドが呼ばれると、「currentProblem」に「1」が追加されます。ここで、出題済みの問題数が既定数(今回は10問)に達していない場合は、次の問題の問題文が画面上に出されます。一方で、「currentProblem」が「10」となった場合は、すべて解き終わったということで、結果表示画面のSegueが発動されます。

Segueを発動させるにあたり、結果表示画面へのSegueを設定する時にアサインした識別子である「toResultView」を、ここで指定しています。

Segueを発動時のメソッド実装

ここで、Segueが正しく行われるためには、以下のメソッドを実装する必要があります。

QuizViewController.m

//結果表示画面へのSegueの発動

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

    //正答率を算出

    int percentage = (correctAnswers * 100 / totalProblems) ;

    

    //ResultViewControllerRVC)のインスタンスを作成し、

    //RVCクラスのメンバー変数である「correctPercentage」に値を渡す

    if ([[segue identifierisEqualToString:@"toResultView"]) {

        ResultViewController *rvc = (ResultViewController*)[segue destinationViewController];

        rvc.correctPercentage = percentage;

    }

}


画面間でデータを受け渡しする場合、遷移先の画面のView Controllerのクラスをインスタンスとして宣言し、そのインスタンスのメンバー変数に渡したいデータを代入します。上の例では、先程実装したResult View Controllerの正答率を格納する「correctPercentage」というメンバー変数に正答率を代入しています。なお、ここで登場する「toResultView」は、結果表示画面へのSegueの識別子となっています。

最後に、Result View Controllerクラスのインスタンスを生成するにあたり、Quiz View Controllerのインターフェースファイル(QuizViewController.h)上で以下のコードを追加する必要があります。

QuizViewController.h

#import <UIKit/UIKit.h>

#import "ResultViewController.h"


「○」ボタンと「×」ボタン向けのメソッド

ユーザーは、「○」ボタンと「×」ボタンを用いて、答えを入力します。そこで、これらボタンが押された時の処理を実装し、その中で、正誤判定を行うようにします。

以下の通り、コードを記述してください。

QuizViewController.m

//」ボタンが押された場合

- (IBAction)answerIsTrue:(id)sender {

    //答えを確認し、次の問題を提示

    if ([[problemSet objectAtIndex:currentProblemgetA] == 0) {

        correctAnswers++;

    }

    [self nextProblem];

}


//×」ボタンが押された場合

- (IBAction)answerIsFalse:(id)sender {

    //答えを確認し、次の問題を提示

    if ([[problemSet objectAtIndex:currentProblemgetA] == 1) {

        correctAnswers++;

    }

    [self nextProblem];

}


これら2つのIBAction型のメソッドは、それぞれ「○」ボタンと「×」ボタンが押されたときに呼ばれるものです。

今回のクイズ問題では、問題文が「正」の場合、答えは「0」と記録されています。一方で「誤」の場合は「1」が記録されています。これらを用いて、正誤判定を行い、次の問題を呼び出すために、先程実装した「-(void)nextProblem」というメソッドを呼び出しています。

ここまで、駆け足で解説してきましたが、クイズの処理に関する処理はひと通り理解できましたでしょうか。わからなかった場合は、もう一度自分で実装したコードを読み直し、流れを理解するようにしましょう。

これにて、すべてのコードの記述は完了となります。最後に、各種メソッドやインスタンスをUI画面上のオブジェクトと関連付け、「クイズ」の完成となります。

UI画面とコードの関連付け

ここまでで、基本的にコードの記述は全て完了となります。そこで、これまでに設置したUI画面上の部品とコード上の処理を関連付けます。

今回、スタート画面上の「スタート」ボタンはSegueを設定済みなので、新たに関連付けるべきUIオブジェクトはありません。よって、このセクションではクイズ画面と結果表示画面のみに着目して解説していきます。

クイズ画面の関連付け

毎度、お馴染みの作業となっていますが、今回もText Viewとボタンの関連付けを行います。

まず、問題文のテキストを表示するText Viewを、Quiz View Controllerの「problemText」と関連付けます


次に、「○」ボタンと「×」ボタンをそれぞれ「answerIsTrue」と「answerIsFalse」と関連付けます。





これにて、クイズ画面の関連付けは完了となります。

結果表示画面の関連付け

結果表示画面にかんしても、ラベルとImage Viewの関連付けを行います。

まず、「一般常識レベル」の位(「神レベル」など)を表示するラベルと、正答率を示すラベルを、それぞれ、Result View Controllerの中の「levelLabel」と「percentageLabel」と関連付けます。




最後に、「一般常識レベル」に応じたアイコンを表示するためのImage Viewを「levelImage」と関連付けます。

ビルドと動作試験

これにて、すべての作業は完了となります。編集内容を全て保存し、ビルドを行なってください。このテキストの内容をすべて正しくやった場合、特に問題なくアプリが動作するはずです。

ボタンによって、画面遷移が起こることや、クイズの正誤判定がしっかりできていること、規定した問題数だけ出題されること、正答率に応じて「一般常識レベル」が変化することを確認してください。


まとめ

第5講では、CSVファイルの読み込みや可変配列、乱数の発生等を扱いました。また、機能的なiOSアプリを実装する上で必要不可欠な画面遷移のやり方も学習しました。また、画面間でデータを受け渡す典型的な手法も学びました。

今回のクイズは画面が多い分、コーディングの量も多かったと思います。本テキストは講義時間の都合上、説明の一部を簡略化している部分があります。駆け足で解説しましたが、もし理解できていない部分があったら、適宜コードを見直すようにして、全体の流れをイメージできるようにするといいでしょう。また、コード上のメソッド等でわからない部分があったら、適宜インターネット等で調べることも大切です。

第1講から第5講まで、Objective-Cにおける初歩的プログラミングテクニックやiOS SDKの基礎機能を中心に扱って来ました。これまでの内容の復習をしっかり行った上で、次回以降、iOS SDKの提供する、便利かつ高機能なフレームワークを使ったアプリを作っていきましょう。
Comments