クイズアプリ

講義の目標

  • CSVファイルからデータを取得する方法について理解すること
  • 可変配列(Mutable Array)が利用できるようになること
  • 乱数の生成と利用方法をマスターすること
  • 複数の画面を持つアプリの制作とStoryboard Segueによる画面遷移ができるようになること

はじめに

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

完成品のサンプルは以下よりダウンロードできます。イメージが湧かない方は是非ご確認ください。

iOS完成品サンプル(クイズアプリ)

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

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

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

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

Welcom to Xcode画面から、Create a new Xcode projectを選択します。そしてSingle View Appを選択します。

今回は以下のように設定します。

入力項目 入力値
Product Name Quiz
Team None
Oganization Identifier jp.co.al-j
Bundle Identifier jp.co.al-j.Quiz
User Interface Storyboard
Life Cycle UIKit App Delegate
Language Swift
Use Core Data チェックを外す
include Tests チェックを外す

プロジェクトの保存場所を設定します。任意の場所を選択し、Createボタンをクリックして下さい。

※ ここではDesktopを保存場所として設定しています。

アプリの設定

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

Device Orientation の設定

対象デバイスとサポートするデバイスの向きの設定を行います。

ナビゲータエリアからproject > General を選択し、Development infoを以下の設定にします。

Development info

Target Device 入力値
iOS14.5 iPhone チェックをする
iOS14.5 iPad チェックをはずす
Device Orientation Portrait チェックをする
Device Orientation Upside Down チェックをはずす
Device Orientation Landscape Left チェックをはずす
Device Orientation Landscape Right チェックをはずす

Bundle Nameの設定

アプリの表示タイトルを決めている設定項目Bundle Nameを変更します。

infoタブを選択し、Bundle Nameを次のように変更します。

Custom iOS Target Properties

Key Type Value
Bundle Name String クイズ

素材取り込み

以下のリンクから素材ファイルをダウンロードします。

素材ダウンロード

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

ファイル名 説明
icon-20pt@2x.png Notification用x2画像
icon-20pt@3x.png Notification用x3画像
icon-60pt@2x.png アイコン用x2画像
icon-60pt@3x.png アイコン用x3画像
icon-29pt@2x.png 設定用x2画像
icon-29pt@3x.png 設定用x3画像
icon-40pt@2x.png Spotlight用x2画像
icon-40pt@3x.png Spotlight用x3画像
icon-1024pt@1x.png AppStore用アイコン画像
btn_yes.pdf クイズ画面のYESボタン画像
btn_no.pdf クイズ画面のNOボタン画像
btn_start.pdf トップ画面のスタートボタン画像
btn_go_top.pdf 結果画面のトップへボタン画像
good.pdf 結果画面の高得点用イメージ画像
normal.pdf 結果画面の合格点用イメージ画像
bad.pdf 結果画面の得点が低い場合のイメージ画像
quiz.csv クイズ問題ファイル

ダウンロードしたら、zipファイルを解凍して画像ファイルiconファイルを、下図のとおり設定してください。

画像ファイル
プロパティ
Scales Single Scale

iconファイル

画面のデザイン

画面を作成していきます。

スプラッシュ画面の設定

スプラッシュ画面を作成します。

今回は以下のように設定します。

background の設定

ナビゲーターエリアからLaunchScreen.storyboard を選択し、ViewController > View > Attributes inspector を選択し、 Background を以下へ設定します。

Viewプロパティ名 設定値 Hex Color
Background Custom E74C3C

Labelの設置

ライブラリーエリアを選択し、 View 上に Label を配置します。

Label に対して以下の設定を行います。

Labelプロパティ名 設定値
Text クイズ
Color White Color
Font 50px
Alignment centor

AutLayoutの設定

Labelに対して、以下の「制約(Constraint)」を設定します。

警告が表示されていなければOKです。

メイン画面の設定

次にメイン画面の設定をおこなっていきます。
Main.Storyboardを開き、空白のキャンバス上にアイテムを配置していきます。

今回のメイン画面は、スタート画面クイズ画面結果表示画面の3つの画面を作成していきます。

スタート画面のデザイン

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

背景色指定・Labelの設置

画面上部にタイトル「一般常識クイズ」を記したLabelを配置します。

Labelプロパティ名 設定値
Text 一般常識クイズ
Font 30px
Alignment centor

TextViewの設置

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

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

Text Viewプロパティ名 設定値
Behavior Editable false

Text Viewの設定変更が終わったら、テキストの内容を以下のような説明文に書き換えます。

Text Viewプロパティ名 設定値
Text 与えられた問題が「YES」か「NO」かを選んでください。すべての問題に答え終わると、あなたの「一般常識レベル」が表示されます。
Font 20px

Buttonの設置

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

