SwiftUI  アプリ制作で使用したコードまとめ

# はじめに
初めまして，大学1年生のkigです．
大学の課題でSwiftを使用して作成したアプリを，春休み中にリリースするため，機能追加や改良を行いました．今回は，Swiftでのアプリ制作でよく使用したコードや，必須なコードを誰にでも分かりやすいように，自分のアウトプットも兼ねてまとめてみました．サンプルコードもあるので，ご活用ください．
SwiftUIの文字表示(Stack)やボタン，@State，var，画面遷移...などの基盤部分は，Rikuto Sato様の[【総集編】【SwiftUI開発講座】ゼロからのSwiftUI開発！~3時間ぶっ通しで基本をマスターしよう~](https://youtu.be/EHdAqVVzAIE?si=yP42aGMQrru2mb8M)にて，とても分かりやすい解説をしてくださっているので，こちらと併せることで様々なコードを書くことができるようになると思います．

# 目次
1. データの永続化1(AppStorage)
2. データの永続化2(UserDefault)
3. Viewを表示する瞬間に処理を実行する(.onAppear)
4. ボタンタップ，画面遷移と同時に処理を実行(.simultaneousGesture)
5. ◯秒ごとに実行(Timer)
6. おまけ 広告をつける(参考サイトの紹介のみ)

# 1. データの永続化1(AppStorage)
<img width="300" alt="simulator_screenshot_80132327-5B52-47F8-A6D1-D0F430ACF683.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3694798/097de551-21dd-68fd-7220-64861cb15e2e.png">

ここでは，+ボタンを押すと1に1ずつ加算され，-ボタンを押すと，100から1ずつ減算されるアプリを作成しました．

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    //変数宣言
    @State var up = 1
    @State var down = 100
    
    var body: some View {
        
        VStack {                                    //{}内を縦向きに表示する
            HStack{                                 //{}内を横向きに配置
                Text("\(up)")                      //変数oneを表示
                    .font(.system(size: 40))        //文字の大きさを指定
                Button(action: {
                    //ボタンが押された時の処理
                    up += 1
                }, label: {
                    //ボタンの見た目を記述
                    Text("+")
                        .font(.system(size: 40))
                })
            }
            .padding()      //1+ と -100の間に少しスペースを作る
            
            HStack{
                Button(action: {
                    down -= 1
                }, label: {
                    Text("-")
                        .font(.system(size: 40))
                })
                Text("\(down)")
                    .font(.system(size: 40))
            }
        }
    }
}
//ここから下は消しても問題ないため，次回から省略する(Xcode内のプレビューが消えるだけ)
#Preview {
    ContentView()
}


```
このコードでは，1度アプリを閉じるとupとdowwnの値が初期値に戻ります(up = 1とdown = 100)．ここで，AppStorageを使うことで，アプリを1度閉じて再度開いても，upとdownが初期値に戻らなくなります．変更部分は
```swift:ContentView.swift
...
struct ContentView: View {
    //変数宣言
    @State var up = 1
    @State var down = 100
    
