講義の目標
- CSVファイルからデータを取得する方法について理解すること
- 可変配列(Mutable Array)が利用できるようになること
- 乱数の生成と利用方法をマスターすること
- 複数の画面を持つアプリの制作とStoryboard Segueによる画面遷移ができるようになること
はじめに
今回の課題アプリはクイズ
となります。このアプリは複数の画面を持つアプリ
となります。スタート画面
から始まり、ボタン操作によってクイズ画面
へ画面遷移しされ、クイズが開始されます。ユーザーは10問の一般常識問題(2択方式)に挑戦し、全問題を解き終わると、結果表示画面
へ画面遷移し、成績に応じて「一般常識レベル」が表示されます。
完成品のサンプルは以下よりダウンロードできます。イメージが湧かない方は是非ご確認ください。
それでは、早速開発を始めて行きましょう!
プロジェクトの立ち上げと設定
新しいアプリを作る際は、まず、新規プロジェクトを立ち上げとアプリの設定を行います。
新規プロジェクトの立ち上げ
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しか含んでいません。そこで、クイズ画面用
のView
をMain.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_yes
とbtn_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つの画面のうち、最後の結果表示画面
のデザインに移ります。クイズ画面
同様、結果表示画面用
のViewController
をMain.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
属性をCustom
、Image
属性を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
のひな形が作成されました。次に、新たに作ったQuizViewController
とResult 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
と組み合わせることで、処理を比較的に簡単に実装することができます。これまで、Button
やLabel
等を配置する際、それらをコード上のメソッドやインスタンスと関連付けることをやってきました。Storyboard Segue
も同じ要領で、予めInterfaceBuilder
を用いてStoryboard
上に設定し、コード上から任意のタイミングでそれを発動させることができるのです。
それでは、早速作業を進めていきます。下図のとおりにcontrolキーを押しながらドラッグしてResultViewController
にドロップしてください。
そのまま「Control」キーを押し続けながら、マウスを遷移先の画面(結果表示画面
)にドラッグし、マウスを離します。下記の吹き出しが出てきますのでshow detail
を選択してください。
ここまでの作業を正しく行った場合、以下のようにクイズ画面と結果表示画面を結ぶ矢印が表示されます。このSegue(矢印)
をクリックし、インスペクターを開き、Identifier(識別子)
を付けましょう。識別子はtoResultView
とします。
Storyboard Segue プロパティ名 | 設定値 |
---|---|
Identifier | toResultView |
コード上からこの画面遷移(Segue)を発動させる際は、先程設定した識別子であるtoResultView
を参照することで呼び出せます。
結果表示画面からスタート画面への遷移
最後に、ユーザーが結果を見終わったら、スタート最後へ戻れるように、Segue
を設定します。まず最初に、もとに戻りたいViewController
へ以下の記述を行います。ここではViewController.swift
に記述します。
ViewController.swift
// ▼▼ 追加 ▼▼ // |
次に、結果表示画面上に「トップへ」ボタンを設置したかと思いますが、このボタンに対して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
|
Result View Controllerの初期処理
結果表示画面では、呼び出された段階で、correctPercentage
に正答率が格納されています。よって、今回は立ち上がり時に呼ばれるoverride func viewDidLoad()
メソッド内で、正答率に応じて画面をつくり上げていきます。なお。データを受け渡す実際の処理はクイズ画面のコーディングを行う際に解説します。
以下のとおり、ResultViewController.swiftにコードを記述します。
ResultViewController.swift
override func viewDidLoad() { |
ここでは、正答率に応じて「一般常識レベル」の位とアイコンの場合分けを行なっています。正答率が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 { |
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.swiftoverride 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の提供する、便利かつ高機能なフレームワークを使ったアプリを作っていきましょう。