Buttonプロパティ名 設定値
Type Custom
title (空白)
image btn_start

スタート画面のAuto Layout設定

スタート画面のレイアウトの調整をしていきます。

LabelのAuto Layout設定

Text ViewのAuto Layout設定

ButtonのAuto Layout設定

クイズ画面のデザイン

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

Labelの配置

スタート画面同様、Labelを配置します。文字は問題にしておきましょう。

Labelプロパティ名 設定値
Text 問題
Font 25px
Alignment centor

Text Viewの配置

次に画面中央部にクイズの問題文を表示するText Viewを配置します。配置が終わったら、以下の値を設定してください。

Text Viewプロパティ名 設定値
Text 問題文
Font 20px
Alignment left
Behavior Editable false

Buttonの配置

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

Buttonプロパティ名 設定値
Type Custom
title (空白)
image btn_no

Buttonプロパティ名 設定値
Type Custom
title (空白)
image btn_yes

クイズ画面のAuto Layout設定

クイズ画面Auto Layout設定をします。下記の図のとおりに設定してください。

Label(問題)のAuto Layout設定

Text View(問題文)のAuto Layout設定

Button(btn_no)のAuto Layout設定

Button(btn_yes)のAuto Layout設定

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

結果表示画面のデザイン

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

Labelの配置

画面上部に以下のとおり、2つLabelを配置します。これらは、コード上の処理から一切変更されない静的なLabelとなります。

Labelプロパティ名 設定値
Text あなたの正答率は
Font 20px
Alignment centor

Labelプロパティ名 設定値
Text あなたの一般常識レベルは
Font 20px
Alignment centor

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

Labelプロパティ名 設定値
Text 0%
Font 40px
Alignment centor

Labelプロパティ名 設定値
Text 普通です
Font 40px
Alignment centor

ImageViewの配置

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

Buttonの設置

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

Buttonプロパティ名 設定値
Type Custom
title (空白)
image btn_go_top

結果画面の Auto Layout設定

結果画面Auto Layoutの設定をします。下記の図のとおりに設定してください。

「トップへ」ボタンの設定

レベル表示Labelの設定

ImageViewの設定

「あなたの一般常識は」Labelの設定

「あなたの正答率は」Labelの設定

「正答率」Labelの設定

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

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

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

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

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

新規View Controllerの作成

QuizViewControllerクラスファイルの作成

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

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

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

入力項目 入力値
Class QuizViewController
Subclass of UIViewController
Also create XIB file チェックを外す
Language Swift

そうすると、ナビゲーターエリアの一覧に今回作成したQuizViewControllerクラス(QuizViewController.swift)が表示されます。

ResultViewControllerクラスファイルの作成

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

入力項目 入力値
Class ResultViewController
Subclass of UIViewController
Also create XIB file チェックを外す
Language Swift

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

新規ViewControllerのアサイン

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

まずは、Main.storyboardを選択し、下図の手順でQuizViewControllerを選びます。

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

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

Storyboard Segueによる画面遷移

3つの画面のデザインとView Controllerのアサインが終わったところで、それぞれの画面を遷移する手法について検討します。

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

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

Storyboard Segueの設定

Storyboard Segueは、遷移元の画面と遷移先の画面を「線」で結ぶことによって設定します。今回は、以下の3つの画面遷移があるので、それぞれに対してStoryboard Segueを設定していきます。

  • スタート画面 → クイズ画面
  • クイズ画面 → 結果表示画面
  • 結果表示画面 → スタート画面

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

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

下図のようにスタートボタンを選択してcontorolキーを押しながらQuizViewControllerの画面にドロップしてください。

クイズ画面の中でマウスを離すと、下記の吹き出しが出てきますのでshow detailを選択してください。

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

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

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

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

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

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

それでは、早速作業を進めていきます。下図のとおりにcontrolキーを押しながらドラッグしてResultViewControllerにドロップしてください。

そのまま「Control」キーを押し続けながら、マウスを遷移先の画面(結果表示画面)にドラッグし、マウスを離します。下記の吹き出しが出てきますのでshow detailを選択してください。

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

Storyboard Segue プロパティ名 設定値
Identifier toResultView

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

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

最後に、ユーザーが結果を見終わったら、スタート最後へ戻れるように、Segueを設定します。まず最初に、もとに戻りたいViewControllerへ以下の記述を行います。ここではViewController.swiftに記述します。

ViewController.swift

// ▼▼ 追加 ▼▼ //

@IBAction func backView(segue: UIStoryboardSegue) {

}

// ▲▲ 追加 ▲▲ //

次に、結果表示画面上に「トップへ」ボタンを設置したかと思いますが、このボタンに対してSegueを設定します。キーボード上のcontrolキーを押しながら、ViewController上のExitというボタンにドラッグしてください。