    var body: some View {...
```
の変数宣言をしている部分(2行だけ)です．下のように書き換えます．
```swift:ContentView.swift
...
    //変数宣言
    @AppStorage ("upKey") var up = 1
    @AppStorage ("downKey") var down = 100
    ...
```
```swift
@AppStorage ("キー値") var 変数名 = 値
```
で変数宣言を行います．すると，値とキー値(文字列)が結びつき，アプリが削除されるまで端末内に保存されます．

### しかし
このAppStorageですが，何でもかんでも保存できるわけではなく，配列など一部対応していない型があります．ここで登場するのがUserDefaultです．

参考サイト：[【SwiftUI】@AppStorageでデータを永続化（端末保存）する](https://www.yururiwork.net/archives/1310)

# 2. データの永続化2(UserDefault)
アプリ制作に配列は必須だと思います．が，UserDefaultの使い方を調べ，理解するのが一番大変でした．(難しそうなコードや単語が多かった...) まず，1.のコードで配列を使用します．
```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    //変数宣言
    @State var number = [1, 100]
    
    var body: some View {
        
        VStack {                                    //{}内を縦向きに表示する
            HStack{                                 //{}内を横向きに配置
                Text("\(number[0])")
                    .font(.system(size: 40))        //文字の大きさを指定
                Button(action: {
                    //ボタンが押された時の処理
                    number[0] += 1
                }, label: {
                    //ボタンの見た目を記述
                    Text("+")
                        .font(.system(size: 40))
                })
            }
            .padding()      //1+ と -100の間に少しスペースを作る
            
            HStack{
                Button(action: {
                    number[1] -= 1
                }, label: {
                    Text("-")
                        .font(.system(size: 40))
                })
                Text("\(number[1])")
                    .font(.system(size: 40))
            }
        }
    }
}
```
先ほどはupとdownという変数名でしたが，この2つの変数をnumber(Int型配列)に格納しました．見た目と動作は1.と全く同じです．では早速UserDefaultを使用して，配列を保存しましょう．
```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    //変数宣言
    @State var number = [1, 100]
    
    var body: some View {
        
        VStack {                                    //{}内を縦向きに表示する
            HStack{                                 //{}内を横向きに配置
                Text("\(number[0])")
                    .font(.system(size: 40))        //文字の大きさを指定
                Button(action: {
                    //ボタンが押された時の処理
                    number[0] += 1
                    //number全体を保存
                    UserDefaults.standard.set(number, forKey: "numberKey")
                }, label: {
                    //ボタンの見た目を記述
                    Text("+")
                        .font(.system(size: 40))
                })
            }
            .padding()      //1+ と -100の間に少しスペースを作る
            
            HStack{
                Button(action: {
                    number[1] -= 1
                    //number全体を保存
                    UserDefaults.standard.set(number, forKey: "numberKey")
                }, label: {
                    Text("-")
                        .font(.system(size: 40))
                })
                Text("\(number[1])")
                    .font(.system(size: 40))
            }
            .padding()
            //ボタン追加
            Button(action: {
                //ボタンを押したら，保存されたデータを読み込み
                guard let defaultNumber = UserDefaults.standard.array(forKey: "numberKey") as? [Int]
                else{return}
                number = defaultNumber
            }, label: {
                Text("保存されたデータを呼び出す")
                    .font(.system(size: 20))
            })
            
        }
    }
}
```
+または-ボタンを押し，加算or減算したと同時に，Number全体をNumberKeyに保存します．
```Swift
//number全体を保存
UserDefaults.standard.set(number, forKey: "numberKey")
```
```swift
UserDefaults.standard.set(保存したい変数名, forKey: "キー値")
```
でAppStorageのように，キー値と変数を結びつけます．
例えば，+を4回，-を4回押したとしましょう．するとnumberは[5, 96]となります．一方で，ボタンを押すごとにnumberは保存されるため，numberKeyに結びついている値も[5, 96]になっています．<font color="Red">この時点では保存しかしていない</font>ため，1度アプリを閉じて再度アプリを開くと，numberは[1, 100]となります(numberKeyに結びついている[5, 96]は保存されています)．そのため，<font color="Blue">保存した値をnumberに代入する必要があります．</font>([1, 100] に[5, 96]を代入する)
```swift
//ボタンを押したら，保存されたデータを読み込み
//.arrayや[Int]は，保存したい値の型によって変更する必要がある
guard let defaultNumber = UserDefaults.standard.array(forKey: "numberKey") as? [Int]
else{return}
Nnmber = defaultNumber
```
「保存されたデータを呼び出す」ボタンを押すことでdefaultNumberに，端末に保存してある[5, 96]を代入し，numberにdefaultNumberを代入するイメージです．以上で配列の保存，読み込みを行います．
<img width="300" alt="simulator_screenshot_9861A8B0-B8E9-4B47-9E75-D47CFB952703.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3694798/c2fc68d1-7da1-078a-e0f1-78f03ae6ad62.png">

私がUserDefaultを使えるようになった際，参考にさせていただいた動画リンクも貼りますので，是非ご覧ください．
[【初心者向け SwiftUI 】UserDefaults ってなに？ ~ データを簡単に保存できる便利な方法を紹介します！](https://youtu.be/pFEemgQvJzg?si=Y6vkzzxAMF32ujzv)

# 3. Viewを表示する瞬間に処理を実行する(.onAppear)
また先ほどのコードを再利用します．実行していただいた方は特に感じたかもしれませんが，<font color="Red">2.のアプリは使いずらい</font>と思います．例えば，ゲームで「オーブの数を更新する」なんてボタンありませんよね？こちらの操作がなくとも変数は更新されるのが一般的です．そこで，モディファイア「.onAppear」の登場です．.onAppearはStackやViewのモディファイアとして使用することで，そのStack，Viewが表示される直前に好きな処理を行えるというものです．つまり！先ほどのコードにて，Numberの値を表示する直前に，Default_Numberの値を代入することで，ボタンを押さずにNumberの値を更新することができます．
```swift:ContentView.swift

