Swift初心者によるSwift初心者のためのアプリ開発

[アイスタイル Advent Calendar 2022](https://qiita.com/advent-calendar/2022/istyle) 21日目を担当させて頂きます、こたちゃんです:santa_tone1::gift:

## はじめに

私は、3週間ほど前からiOSのアプリチームで研修をしている新卒のエンジニアになります👩‍💻
普段は主にバックエンドエンジニアしてます！

これまで、Swiftに一度も触れたことがなかったのですが、学んで２週間ほどで、、なんだこれ、、楽しすぎる！！！と恋をしてしまうような体験:heart_eyes:をしてしまったため
**触れたことないいんだけどやってみるか！**
といった人を増やすべく、この記事を書こうと思いました:sunny:

「ボタンを押したらおみくじの結果が表示される」という初心者の方でも作れるようなアプリを
チュートリアルのような形式で作成していこうと思います:muscle_tone1:

## 作成するアプリ

<img width="300" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/870f49a8-8151-7577-323e-35efb8bdba92.gif">


## 仕様・要件

- おみくじを引く画面
  - おみくじアプリのタイトル、「引く」ボタンがある
  - 「引く」ボタンを押下すると結果画面へ遷移する
- 結果画面
  - 結果画面では大吉、中吉、小吉、吉、末吉、凶、大凶の7種類の結果が表示される
  - 結果は画像とテキストの両方が表示され、内容が一致している
  - 「戻る」ボタンでおみくじを引く画面へ戻る
  - 「もう一度」ボタンで結果の表示が変わる

## このアプリ開発で学べること
- 画面遷移(モーダル遷移)
- 遷移元に戻る処理
- UIImage
- UIImageView
- 配列
- 乱数の生成

## 実装手順
[0.アプリの雛形作成](#0-アプリの雛形作成)
[1.スタート画面の作成](#1-スタート画面の作成)
[2.もう一つの画面を追加する](#2-もう一つの画面を追加する)
[3.画面遷移の設定](#3-画面遷移の設定)
[4.戻るボタンの作成](#4-戻るボタンの作成)
[5.ランダムに表示させるコントローラの書き方](#5-ランダムに表示させるコントローラの書き方)
[6.画像を表示する](#6-画像を表示する)
[7.もう一度ボタンを押すと再度抽選される](#7-もう一度ボタンを押すと再度抽選される)


## 0. アプリの雛形作成

xcodeを立ち上げると
「Welcome to Xcode」と表示されおり、その下に三つの項目があります。

今回は新しいiOSアプリのプロジェクトを作成するため、下記を選択してください。
![スクリーンショット 2022-12-14 16.49.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/9f608f6f-91b1-281d-8e12-b8c1d7e5fed1.png)

iOSのAppを選択して「Next」ボタンを押してください。
![スクリーンショット 2022-12-15 19.03.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/e2d00d19-20a0-6ddf-fb75-208cb94afd3a.png)

次にProduct Nameを入力します。
今回はおみくじのアプリですので「OmikujiApp」とします。
Interfaceが`Storyboard`かどうか確認した後に「Next」ボタンを押してください。
![スクリーンショット 2022-12-20 13.08.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/87f31615-0683-79a0-26a1-84ad3cc7a5d8.png)


ここまででアプリの雛形作成が完了しました。
さっそく、実装を始めていきます。

## 1. スタート画面の作成
まずは左側の並んでいるファイルツリーの中から**Main**を開いて下さい。
真っ白のiPhoneがでてくるはずです。

① iPhoneの上に`View Controller`と書かれた枠があるのでクリック
② ＋ボタンをクリック
③ `Label`をクリックしたままドラッグ&ドロップ

![スクリーンショット 2022-12-16 11.30.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/2c53c57e-1413-1da3-d466-25764c56f461.png)

真っ白なiPhoneにドラッグ&ドロップすると`Label`が追加されます。
![スクリーンショット 2022-12-16 11.30.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/b5d178a1-fe05-13af-23ab-95be97e55e75.png)
`Label`の文字を変更しましょう。
右上に並んでいるメニューの右から3つ目をクリックするとTextを入力するフィールドがあります。
TextをLabelから「おみくじアプリ」に変更してください。

:::note
他のやり方として
要素を直接ダブルクリックして変更もできます。
:::

![スクリーンショット 2022-12-16 11.40.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/6aa19212-8150-583b-3def-cae779664ee4.png)


続いて、おみくじを引くためのボタンを設置していきます。
先ほどの`Label`と同じようにドラッグ&ドロップして設置してください。

![スクリーンショット 2022-12-16 11.42.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/d744db0e-2939-7023-5717-7a1da38c3585.png)
以下のようになっていれば大丈夫です。
<img width="400" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/a936a0c9-1228-8d9b-c3ca-3ef9307f2903.png">

先ほどと同じようにテキストを変更したいと思います。
右上に並んでいるメニューの右から3つ目をクリックするとTitleを入力するフィールドがあります。
Titleを`button`から「引く」に設定してください。
![スクリーンショット 2022-12-16 11.43.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/05b3eebb-4463-2b76-b0aa-22f8620ff1de.png)

## 2. もう一つの画面を追加する
右上の＋ボタンから
View Conntrollerを検索し、作業中のiPhoneの横にドラッグ&ドロップをします。
![スクリーンショット 2022-12-16 11.49.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/823ae87e-60f7-b2fd-0381-1065446972f7.png)

下記の画像のように横に並べれたら大丈夫です。

:::note
クリックしたまま動かすと移動させることが出来ます。
:::

![スクリーンショット 2022-12-16 11.52.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/4d2223ae-193f-6cfe-5dad-f5d08c9e3eca.png)

左側のファイルが並んでいるところで、右クリックして「NewFile」を選択
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/27f66d25-1423-58f5-78fb-a7583ecb1377.png">

`Swift File`を選択し「Next」ボタンを押してください。
![スクリーンショット 2022-12-16 11.54.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/ca43583d-9ec3-d96f-5293-5daee176140b.png)
結果を表示する画面になりますので
`ResultViewController`という命名にして「Create」ボタンを押します。
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/d1080ca0-7ba0-b562-5dbc-21c3db8fcf98.png">

続いて、`Storyboard`と繋げていきます。
右側のiPhoneを選択してください。

① 右上に並んでいるメニューの右から4つ目をクリックすると`Custom Class`がでてきます。
② Classがプルダウンで選択できますので先ほど作成した`ResultViewController`を選択します。
![スクリーンショット 2022-12-16 11.59.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/15e331fd-5680-4112-3392-5f36d7a2466c.png)

上記は複数の画面を一つのStoryboardで管理していく方法です。
それ以外にも１つの画面に1つのStoryboardを用意する方法もあります。
今回はそちらの説明は省略させて頂きます。
気になった方はぜひ、ググってみてください！

## 3. 画面遷移の設定

続いて、画面を遷移するための設定を行います。
今回は「引く」ボタンをおすと結果の画面に移動するように設定します。

「引く」ボタンを選択します。
Controlキーを押しながら、「引く」ボタンを遷移の画面までドラッグします。
遷移先の画面で離すと以下のようなメニューが出てくるので、
今回は「Present Modally」を選択してください。
![スクリーンショット 2022-12-16 12.12.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/e6296561-64ef-2869-e7ac-16a900333a11.png)

すると画面の間に矢印が出てきます。
この矢印は`セグエ`と言い、画面遷移の流れを示します。
セグエが表示されているかどうか確認して下さい。
右側の**iPhoneの上部分**も変化していることがわかると思います。
![スクリーンショット 2022-12-16 12.12.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/00b53f72-0c82-25e8-8e8c-03e34c7cf16e.png)
ここまでの作業で「引く」ボタンをクリックすると
モーダルでの遷移を行うところまで実装することができました。

## 4. 戻るボタンの作成

遷移先にもボタンを設置していきます。
`Button`を選択し、ドラッグ&ドロップします。
![スクリーンショット 2022-12-16 12.16.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/ce6802b3-33b3-55ab-fbe7-12a2d2821d97.png)

右上に並んでいるメニューの右から3つ目をクリックするとTitleを変更させることができます。
`button`から「戻る」に変更します。
![スクリーンショット 2022-12-16 12.18.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/5edc30a5-5cbf-305a-169b-27fb5142bd8c.png)

#### 「戻る」ボタンのコントローラーの記述

今回は「戻る」ボタンですので、関数名を`backButtonAction`と名付けます。

```swift:ResultViewController.swift
@IBAction private func backButtonAction() {
    dismiss(animated: true)
}
```


`＠IBAction`とは、
ユーザーがUIに対して行うアクションを関連付けるために利用する関数につける属性です。

`dismiss()`とは、
ViewController自身を破棄するメソッドです。
遷移元の上に上書きしていた遷移先画面が破棄された結果、元の画面に戻ります。

`@animated`とは、
ボタンを押下した際のアニメーションの有無を設定できます。

続いて、`Storyboard`と繋げていきます。
画面の右上にある下記のアイコンを押すと2画面になります。
繋げるために2画面にしてください。
![スクリーンショット 2022-12-16 12.32.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/c939c2a7-892d-1e8c-64cc-b9e8e1eab7fe.png)

2画面にした後、
左側にResultViewController
右側にStoryboard を配置します。

:::note warn
逆でも問題ありません。
:::

backButtonActionをControlキーを押しながら、「戻る」ボタンの画面までドラッグします。
![スクリーンショット 2022-12-16 12.27.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/5cbbfd5e-b331-0171-f484-6dc82b3b3193.png)

`Touch Up Inside`を選択します。
Touch Up Insideは、
ボタンを押したあとにボタンの近くで指を離したときにメソッドが呼び出されるものです。

正しく繋がっている場合は下記のように表示されます。
![スクリーンショット 2022-12-16 12.28.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/0ed50cb2-8df5-8c86-350c-3f218418fd22.png)

ここまでで以下の画面遷移ができるようになりました。
```
・「引く」を押したら結果の画面に遷移する
・「戻る」を押したらおみくじを引くの画面に遷移する
```

## 5. ランダムに表示させるコントローラの書き方

続いて「引く」ボタンを押すと
ランダムに大吉、中吉、小吉、吉、末吉、凶、大凶の7種類のいずれかを結果として表示させていきます。

＋ボタンからLabelを選択、Labelをクリックしたままドラッグ&ドロップします。
![スクリーンショット 2022-12-20 0.29.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/f6beb33e-ec60-1017-4676-024a01f79751.png)


次にコントローラーに記述していきます。
下記のように定義します。

```swift:ResultViewController.swift
@IBOutlet private weak var resultLabel: UILabel!
```

一つずつ簡単に説明します。
`@IBOutlet`
IBとはInterface Builderの略でつまりStroyBoardのことを指します。
`@IBOutlet`とはStoryBoardに配置された部品をViewController内で扱うための接続を行います。

`private`
この部品の公開範囲です。
この部品は今編集しているViewController以外では呼び出されることがないのでprivateにしています。

`weak`
弱参照の意味です。

`var label: `
よく見るSwiftの変数の宣言と同じです。
ViewController内ではStoryBoard内に配置された部品を変数として扱います。
変数なので名前は自由に変えられます。 例 `var numberLabel`

`UILabel!`
よく見るSwiftの型宣言と同じです。`var hoge: Int`のInt部分にあたります。

続いて表示するための文字列を配列にしていきます。

```swift:ResultViewController.swift
private let omikujiText: [String] = [
    "大吉",
    "中吉",
    "小吉",
    "吉",
    "末吉",
    "凶",
    "大凶"
]
```

配列では整数から文字列、クラスなどのあらゆる種類のデータ形式を格納することが可能です。
配列を作成する方法は簡単で、
以下のように（コンマ）で区切ったリストを[]で囲むだけで作成することができます。

```swift
書き方（型指定なし）
let 配列名 = [値1, 値2, 値3, ……]
```

```swift
書き方（型指定）：
let 配列名: [Int] =  [値1, 値2, 値3, ……]
```

ランダムにラベルの文言を変える関数を作成していきます。
```swift:ResultViewController.swift
private func setOmikujiResult() {
    let randomInt = Int.random(in: 0..<7)
    resultLabel.text = String(omikujiText[randomInt])
}
```

分解して説明していきます。

関数は以下のように定義します。
`private`は先ほども出てきたように公開範囲を限定します。
関数をよびだす時は`関数名()`
```swift
private func 関数名() {
}
```

Swiftでランダムな数字を生成するには独自メソッドが用意されています。
下記のように指定するとランダムな数字を返してくれます。
今回は配列に要素が７つあるため下記のように指定しています。
```swift
Int.random(in: 0..<7) // 0〜6まででランダムな数字（整数）を返す
```

先ほど定義した`resultLabel`に.textとするとUILabelのtextを変更することができます。
`String()`で型を文字列と指定しています。
`omikujiText[randomInt]`は配列[ランダムな数値]となっており
omikujiText[0]の場合、**大吉**が代入されます。
```swift
resultLabel.text = String(omikujiText[randomInt])
```

先ほど作成した関数を下記で`setOmikujiResult()`と呼び出します。
`viewDidLoad()`はViewControllerの保持しているviewにアクセスしたときに呼び出されます。
```swift
override func viewDidLoad() {
    super.viewDidLoad()
    setOmikujiResult()
}
```

Storyboardと繋げていきます。
下記のように◉に変化していれば繋がっています。
![スクリーンショット 2022-12-20 1.18.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/e7107b4d-c72f-d350-0414-d0c73e72e5ca.png)

確認として下記のようになっていれば大丈夫です。
![スクリーンショット 2022-12-20 1.22.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/938222d2-31ef-ba37-9ed3-5a59cb204ca8.png)

ここまででコードは以下のようになっています。
```swift:ResultViewController.swift
//
//  ResultViewController.swift
//  OmikujiApp
//
//

import UIKit

final class ResultViewController: UIViewController {
    
    @IBOutlet private weak var resultLabel: UILabel!
    
    private let omikujiText: [String] = [
        "大吉",
        "中吉",
        "小吉",
        "吉",
        "末吉",
        "凶",
        "大凶"
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        setOmikujiResult()
    }
    
    @IBAction private func backButtonAction() {
        dismiss(animated: true)
    }

    private func setOmikujiResult() {
        let randomInt = Int.random(in: 0..<7)
        resultLabel.text = String(omikujiText[randomInt])
    }
}
```

## 6. 画像を表示する
次におみくじの結果を画像で表示する設定を行なっていきます。

右上の＋ボタンから`Image View`を探してドラッグ&ドロップをします。
![スクリーンショット 2022-12-20 1.23.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/ba208456-54c9-a585-6e1a-542f94c8f0e2.png)

`Content Mode`は「Aspect Fit」にしておきます。
Aspect Fitと指定しておくと、縦横の比率を保ったまま収まるサイズに拡大縮小してくれます。
縦横比で余った分は空白になります。
そのため、画像が伸びて見えたり比率がおかしく見えるなどが起きないようにしてくれます。
![スクリーンショット 2022-12-20 2.22.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/4ebdc1d2-364e-eefb-79de-7f4d0004d53b.png)

アスペクト比などについて詳しいqiitaの記事がありましたので参考にしてみてください。

https://qiita.com/Saayaman/items/a23519ff5a8ad287cf20

今回使用する画像はどんな画像でも良いです。
大吉 フリー素材🔍 などで検索して保存した画像を`Assets`に格納してください。
![スクリーンショット 2022-12-20 1.26.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/1f86c61e-fc01-cd2e-989a-ee1a1484dc00.png)

Assetsに格納した画像は
Scalesを「Single Scale」に設定します。
![スクリーンショット 2022-12-16 12.38.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/2e09ec7f-3335-0bf5-a9a7-de2638336421.png)

次にコントローラーに記述していきます。
まずは、下記のように定義します。　`resultImage`と命名しました。
```swift:ResultViewController.swift
@IBOutlet private weak var resultImage: UIImageView!
```

先ほどのラベルと同じように配列を作成していきます。
今回は`Assets`に格納した画像のファイル名を記述して下さい。

```swift:ResultViewController.swift
private let omikujiImage: [String] = [
    "daikichi",
    "chuukichi",
    "syoukichi",
    "kichi",
    "suekichi",
    "kyou",
    "daikyou"
]
```

下記に追記していきます。
今回はtextではなく.imageで画像の変更を行うことができます。
そして画像をrandomIntからランダムに選択しています。
```diff_swift:ResultViewController.swift
private func setOmikujiResult() {
    let randomInt = Int.random(in: 0..<7)
    resultLabel.text = String(omikujiText[randomInt])
+   resultImage.image = UIImage(named: omikujiImage[randomInt])
}
```

Storyboardと繋げていきます。
下記のように◉に変化していれば繋がっています。
![スクリーンショット 2022-12-20 2.19.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/e12807b9-1cfc-adec-bab5-061a2cb3577d.png)


確認として下記のようになっていれば大丈夫です。
![スクリーンショット 2022-12-20 1.37.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/fb732b67-a64e-9e58-523f-a57230ee2d34.png)

## 7. もう一度ボタンを押すと再度抽選される

まずは、「もう一度」ボタンの作成をします。
ドラッグ&ドロップでボタンを設置した後、`Button`から「もう一度」に変更します。
![スクリーンショット 2022-12-20 2.17.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/e7150d01-c8e6-501a-c703-310252091111.png)

今回は、「もう一度」ボタンを押した際にアクションが起きるため
`againButtonAction`と命名します。
下記はボタンが押された瞬間、　`setOmikujiResult()`が呼び出されるようにしています。
```swift
@IBAction private func againButtonAction() {
    setOmikujiResult()
}
```

記述ができたら、ドラッグ&ドロップでボタンとアクションを紐付けます。
下記のボタンが◉になれば大丈夫です。
![スクリーンショット 2022-12-20 2.18.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2098611/3b2f3d32-ed92-983d-e9de-a4151fa4af7d.png)


## 完成コード

```swift:ResultViewController.swift
//
//  ResultViewController.swift
//  OmikujiApp
//

import UIKit

final class ResultViewController: UIViewController {
    
    @IBOutlet private weak var resultLabel: UILabel!
    @IBOutlet private weak var resultImage: UIImageView!
    
    private let omikujiText: [String] = [
        "大吉",
        "中吉",
        "小吉",
        "吉",
        "末吉",
        "凶",
        "大凶"
    ]
    
    private let omikujiImage: [String] = [
        "daikichi",
        "chuukichi",
        "syoukichi",
        "kichi",
        "suekichi",
        "kyou",
        "daikyou"
    ]
  
    override func viewDidLoad() {
        super.viewDidLoad()
        setOmikujiResult()
    }
    
    @IBAction private func backButtonAction() {
        dismiss(animated: true)
    }
    
    @IBAction private func againButtonAction() {
        setOmikujiResult()
    }
    
    
    private func setOmikujiResult() {
        let randomInt = Int.random(in: 0..<7)
        resultLabel.text = String(omikujiText[randomInt])
        resultImage.image = UIImage(named: omikujiImage[randomInt])
    }
}
```

## 【チャレンジ】効果音を入れてみる

下記は効果音を入れたverのコードになります。
ぜひ、チャレンジした後に確認してみて下さい！

```diff_swift:ResultViewController.swift
//
//  ResultViewController.swift
//  OmikujiApp
//

import UIKit
+ import AVFoundation

final class ResultViewController: UIViewController {
    
    @IBOutlet private weak var resultLabel: UILabel!
    @IBOutlet private weak var resultImage: UIImageView!
    
    private let omikujiText: [String] = [
        "大吉",
        "中吉",
        "小吉",
        "吉",
        "末吉",
        "凶",
        "大凶"
    ]
    
    private let omikujiImage: [String] = [
        "daikichi",
        "chuukichi",
        "syoukichi",
        "kichi",
        "suekichi",
        "kyou",
        "daikyou"
    ]
    
+   private var player: AVAudioPlayer?

    override func viewDidLoad() {
        super.viewDidLoad()
        setOmikujiResult()
        playSoundEffect()
    }
    
    @IBAction private func backButtonAction() {
        dismiss(animated: true)
    }
    
    @IBAction private func againButtonAction() {
        setOmikujiResult()
        playSoundEffect()
    }
    
    
    private func setOmikujiResult() {
        let randomInt = Int.random(in: 0..<7)
        resultLabel.text = String(omikujiText[randomInt])
        resultImage.image = UIImage(named: omikujiImage[randomInt])
    }
    
+   private func playSoundEffect() {
+       if let soundURL = Bundle.main.url(forResource: "omikujiResultBgm", withExtension: "mp3") {
+           do {
+               player = try AVAudioPlayer(contentsOf: soundURL)
+               player?.play()
+           } catch {
+               print("error")
+           }
+       }
+   }
}
```

## 最後に

学んだ点のおさらいになります。

```swift
画面遷移(モーダル遷移) ... Present Modally
遷移元に戻る処理 ... dismiss()
画像の表示 ... UIImage、UIImageView
配列 ... let 配列名: [Int] =  [値1, 値2, 値3, ……]
乱数の生成 ... Int.random(in: 0..<7)
```

swiftを触り始めて2週間の初心者が作成しているため拙い説明だったかもしれませんが...
これをきっかけにiOSに興味を持ってくださる方が増えると嬉しいです:relaxed:
みなさま、素敵なクリスマス、年末をお過ごし下さい:baby_tone1::snowflake:

## 参考文献

https://qiita.com/Saayaman/items/a23519ff5a8ad287cf20

https://qiita.com/tanaka-tt/items/863fc274707c396070a8

https://hajihaji-lemon.com/swift/uibutton_event/

https://naoya-ono.com/swift/swift-random/

【チャレンジ】

https://ios-docs.dev/sound/