するとBackViewWithSegue表示されていると思いますのでそれを選択してください。
これで元に戻るための接続が完了しました。

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

UIオブジェクトをコードと結びつける

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

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

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

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

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

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

Result View Controllerのメンバー変数

ここでは、先ほど接続したメンバ変数とクイズ画面から受け渡される値を入れるためのメンバ変数の関連付けをします。結果が下記のようになっていればOKです。

IBOutletの関連付けが終わったらその下にcorrectPercentageの変数を追加してください。

ResultViewController.swift


class ResultViewController: UIViewController {

//正答率表示Label用変数
@IBOutlet weak var percentageLabel: UILabel!

//レベルImage用変数
@IBOutlet weak var levelImage: UIImageView!

//レベルLabel用変数
@IBOutlet weak var levelLabel: UILabel!

// ▼▼ 追加 ▼▼

//クイズ画面から受け渡される値
var correctPercentage = Int()

// ▲▲ 追加 ▲▲

Result View Controllerの初期処理

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

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

ResultViewController.swift

override func viewDidLoad() {
super.viewDidLoad()

// ▼▼ 追加 ▼▼

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

levelLabel.text = "猿レベル"
levelImage.image = UIImage(named:"bad")

} else if correctPercentage >= 30 && correctPercentage < 90 {

levelLabel.text = "一般人レベル"
levelImage.image = UIImage(named:"normal")

} else if correctPercentage >= 90 {

levelLabel.text = "神レベル"
levelImage.image = UIImage(named:"good")

}

//実際の正答率を表示
percentageLabel.text = String(format:"%d %%", correctPercentage)

// ▲▲ 追加 ▲▲

}

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

アイコンに関しては、それぞれのレベルに応じたアイコン画像をlevelImageに設定します。levelImageで表示する画像は、UIImageクラスの引数namedによって指定します。

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

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

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

まず、クイズ問題のデータベースから解説していきます。データベースとは、複数のデータをあるルールに沿って集めたものです。

冒頭で、quiz.csvというファイルをインポートしましたが、これはクイズ問題の問題文と解答を、CSV(Comma Separated Values)形式でまとめたものです。CSVでは、それぞれのデータのまとまりは行毎に記録し、それぞれの列はカンマで区切って表現します。その例を以下に示します。

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

クイズデータのインポート

下図のとおり素材フォルダからquiz.csvをプロジェクトにドラッグしてインポートしてください。

Problemクラスの生成

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

新規クラスの作成

まずは、Clapクラスと同じ要領でProblemという名前の新規クラスを作成します。正しく作成が行えた場合、 以下のようにナビゲーターエリアにProblem.swiftができているはずです。

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

Problemクラスの実装ファイル

それでは早速実装を進めていきましょう。Problem.swiftを実装します。以下通り、メンバー変数を宣言します。

Problem.swift

class Problem: NSObject {

//問題文を管理する変数
var question = String()

//解答(「YES」なら「0」、「NO」なら「1」)を管理する変数
var answer = Int()

}

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

Problem.swift

// ▼▼ 追加 ▼▼

//問題文と解答を格納するメソッド
func setQ(q: String, a: Int) {

question = q

answer = a

}

// ▲▲ 追加 ▲▲

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

Problem.swift

// ▼▼ 追加 ▼▼

//問題文を読み出しするためのメソッド
func getQ() -> String {

return question

}

//解答を読み出しするためのメソッド
func getA() -> Int {

return answer

}

// ▲▲ 追加 ▲▲

QuizViewControllerの実装

次に、QuizViewControllerの実装に移ります。

QuizViewControllerのIBOutlet関連付け

class QuizViewController: UIViewController {

//問題文を格納するための変数
@IBOutlet weak var problemText: UITextView!

//画面起動時に実行されるメソッド
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}


//YESボタンが押された時のメソッド
@IBAction func answerIsTrue(sender: AnyObject) {

}

//NOボタンが押された時のメソッド
@IBAction func answerIsFalse(sender: AnyObject) {

}

}

QuizViewControllerのメンバー変数

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

QuizViewController.swift

class QuizViewController: UIViewController {

@IBOutlet weak var problemText: UITextView!

// ▼▼ 追加 ▼▼

//問題(Problemクラスのインスタンス)を格納する可変配列型の変数
var problemSet = NSMutableArray()

//出題する問題数を管理するint型の変数
var totalProblems = Int()

//現在の進捗(出題済み問題数)を管理するint型の変数
var currentProblem = Int()

//正答数を管理するint型の変数
var correctAnswers = Int()

// ▲▲ 追加 ▲▲

今回、1つの問題文と解答は、先ほど作成したProblemクラスで管理します。そして、全てのProblemクラスをNSMutableArrayクラスで管理します。

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

テキストファイルの読み込み

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

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

QuizViewController.swift

