SwiftUIを使って10日でSNSアプリをリリースしたので知見を公開してみる

# はじめに :beginner: 
DeNAでiOSエンジニアをしている[@tsuzuki817](https://twitter.com/tsuzuki817)です！

本記事は [SwiftWednesday Advent Calendar 2023](https://qiita.com/advent-calendar/2023/swift-wednesday) の16日目の記事です🎅🎄

https://qiita.com/advent-calendar/2023/swift-wednesday

昨日は @treastrain(Ryoga Tanaka)さんの[【初心者向け】iPad だけで Google の AI モデル「Gemini」を使ったアプリを作ってみよう！](https://qiita.com/treastrain/items/6ed2802836969d308481)でした :tada: 

Geminiを使ってみようと思える良記事でしたね:pray: 

さて、今回は `SwiftUIを使って10日でSNSアプリをリリースしたので知見を公開してみる`という記事です！

本当はもっと技術よりの記事を書こうと思っていたのですが、書こうとしていた内容が `iOS 17` だと解消されている問題だったので急遽内容を変更して、最近リリースしたアプリについて書かせていただきますのでぜひ飛ばし飛ばしでも良いので見てってください🙏

# 本編 :santa: 
## アプリをリリース
個人開発したアプリ `「:igyo:」` つい先日リリースしました :tada: 

ざっくり説明すると、スタンプコミュニケーションをベースとしたゆるいSNSアプリです。
（ぜひダウンロードしたり、友達・知人・家族に紹介してみてね）

![igyo .001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/942631b1-172e-5ff6-7335-53f5b753bb3a.png)

https://apps.apple.com/jp/app/igyo/id6473527357?platform=iphone

このアプリはiOS 17以上をターゲットとしたアプリです。
UIはSwiftUIで作り、バックエンドはFirebaseを利用しています。

調べてみるとこのアプリを作り始めたののは12/2でした。
平日は本業＋副業＋趣味＋家族の時間にリソース割いており毎日1~2時間、土日は金沢旅行で片道5時間を爆走するなどしていてガッツリ開発の時間を確保できていない中での10日間でアプリをリリースすることができました。

~~もっと暇な時だったら2日間でもいけた（盛）~~

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/ac553a38-1d9a-badc-808d-186fd66082fa.png", width=120>

![スクリーンショット 2023-12-15 1.14.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/b26310ba-124a-ab19-aff3-23d625cb2c93.png)

## 開発編
アイデアが浮かんだらとりあえず作り始めてしまいましょう！
手を動かしながら作った方が実際に動くものを触りながら開発できるのでより良い体験を作り出すことができると信じてます。

また、開発途中に今の実装とはあまり関係のない機能を思いつくことがあると思います。
その際にすぐにそちらの作業に移行してしまうのは自分は極力避けています！

理由は単純で行なっている作業がおざなりになってしまうからです。
アイデアが浮かんだらすぐにgithubのissueに書き込んで忘れないようにしたら一旦我慢しましょう🐵

次からは今回作ったアプリの各画面を紹介しながらどのように作っただとか、リジェクトの危険性がある箇所について話していきます！

### 初回起動
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/cf3dfa01-6bb2-c1f2-bdbd-4cc9a75f6d9e.png", width=320>

Firebase Analitics, Admobなどを使っている場合はトラッキングの許可のリクエストが必要なので、初回起動時に表示させています。

`Info.plist` に　`Privacy - Tracking Usage Description`を記入した文言がダイアログの中に表示されます。

![スクリーンショット 2023-12-15 1.57.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/ada77f47-20be-ea80-cc63-dccd72af115c.png)

SwiftUIで表示させたい場合は以下のように `didBecomeActive`をキャッチしてリクエストします。

```swift
import AppTrackingTransparency
...
 ContentView()
    .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
        ATTrackingManager.requestTrackingAuthorization(completionHandler: { _ in
        })
    }
```

### チュートリアル
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/383f527d-c078-0049-e859-0d098ad7c67c.png", width=320>
`TabView`を使って実装、 TutorialStateを `enum` で作ってTabViewにBindingさせてます。
（`enum`大好き）
<details>
<summary>TutorialState.swift</summary>
<div>

```swift
enum enum TutorialState: CaseIterable {
  case first
  case second
  case third
  case force

  var title: String {
    switch self {
    case .first:
      "ようこそ"
    case .second:
      "褒めよう"
    case .third:
      "守ろう"
    case .force:
      "投稿しよう"
    }
  }

  var description: String {
    switch self {
    case .first:
      "このアプリにあなたを否定する人は誰もいません"
    case .second:
      "あなたも誰かを褒めるところから始めてみましょう"
    case .third:
      "ふさわしくない投稿からこの世界を守りましょう"
    case .force:
      "あなたの偉業を周知させよう"
    }
  }

  var imageName: String {
    switch self {
    case .first:
      "post001"
    case .second:
      "post002"
    case .third:
      "post003"
    case .force:
      "post004"
    }
  }
}
```

</div>
</details>

### サインアップ画面
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/a27a0a4f-1e75-9d70-e230-136af9df40a8.png", width=320>

シンプルに今はiOSのみの提供しかしていないので `Sign in with Apple`ボタンを置いてます。
（他のSNSの認証機構を実装している場合、Appleでのサインインの提供が必須）

また、このアプリはUGCを提供しているので利用規約の同意やプライバシーポリシーへの事前同意が必要になります。
(User Generated Contents: 一般のユーザーによって制作・生成されたコンテンツ)

投稿する前だったらどこでも良いのですが、一番らくなサインアップ画面の下においてます。

サインアップにはFirebaseAuthを利用しています。
https://firebase.google.com/docs/auth/ios/apple?hl=ja

認証に成功したらFirestoreにuserデータを作成します。

```swift
  try await firestore.collection(collectionPath)
      .document(id)
      .setData(
        [
          "id": id,
          "name": name,
          "imageUrl": "",
          "createdAt": FieldValue.serverTimestamp(),
          "updatedAt": FieldValue.serverTimestamp(),
        ]
      )
```

### 認証後
認証後、アイコンを設定させます。
LazyVGridを使ってグリッドレイアウトは表現しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/741409c1-91eb-4e15-8adb-3b759cbbab3e.png", width=320>

### タイムライン
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/9f8ed8b8-1456-9972-121a-30abf261f1a1.png", width=320>

ScrollView + LazyVStackで構成されています。
また、3点リーダーの箇所はMenuを使って実装しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/28427ec6-03fe-c0fe-d1ec-6d26fb9abf23.png", width=320>

UGCの場合コンテンツをユーザーが非表示にできるようにしなくてはならないです。
この値は単純にAppStrageで保持しています。

```swift
  @AppStorage("hiddenPostIDs") var hiddenPostIDs: [String] = []
```
配列はそのまま AppStorageを適応できないので以下のExtensionを使って配列に対応させています。

```swift
extension Array: RawRepresentable where Element: Codable {
  public init?(rawValue: String) {
    guard let data = rawValue.data(using: .utf8),
          let result = try? JSONDecoder().decode([Element].self, from: data)
    else {
      return nil
    }
    self = result
  }
  
  public var rawValue: String {
    guard let data = try? JSONEncoder().encode(self),
          let result = String(data: data, encoding: .utf8)
    else {
      return "[]"
    }
    return result
  }
}

```

### 設定画面
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/1a7beb47-66fd-f914-220a-109bfc169778.png", width=320>
アカウント削除機能も必須なので実装しておきましょう！
https://developer.apple.com/jp/news/?id=12m75xbj

以下のように認証情報を削除してあげたり、サインアウトさせてあげる必要があります。
```swift
    try await Auth.auth().currentUser?.delete()
    try Auth.auth().signOut()
```

## ストア申請編
### スクショの作り方
自分はKeynoteを使って作っています！
自由に作れますし、画像への出力も早いので便利です🐣

![スクリーンショット 2023-12-15 21.11.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171702/23c90e23-c848-6060-ac34-622c543c5291.png)

### プライバシー
AdMob利用時の「Appのプライバシー」の入力はこちらを参考に入力してます🙇
https://zenn.dev/kazushige/articles/9afa10b36d6828

## 宣伝のための道具
App Store Marketing Toolsを使うと無料でアプリのショートリンクやアイコン、アイコンつきQRコードなど生成できるのでぜひ宣伝に利用しましょう！
https://tools.applemediaservices.com/app-store

## 最後に
ざっくりとですが、アプリをリリースするまでの知見をまとめてみました！
この記事を読んで少しでのアプリ開発のトラブル解消に役立てれば嬉しいです！