2018/01/23
Swift – 4択クイズアプリのサンプル(Swift3.0)
備忘録。
四択クイズのサンプルコード。
最終的にはテキストフィールドにフリーで入力した内容との整合性チェックを行いたい。
問題文の読み出し
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
import Foundation class QuestionData { // 問題文 var question: String // 選択肢1 var answer1: String // 選択肢2 var answer2: String // 選択肢3 var answer3: String // 選択肢4 var answer4: String // 正解の番号 var correctAnswerNumber: Int // ユーザが選択した選択肢の番号 var userChoiceAnswerNumber: Int? // 問題文の番号 var questionNo: Int = 0 // クラスが生成された時の処理 init(questionSourceDataArray: [String]) { question = questionSourceDataArray[0] answer1 = questionSourceDataArray[1] answer2 = questionSourceDataArray[2] answer3 = questionSourceDataArray[3] answer4 = questionSourceDataArray[4] correctAnswerNumber = Int(questionSourceDataArray[5])! } // ユーザが選択した答えが正解かどうか判定する func isCorrect() -> Bool { // 答えが一致しているかどうか判定する if correctAnswerNumber == userChoiceAnswerNumber { // 正解 return true } // 不正解 return false } } // クイズデータ全般の管理と生成を管理するクラス class QuestionDataManager { // シングルトンのオブジェクトを生成 static let sharedInstance = QuestionDataManager() // 問題を格納するための配列 var questionDataArray = [QuestionData]() // 現在の問題のインデックス var nowQuestionIndex = 0 // 初期化処理 private init() { // シングルトンであることを保証するためにprivateで宣言しておく } // 問題文の読み込み処理 func loadQuestion() { // 格納済みの問題文であればいったん削除しておく questionDataArray.removeAll() // 現在の問題のインデックスを初期化 nowQuestionIndex = 0 // csvファイルパスを取得 guard let csvFilePath = Bundle.main.path(forResource: "question", ofType: "csv") else { // csvファイルなし print("csvファイルが存在しません") return } do { let csvStringData = try String(contentsOfFile: csvFilePath, encoding: String.Encoding.utf8) // csvデータを1行ずつ読み込む csvStringData.enumerateLines { (line, stop) in // カンマ区切りで分割 let questionSourceDataArray = line.components(separatedBy: ",") // 問題データを格納するオブジェクトを作成 let questionData = QuestionData(questionSourceDataArray: questionSourceDataArray) // 問題を追加 self.questionDataArray.append(questionData) // 問題番号を設定 questionData.questionNo = self.questionDataArray.count } } catch let error { print("csvファイル読み込みエラーが発生しました\(error)") return } } // 次の問題を取り出す func nextQuestion() -> QuestionData? { if nowQuestionIndex < questionDataArray.count { let nextQuestion = questionDataArray[nowQuestionIndex] nowQuestionIndex += 1 return nextQuestion } return nil } } |
開始画面
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 |
import UIKit class StartViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. // 問題文の読込 QuestionDataManager.sharedInstance.loadQuestion() // 遷移先画面の呼び出し guard let nextViewController = segue.destination as? QuestionViewController else { // 取得できずに終了 return } // 問題文の取り出し guard let questionData = QuestionDataManager.sharedInstance.nextQuestion() else { // 取得できずに終了 return } // 問題文のセット nextViewController.questionData = questionData } // タイトルに戻ってくるときに呼び出される処理 @IBAction func goToTitle(_ segue: UIStoryboardSegue) { } } |
問題と回答チェック
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
import UIKit import AudioToolbox class QuestionViewController: UIViewController { var questionData: QuestionData! @IBOutlet weak var questionNoLabel: UILabel! // 問題番号ラベル @IBOutlet weak var questionTextView: UITextView! // 問題文テキストビュー @IBOutlet weak var answer1Button: UIButton! // 選択肢1ボタン @IBOutlet weak var answer2Button: UIButton! // 選択肢2ボタン @IBOutlet weak var answer3Button: UIButton! // 選択肢3ボタン @IBOutlet weak var answer4Button: UIButton! // 選択肢4ボタン @IBOutlet weak var correctImageView: UIImageView! // 正解時のイメージビュー @IBOutlet weak var incorrectImageView: UIImageView! // 不正解時のイメージビュー override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. // 初期データ設定処理。前画面で設定済みのquestionDataから値を取り出す questionNoLabel.text = "Q.\(questionData.questionNo)" questionTextView.text = questionData.question answer1Button.setTitle(questionData.answer1, for: UIControlState.normal) answer2Button.setTitle(questionData.answer2, for: UIControlState.normal) answer3Button.setTitle(questionData.answer3, for: UIControlState.normal) answer4Button.setTitle(questionData.answer4, for: UIControlState.normal) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // 選択肢1をタップ @IBAction func tapAnswer1Button(_ sender: Any) { questionData.userChoiceAnswerNumber = 1 // 選択した答えの番号を保存する goNextQuestionWithAnimation() // 次の問題に進む } // 選択肢2をタップ @IBAction func tapAnswer2Button(_ sender: Any) { questionData.userChoiceAnswerNumber = 2 // 選択した答えの番号を保存する goNextQuestionWithAnimation() // 次の問題に進む } // 選択肢3をタップ @IBAction func tapAnswer3Button(_ sender: Any) { questionData.userChoiceAnswerNumber = 3 // 選択した答えの番号を保存する goNextQuestionWithAnimation() // 次の問題に進む } // 選択肢4をタップ @IBAction func tapAnswer4Button(_ sender: Any) { questionData.userChoiceAnswerNumber = 4 // 選択した答えの番号を保存する goNextQuestionWithAnimation() // 次の問題に進む } // 次の問題にアニメーション付きで進む func goNextQuestionWithAnimation() { // 正解しているか判定する if questionData.isCorrect() { // 正解のアニメーションを再生しながら次の問題へ遷移する goNextQuestionWithCorrectAnimation() } else { // 不正解のアニメーションを再生しながら次の問題へ遷移する goNextQuestionWithIncorrectAnimation() } } // 次の問題に正解のアニメーション付きで遷移する func goNextQuestionWithCorrectAnimation() { // 正解を伝える音を鳴らす AudioServicesPlayAlertSound(1025) // アニメーション UIView.animate(withDuration: 2.0, animations: { // アルファ値を1.0に変化させる(初期値はStoryboardで0.0に設定済み) self.correctImageView.alpha = 1.0 }) { (Bool) in self.goNextQuestion() // アニメーション完了後に次の問題に進む } } // 次の問題に不正解のアニメーション付きで遷移する func goNextQuestionWithIncorrectAnimation() { // 不正解を伝える音を鳴らす AudioServicesPlayAlertSound(1006) // アニメーション UIView.animate(withDuration: 2.0, animations: { // アルファ値を1.0に変化させる(初期値はStoryboardで0.0に設定済み) self.incorrectImageView.alpha = 1.0 }) { (Bool) in self.goNextQuestion() // アニメーション完了後に次の問題に進む } } // 次の問題へ遷移する func goNextQuestion() { // 問題文の取り出し guard let nextQuestion = QuestionDataManager.sharedInstance.nextQuestion() else { // 問題文がなければ結果画面へ遷移する // StoryboardのIdentifierに設定した値(result)を指定して // ViewControllerを生成する if let resultViewController = storyboard?.instantiateViewController(withIdentifier: "result") as? ResultViewController { // StoryboardのSegueを利用しない明示的な画面遷移処理 present(resultViewController, animated: true, completion: nil) } return } // 問題文がある場合は次の問題へ遷移する // StoryboardのIdentifierに設定した値(question)を設定して // ViewControllerを生成する if let nextQuestionViewController = storyboard?.instantiateViewController(withIdentifier: "question") as? QuestionViewController { nextQuestionViewController.questionData = nextQuestion // StoryboardのSegueを利用しない明示的な画面遷移処理 present(nextQuestionViewController, animated: true, completion: nil) } } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. } */ } |
回答ページ
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 |
import UIKit class ResultViewController: UIViewController { @IBOutlet weak var correctPercentLabel: UILabel! // 正解率ラベル override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. // 問題数を取得する let questionCount = QuestionDataManager.sharedInstance.questionDataArray.count // 正解数を取得する var correctCount: Int = 0 // 正解数を計算する for questionData in QuestionDataManager.sharedInstance.questionDataArray { if questionData.isCorrect() { // 正解数を増やす correctCount += 1 } } // 正解率を計算する let correctPercent: Float = (Float(correctCount) / Float(questionCount)) * 100 // 正解率を小数第一位まで計算して画面に反映する correctPercentLabel.text = String(format: "%.1f", correctPercent) + "%" } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. } */ } |
お知らせ
ヒヨコ歩数計という歩きながらヒヨコが育っていくアプリを作って、いろんな方に結構使ってもらっています。RealmSwift, Admobの動画・インステ・バナー広告、UICollectionView、iOS-Charts、UITableViewを使用しているので、是非ご利用ください!
とても参考になりました。
質問ですが、間違えた問題だけを再度やり直すようにしたいのですが、アドバイスをいただけるとありがたいです。
よろしくお願いいたします。
すみません、別からの引用メインなのですべて理解しているわけではないのですが、、
下記で実現できないでしょうか?
1.間違えた問題用の配列を作成して、間違えたタイミングでその問題を配列に格納して、
やり直す際には、その配列を参照する
2.問題文をcsv -> 配列に格納する際に正解フラグを作っておき、問題回答毎にそのフラグを更新する。
次に問題を読み出すときには、配列のfilterで正解フラグ == falseの問題を読み出す。
ご確認のほどお願いいたします。