 // ▼▼ 追加 ▼▼

//問題を読み込むためのメソッド
func loadProblemSet() {

// CSVファイルのパスを取得
let path = Bundle.main.path(forResource: "quiz", ofType: "csv")

// 文字コード(UTF-8)を生成
let enc = String.Encoding.utf8

// ファイルのパスの読み込み
let data = NSData(contentsOfFile: path!)

let text = String(NSString(data: data! as Data, encoding: enc.rawValue)!)

// 行ごとに分割し、配列「lines」に格納
let lines = text.components(separatedBy: "\n")

// 問題の数だけ繰り返し
for i in 0..<lines.count {

//問題をカンマで区切って、要素を配列「items」に格納
let items = lines[i].components(separatedBy: ",")

//Problemクラスのインスタンスを生成・初期化し、問題文と答えを格納
let p = Problem()
let q = items[0]
let a = Int(items[1])
p.setQ(q: q, a: a!)

//新たに生成したProblemクラスのインスタンスをproblemSetに追加
problemSet.add(p)
}
}

// ▲▲ 追加 ▲▲

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

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

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

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

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

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

QuizViewController.swift

// ▼▼ 追加 ▼▼

//問題文をシャッフル
func shuffleProblemSet() {

//problemSetに格納された全問題の数を取得
let totalQuestions = problemSet.count

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

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

//乱数を発生
let j = arc4random() % UInt32(i)

//要素を並び替え
problemSet.exchangeObject(at: i-1, withObjectAt: Int(j))

//カウンターを減少させる
i -= 1

}

}

// ▲▲ 追加 ▲▲

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

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

乱数を発生させる場合は、「arc4random()」メソッドを使えばランダムな数字を発生させることができます。

//「0」から「19」までの乱数を発生
let j = arc4random() % 20

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

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

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

Quiz View Controllerの初期処理

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

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

QuizViewController.swift

override func viewDidLoad() {

super.viewDidLoad()

// ▼▼ 追加 ▼▼

//クイズ問題を読み込み
self.loadProblemSet()

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

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

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

//problemSetの最初の要素の問題文をクイズ画面にセット
let questions = problemSet.object(at: currentProblem) as! Problem
problemText.text = questions.getQ()

// ▲▲ 追加 ▲▲
}

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

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

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

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

QuizViewController.swift

// ▼▼ 追加 ▼▼

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

//currentProblemを繰り上げ
currentProblem += 1

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

//次の問題の問題文を提示
let questions = problemSet.object(at: currentProblem) as! Problem
problemText.text = questions.getQ()

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

} else {

//結果表示画面へのSegueを始動
self.performSegue(withIdentifier: "toResultView", sender:self)
}

}

// ▲▲ 追加 ▲▲

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

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

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

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

QuizViewController.swift

// ▼▼ 追加 ▼▼

//結果表示画面へのSegueの発動
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

//正答率を算出
let percentage = correctAnswers * 100 / totalProblems

//ResultViewController(RVC)のインスタンスを作成し、
//RVCクラスのメンバー変数である「correctPercentage」に値を渡す
if segue.identifier == "toResultView" {

let rvc = segue.destination as! ResultViewController
rvc.correctPercentage = percentage

}

}

// ▲▲ 追加 ▲▲

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

「YES」ボタンと「NO」ボタン向けのメソッド

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

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

QuizViewController.swift

//「YES」ボタンが押された場合
@IBAction func answerIsTrue(sender: AnyObject) {

// ▼▼ 追加 ▼▼

//答えを確認し、次の問題を提示
let questions = problemSet.object(at: currentProblem) as! Problem

if questions.getA() == 0 {

correctAnswers += 1;

}

self.nextProblem()

// ▲▲ 追加 ▲▲
}

//「NO」ボタンが押された場合
@IBAction func answerIsFalse(sender: AnyObject) {

// ▼▼ 追加 ▼▼

//答えを確認し、次の問題を提示
let questions = problemSet.object(at: currentProblem) as! Problem

if questions.getA() == 1 {

correctAnswers += 1;

}

self.nextProblem()

// ▲▲ 追加 ▲▲

}

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

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

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

これにて、すべてのコードの記述は完了となります。

ビルドと動作試験

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

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

応用課題編1

作成したクイズアプリに以下の機能を実装してみよう

  • クイズ問題が表示された時に以下の音を鳴らす

出題音ファイル

  • クイズに正解した時に以下の音を鳴らす

正解音ファイル

  • クイズに不正解した時に以下の音を鳴らす

不正解音ファイル

  • 結果表示画面が表示された時に以下の音を鳴らす

結果表示音ファイル

応用課題編2

作成したクイズアプリに以下の機能を実装してみよう

  • クイズ画面の選択ボタンを、4択にする。

まとめ

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

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

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