import SwiftUI

struct ContentView: View {
    //変数宣言
    @State var number = [1, 100]
    
    var body: some View {
        
        VStack {                                //{}内を縦向きに表示する
            HStack{                                 //{}内を横向きに配置
                Text("\(number[0])")
                    .font(.system(size: 40))        //文字の大きさを指定
                Button(action: {
                    //ボタンが押された時の処理
                    number[0] += 1
                    //number全体を保存
                    UserDefaults.standard.set(number, forKey: "numberKey")
                }, label: {
                    //ボタンの見た目を記述
                    Text("+")
                        .font(.system(size: 40))
                })
            }
            .padding()      //1+ と -100の間に少しスペースを作る
            
            HStack{
                Button(action: {
                    number[1] -= 1
                    //number全体を保存
                    UserDefaults.standard.set(number, forKey: "numberKey")
                }, label: {
                    Text("-")
                        .font(.system(size: 40))
                })
                Text("\(number[1])")
                    .font(.system(size: 40))
            }
            .padding()
        }
        .onAppear(){
            //VStackが表示される直前に，保存されたデータを読み込み
            guard let defaultNumber = UserDefaults.standard.array(forKey: "numberKey") as? [Int]
            else{return}
            number = defaultNumber
        }
    }
}
```
VStackに.onAppearを付け，VStackが表示される直前にNumberの値を更新する処理を行っています．これで，値の更新が自動で行われるようになりました．

# 4. ボタンタップ，画面遷移と同時に処理を実行(.simultaneousGesture)
ここでは，ボタンタップ，または画面遷移ボタンが押された時に処理を行うことができるコードを紹介します．

<img width="300" alt="simulator_screenshot_A6C13427-918A-445E-8F21-B7235BC14F6E.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3694798/68d87992-3966-f5e0-32ed-6b7963ebe346.png">

<img width="300" alt="simulator_screenshot_2DCCCC3B-463E-4351-9FEB-DD367D4B0798.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3694798/ca367bd9-a164-2cfc-4ebe-36c2312c7759.png">

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    //変数宣言
    @State var count = 0
    
    var body: some View {
        NavigationStack{
            NavigationLink{
                //ここに count += 1 と書くとエラーになる
                NextView(NextViewCount: $count)
            } label: {
                Text("次の画面へ移動")
            }
            .simultaneousGesture(TapGesture().onEnded{
                //次の画面に移動するボタンを押したら，Countに1を加算
                count += 1
            })
        }
    }
}
```
次に，遷移後の画面のコードです．
```swift:NextView.swift
import SwiftUI

struct NextView: View {
    //ContentViewから変数の値を引き継ぐ
    @Binding var NextViewCount: Int
    
    var body: some View {
        Text("\(NextViewCount)")
            .padding()
    }
}
```
NavigationLinkの{}内に画面遷移処理以外(四則演算など)を記述するとエラーになります．そのため，NavigationLinkに以下のモディファイアをつけることで，画面遷移(ボタンが押される)と同時に，関数実行などの任意の処理を行うことができます．
```swift
.simultaneousGesture(TapGesture().onEnded{
    //次の画面に移動するボタンを押したら，countに1を加算
    count += 1
})
```
他のタップ処理の参考サイト:[SwiftUIでのタップ処理の実装と応用](https://qiita.com/yuu__uuki/items/833dbe566ebc855c2aed)

また，他の画面遷移時に処理を実行する方法として，3.の.onAppearを使用する方法もあります．この場合，NextView(遷移先の画面)を表示する直前にNextView_Countを+1することで実装できます．

# 5. ◯秒ごとに実行(Timer)
最後は，◯秒ごとに処理を行うコードです．時間計測などにも使用できます．
以下のコードでは，「開始」を押すと1秒おきにCountが+1され，「終了」を押すと，それが止まるアプリを書いています．
```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    //変数宣言
    @State var count = 0            //秒数
    @State var timer: Timer?
    @State var isRunning = false    //タイマーが動いているか，動いていないか
    
    var body: some View {
        //秒数表示
        Text("\(count)")
        //タイマー開始
        Button(action: {
            timeCount()
            isRunning = true
        }, label: {
            Text("開始")
        })
        //タイマーが動いているなら「開始」は押せない
        .disabled(isRunning == true)
        .padding()
        
        //タイマー終了
        Button(action: {
            //タイマーを無効にする
            timer?.invalidate()
            timer = nil
            isRunning = false
        }, label: {
            Text("終了")
        })
        //タイマーが動いていていないなら，「終了」は押せない
        .disabled(isRunning == false)
    }
    
    //1秒ごとにcountに1を加算(Intervalの値を変更することで◯秒ごとに実行，なども可能)
    func timeCount(){
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {timer in
            count += 1
        }
    }
}
```
タイマーが起動している間は「開始」ボタンが押せなくなっています．この理由は，タイマーが重複して起動してしまうのを防ぐためです．タイマーが重複して起動した場合，各タイマーはそれぞれが独立して処理を行うため，1秒に何回も加算されてしまったりします．動作が重くなってしまうため，この辺りはよく動作確認をしましょう．

<img width="300" alt="simulator_screenshot_2F11AD7A-3BF9-42F6-95EB-6055EE600276.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3694798/19a11914-f096-e85c-5a04-04d3f9d63817.png">

<img width="300" alt="simulator_screenshot_2D624E86-663D-4AEC-9878-0D2805B9500A.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3694798/4c63ff38-ac85-3a7b-218e-c13b393a49fe.png">

# 6.おまけ 広告をつける 
SwiftUIにてアプリに広告をつける際，参考にさせていただいたサイトを紹介させていただきます．
[【SwiftUI】AdMobを使って広告を実装する方法!バナーViewの作成](https://appdev-room.com/swift-admob)
[【SwiftUI】リワード広告の表示](https://qiita.com/SNQ-2001/items/253173abefc6f90f86ea)

# 最後に
ここまでSwiftでアプリ制作に使用したコードをまとめました．各コードの使用方法を考えることで，実装できるものが増えると思います！

残りの春休みの時間はUnityを勉強し，作りたいゲームの構想もできているので，リリースに向けて１から頑張っていこうと思います．
宣伝にはなりますが，今回私が製作したアプリはAppStoreにて「Dot_Touch」というアプリ名でリリースしました．将来的にはDot_TouchもUnityで作り直せたらいいなと思います．

以上です，ご覧いただきありがとうございました！