iosアプリの作成🔰【研修アプリ開発編】

## 目標
iosアプリを作成する

## 前回
iosアプリの作成🔰【環境構築〜プロジェクト作成】
https://qiita.com/chihiro1020/items/4a85c453d4b88b181a7b

## 環境
|項目|バージョン|その他|
|---|---|---|
|iPhone|16.3.1|iphone8|
|Mac|13.4.1|M1|
|Xcode|14.3.1||
|Swift|5.8.1||

## 目次
[・プロジェクト概要](#プロジェクト概要)
[・ディレクトリ構成](#ディレクトリ構成)
[・画面遷移](#画面遷移)
[・問題画面](#問題画面)
[・正誤チェック機能](#正誤チェック機能)
[・全てのコード](#全てのコード)
[・最後に](#最後に)


## プロジェクト概要
プライバシーマークについての知識を深める事のできる、気軽に使えるクイズアプリを作成します。
今回は４択クイズで選択肢を選ぶと正誤が表示される機能を実装します。

## ディレクトリ構成
・トップ画面
・問題画面
・結果画面

以上３つの画面を使って研修アプリを開発します。
プロジェクト直下に元からある``ContentView``に追加で、
``QuestionView``・・・問題画面
``ResultView``　・・・結果画面
``AppConstants``・・・定数管理
３つのファイルを追加しました。
![スクリーンショット 2023-07-13 15.29.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3507621/c52b732c-6293-8143-8381-c0671cba5b84.png)



## 画面遷移

``ContentView``を含めて3つのファイルが用意できたので、
まず``ContentView``から``QuestionView``に遷移できる記述をします。


```swift:Content.swift
var body: some View {
        NavigationStack {
            VStack {
                Text("Pマーク研修クイズ")
                    .padding()
                NavigationLink(
                    "問題画面へ", destination: QuestionView()
                )
            }
        }
    }
```

``var body: some View{}``の中に
``NavigationStack``の中で``NavigationLink``を使ってプッシュ遷移します。

NavigationLinkの()に第一引数はリンクのテキスト、
第二引数に遷移先のViewを記述します。
遷移先の``QuestionView``を第二引数にします。

このまま画面遷移をすると左上に``＜Back``の表示が出ました。
このナビゲーションバーの戻るボタンを削除するため、
QuestionView.swiftに少し追記をします。
```swift:QuestionView.swift
struct QuestionView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text("Hello, World!")
        }
        // Backボタンの削除の記述
        .navigationBarBackButtonHidden(true)
    }
}

```


ContentViewとQuestionViewに追記ができたら、
以下の左側の画面の「問題画面へ」をクリックします。
左側の画面から、右側の画面へ遷移できるようになりました。
![gamenseni.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3507621/c3cb3a89-9c02-2148-4528-b6e545ed6929.png)



## 問題画面

次にQuestionViewに以下の項目を作成します。
・問題文
・選択肢
・選択ボタン
・正しい解答

```swift:QuestionView.swift
import SwiftUI

struct QuestionView: View {
    let question = "日本でのプライバシーマーク制度は、どの組織が管理していますか？"
    let choices = [
        ("A", "独立行政法人 情報処理推進機構（IPA）"),
        ("B", "内閣官房 情報セキュリティセンター（NISC）"),
        ("C", "総務省 情報通信政策局（MIC）"),
        ("D", "一般財団法人 日本情報経済社会推進協会（JIPDEC）")
    ]
    let correctAnswer = "D"

    var body: some View {
        VStack {
            Text(question)
            ForEach(0..<choices.count, id: \.self){ num in
                Text("\(choices[num].0)：\(choices[num].0)")
                    .padding(10)
            }
            .frame(maxWidth: .infinity,
                   alignment: .leading)
            ForEach(0..<choices.count, id: \.self){ num in
                Button{
                    // ここに選択したボタンと正しい解答が合っているかのアクションを記述する
                } label: {
                    Text(choices[num].0)
                        .padding()
                        .frame(width: UIScreen.main.bounds.width*0.9)
                        .background(.gray)
                        .foregroundColor(.white)
                        .padding(.bottom, 5)
                }
            }
        }
        .navigationBarBackButtonHidden(true)
    }
}
```
これで以下のような画面が作成できます。
![スクリーンショット 2023-07-13 15.45.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3507621/5caefa6e-3b60-9c47-4fc1-993ea20a7c1b.png)

分けて説明すると、

・letで問題文、選択肢、正しい解答を定義
（letで定義したものは定数名で呼び出し可）
```swift
let question = "日本でのプライバシーマーク制度は、どの組織が管理していますか？"
```


・選択肢（choices）が複数あるため、個数分ForEachで表示する。
SwiftUIで配列をForEach文で使用する場合、
第一引数に配列
第二引数のidに対して``\.self``を指定することで使用できるようになります。
指定することで、idを配列内の各要素を一意に識別するための識別子として使用できます。
（配列の値が重複する場合は別の手法を使いますが、今回は重複しないのでスルー）

あとは``choices[num]``に``.``をつけてインデックスを指定することで表示ができます。
今回は``A：選択肢の文章``の型で表示するので+を使って``"："``で繋げました。
```swift
// choices.count（選択肢の個数）
ForEach(0..<choices.count, id: \.self){ num in
    Text(choices[num].0 + "：" + choices[num].1)
        .padding(10)
}
```

・選択肢ボタンは選択肢と同じForEachの記述なので割愛

このままだとボタンに何もアクションを設定していないので、
ボタンを押した時の正誤チェック機能を実装します。

## 正誤チェック機能

選択肢ボタンを押した時に、
選んだ選択肢と正しい選択肢が一致しているかチェックする機能を実装します。

記述はボタンの以下のコメントアウトの箇所にします。
``// ここに選択したボタンと正しい解答が合っているかのアクションを記述する``

記述する内容は、
```swift:QuestionView.swift
if choices[num].0 == correctAnswer {
                        print("正解")
                    } else {
                        print("不正解")
                    }
```

以上の記述をした上で、プレビューでボタンを押すと
正しい解答の``D``を選択すると「正解」
それ以外を選択すると「不正解」とprintされるはずです。

このプリント部分を正解か不正解かで画像の出し分けする記述が以下です。

```swift:QuestionView.swift
    // ~~~ 既存の記述に以下を追加 ~~~

    @State var showAnswer = false
    
    @State var judgeImageName = "circlebadge"
    
    @State var judgeImageColor = Color.red

    var body: some View {
        ZStack{
            VStack {
                Text(question)
                ForEach(0..<choices.count, id: \.self){ num in
                    Text(choices[num].0 + "：" + choices[num].1)
                        .padding(10)
                }
                .frame(maxWidth: .infinity,
                       alignment: .leading)
                ForEach(0..<choices.count, id: \.self){ num in
                    Button{
                        if choices[num].0 == correctAnswer {
                            judgeImageName = "circlebadge"
                            judgeImageColor = Color.red
                        } else {
                            judgeImageName = "multiply"
                            judgeImageColor = Color.blue
                        }
                        showAnswer = true
                        Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false){
                            _ in showAnswer = false}
                    } label: {
                        Text(choices[num].0)
                            .padding()
                            .frame(width: UIScreen.main.bounds.width*0.9)
                            .background(.gray)
                            .foregroundColor(.white)
                            .padding(.bottom, 5)
                    }
                }
            }
            if showAnswer {
                Image(systemName: judgeImageName)
                    .foregroundColor(judgeImageColor)
                    .font(.system(size: 100, weight: .bold))
            }
        }
        .navigationBarBackButtonHidden(true)
    }
}
```

まず、
``showAnswer``・・・正誤の表示、非表示を切り替える変数
``judgeImageName``・・・画像の名前
``judgeImageColor``・・・画像の色
この３つの変数を宣言します。

```swift
@State var 変数名 = 初期値
```
定数はlet、変数はvarで宣言、
そして変数の値が変わった後にViewを再描画したいので、
プロパティに``@State``をつけます。

これでボタンを押した時変数が変更されるとViewが再描画されます。

次に問題画面のViewに正誤の画像を重ねて表示をしたいので
``VStack``の外側に``ZStack``を追加します。
``VStack``の閉じタグ直後に重ねて表示したい記述をします。

```swift
Image(systemName: judgeImageName)
    .foregroundColor(judgeImageColor)
    .font(.system(size: 100, weight: .bold))
```
このままだと常に表示された状態になってしまうので、if文を追記します。
showAnswerがtrueの場合は表示、falseの場合は非表示になります。
```swift
if showAnswer {
    Image(systemName: judgeImageName)
        .foregroundColor(judgeImageColor)
        .font(.system(size: 100, weight: .bold))
}
```
showAnswerを宣言する時にfalseをセットしているので、今は表示されないと思います。

解答を選択した時に表示をさせたいため、ボタンの処理の中にtrue,falseの切り替えを記述します。

```swift
showAnswer = true
```
このままだと一度表示すると表示されたままになってしまうので、
scheduledTimerメソッドを使って0.5秒後にfalseに切り替える処理を記述します。

```swift
showAnswer = true
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false){
_ in showAnswer = false}
```
``withTimeInterval``に秒数を指定、
``repeats``に繰り返し処理を行うか、今回は一度でいいのでfalseします。

これで解答を選択後、正誤が表示され0.5秒後に非表示になる処理ができました。



## 全てのコード
ここまでの記述をまとめたものがこちらです。

```swift:Content.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
            NavigationStack {
                VStack {
                    Text("Pマーク研修クイズ")
                        .padding()
                    NavigationLink(
                        "問題画面へ", destination: QuestionView()
                    )
                }
            }
        }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

```

```swift:Question.swift
import SwiftUI

struct QuestionView: View {
    let question = "日本でのプライバシーマーク制度は、どの組織が管理していますか？"
    let choices = [
        ("A", "独立行政法人 情報処理推進機構（IPA）"),
        ("B", "内閣官房 情報セキュリティセンター（NISC）"),
        ("C", "総務省 情報通信政策局（MIC）"),
        ("D", "一般財団法人 日本情報経済社会推進協会（JIPDEC）")
    ]
    
    let correctAnswer = "D"
    
    @State var showAnswer = false
    
    @State var judgeImageName = "circlebadge"
    
    @State var judgeImageColor = Color.red

    var body: some View {
        ZStack{
            VStack {
                Text(question)
                ForEach(0..<choices.count, id: \.self){ num in
                    Text(choices[num].0 + "：" + choices[num].1)
                        .padding(10)
                }
                .frame(maxWidth: .infinity,
                       alignment: .leading)
                ForEach(0..<choices.count, id: \.self){ num in
                    Button{
                        if choices[num].0 == correctAnswer {
                            judgeImageName = "circlebadge"
                            judgeImageColor = Color.red
                        } else {
                            judgeImageName = "multiply"
                            judgeImageColor = Color.blue
                        }
                        showAnswer = true
                        Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false){
                            _ in showAnswer = false}
                    } label: {
                        Text(choices[num].0)
                            .padding()
                            .frame(width: UIScreen.main.bounds.width*0.9)
                            .background(.gray)
                            .foregroundColor(.white)
                            .padding(.bottom, 5)
                    }
                }
            }
            if showAnswer {
                Image(systemName: judgeImageName)
                    .foregroundColor(judgeImageColor)
                    .font(.system(size: 100, weight: .bold))
            }
        }
        .navigationBarBackButtonHidden(true)
    }
}


struct questionView_Previews: PreviewProvider {
    static var previews: some View {
        QuestionView()
    }
}

```

## 最後に

最後までお読みいただきありがとうございます。
今回、最低限の処理のみ記述して、4択クイズのアプリを作成しました。
クイズの問題数を増やして研修で使えるようにブラッシュアップしていきます。