【個人アプリ開発記】SwiftUIで色検索アプリ

##はじめに
少し前のことになりますが、先日初めて**SwiftUIを使ってマンセルカラーをRGBに変換するアプリ「色検索」をリリース**することができました。

アプリがリリースされわずかでもインストールされ使ってくれる人がいることは大変嬉しいのですが、リリースまでには個人で開発することの難しさ、さらに技術として新しいSwiftUIを用いて開発を行うことはなかなか大変でした。

自分がどのようにアプリをリリースまでに至ったのかをまとめたいと思います。自分と同じようにアプリ開発をしたい！SwiftUIを使ってみたい！という人の参考jになれば幸いです。

##「色検索」の紹介
<img width="200" alt="IMG_0400.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/3420eafa-0128-0b50-590b-6ee23eb772ea.png"><img width="200" alt="IMG_0401.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/1c2fd30d-921a-7f5d-a510-2bfa3354ed81.png"><img width="200" alt="IMG_0402.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/ddd53d73-dcdc-f102-8668-c39f11797a86.png"><img width="200" alt="IMG_0403.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/6c853950-4fac-3da7-d805-f2019c5fdc91.png"><img width="200" alt="IMG_A5E13D68F852-1.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/4c3186c9-56b4-5997-1e63-dbdb93719db8.jpeg"><img width="200" alt="IMG_0399.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/04413e1c-4d13-1abf-0d50-584a8469514c.png">
###①マンセルカラーシステムから色を探せる
CSSでUIColorなどで色を決定する時、RGBピッカーやスライダーによって決定することが多く、極端な色になりがちです。特に最近流行りのパステルカラーや「エモい」色合いなどをRGB値でピンポイントに作り出すことは難しいと思います。そこで細かに色が整理されたマンセルカラーからRGBが分かれば便利なのではないかと考えました。初めに表示される画面は影をつけたり立体的にスライドされるSWiftUIらしい(?)デザインにしました。

###②検索後はRGB値の微調整、カラーコードの取得ができる。
好みの色にたどり着いた後はRGBスライダーで微調整をすることとカラーコードを取得することが可能です。カラーコードは長押しでコピーすることもできます。

##③色をストックしておくことができる。
保存するボタンを押すことで端末に保存され、いつでも色を取り出すことができます。(コピーは保存画面でも検索された色の画面でも両方できます。)

##④スライダーから色をつくることもできる。
検索画面に表示される色だけで　全ての色が載せ切れているわけではありません。スライダーから色を作り出すことも可能です。

##全体の構成
初心者ながらに感じるSwiftUIの良さとして、「抽象的に定義すれば使いまわせる」というものがあります。上の写真の通り、アプリには小さな長方形がたくさん出てきますが、それは一つひとつ作っているのではありません。
###最も小さい構造体
<img width = "250" alt="スクリーンショット 2020-07-04 14.56.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/ea7c429a-7523-e203-2c33-b1e7183fdf1f.png"> 

```Swift
import SwiftUI

struct ColorCell: View {
    var rValue: Int
    var gValue: Int
    var bValue: Int

    var body: some View {
        NavigationLink(destination: ColorSlider(R: Double(rValue), G: Double(gValue), B: Double(bValue))) {
            Text("")
        .frame(width: 30, height: 35)
            .background(Color.init(UIColor(rValue,gValue,bValue)))
           
            .cornerRadius(8)
            .shadow(color: Color.gray.opacity(0.7), radius: 5.0, x: 0.0, y: 0.0)
        }
    }
}

struct ColorCell_Previews: PreviewProvider {
    static var previews: some View {
        ColorCell(rValue: 0, gValue: 0, bValue: 0)
    }
}

```
最も小さい集合となるColorCellと定義した構造体を定義します。ColorCellを呼び出すには
`var rValue: Int
    var gValue: Int
    var bValue: Int
`
の3つを同時に定義しなければなりません。
例`ColorCell(rValue: 0, gValue: 0, bValue: 0)`
###2番目に小さい構造体
<img width = "250" alt = "スクリーンショット 2020-07-04 14.56.56.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/010fb369-e9b7-ba34-3484-6f090173a6e2.png">

```Swift
import SwiftUI

struct ColorValue: View {
    
    var colorArray: [[Int]]
    
    var body: some View {
      
        VStack {
             HStack {
                VStack(spacing: 5.0) {

                    Spacer()

                    ForEach(0..<colorArray.count, id: \.self){ j in
                        
                        ColorCell(rValue: self.colorArray[j][0], gValue: self.colorArray[j][1], bValue: self.colorArray[j][2])

                        
                     
                    }
                }
            }
        }
    }
}

struct ColorValue_Previews: PreviewProvider {
     static var previews: some View {
        ColorValue(colorArray: ColorDataR[0].v9)
    }
}
```

次に`VStack`の中に先ほど定義した`ColorCell`を入れることでColorCellを鉛直方向に並べます。`ColorArray`はRGB値が入った2次元配列です。`ColorValue`を呼び出すためには2次元配列`ColorValue`を定義しなければなりません。
###3番目に小さい構造体
<img width = "250" alt = "スクリーンショット 2020-07-04 14.57.07.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/cc2c8ca8-bc87-f1a0-e91f-04259e914bcb.png">

```Swift

import SwiftUI

struct MunsellColorTable: View {
    var colorData: colorData
    
    var body: some View {
        
        
        VStack {
            
        HStack {
            
        ColorValue(colorArray: colorData.v9)
        ColorValue(colorArray: colorData.v8)
        ColorValue(colorArray: colorData.v7)
        ColorValue(colorArray: colorData.v6)
        ColorValue(colorArray: colorData.v5)
        ColorValue(colorArray: colorData.v4)
        ColorValue(colorArray: colorData.v3)
        ColorValue(colorArray: colorData.v2)
        ColorValue(colorArray: colorData.v1)
          
        }
            Spacer()
                Spacer()
        .navigationBarTitle("\(colorData.name)")
        }
        
    }
}

struct MunsellColorTable_Previews: PreviewProvider {
    static var previews: some View {
        MunsellColorTable(colorData: ColorDataR[0])
    }
}
```
`MunsellColorTable`は`ColorCell`を鉛直方向に並べた`ColorValue`を水平方向に並べたものです。`HStack`の中に`ColorValue`が並べられています。
`MunsellColorTable`を呼び出すためには`ColorData`型のcolorDataを定義する必要があります。

###4番目に小さい構造体
<img width="250" alt="スクリーンショット 2020-07-04 15.31.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/904caa19-46b3-d5f4-fd19-e7df9be1625b.png">
一つの画面ができたので、それらを水平方向に並べます。コードは割愛しておりますが、これも`HStack`の中に先ほど作った`MunsellColorTable'が並べられています。

###5番目に小さい構造体(最も大きい構造体)
<img width="250" alt="スクリーンショット 2020-07-04 15.37.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/86d05161-112e-f7a3-95fb-79af3f975c5a.png">
お疲れ様でした。上で定義した構造体を`VStack`で並べれば完成です。R,YR,Y...と色は続きますが、全てそれぞれの色のRGB値のJSONデータと細分化された構造体で作られています。

##色を選択した画面
<img width="250" alt="スクリーンショット 2020-07-04 15.45.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/94ffd8f3-f5be-4af5-15d3-e819dca1284a.png">
`ColorCell`をタップするとそれぞれのRGB値やカラーコードが表示された画面へ移動します。この画面も小さな構造体を`VStack`で並べて作られています。

<img width="100" alt="スクリーンショット 2020-07-04 15.50.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/b54a61a8-f55b-1321-ef7b-5540a974a337.png"><img width="100" alt="スクリーンショット 2020-07-04 16.05.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/ebaf6e8c-1a41-3f0d-052f-3d4ddfc2e7be.png"><img width="100" alt="スクリーンショット 2020-07-04 16.05.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/e8f8ba64-de22-1261-5a57-d9d4e962288d.png">

##技術を得るために
###教材
SwiftUIは新しい技術であるので、学習教材が多いとはいえません。僕は[SwiftUIのチュートリアル](https://developer.apple.com/tutorials/swiftui)を最も参考にしました。ただ、英語で説明されているので、日本語訳された[超初心者のためのSwiftUIチュートリアル](https://note.com/nyakko/n/n7096926a5c95)にも大変お世話になりました。[SwiftUIを使ったアプリ開発](https://www.youtube.com/channel/UCXKz36QXdjHcm16-Vs9Cv7A)をしているYoutube動画も参考にしました。
###Qiitaでのアウトプット
SwiftUIの技術記事はめちゃくちゃ少ないです。備忘録・アウトプットによる定着などの意味を込めて記事はたくさん書きました。
[SwiftUIで画像を表示する(画像の切り抜きと影)]
(https://qiita.com/Hyperbolic_____/items/aaa9171e81c1683b35a8)
[SwiftUIの基本となるHStack,VStack
](https://qiita.com/Hyperbolic_____/items/7959785001809013ab8f)[SwiftUIで画像とテキストを表示する（チュートリアル)](https://qiita.com/Hyperbolic_____/items/0bc189350a20f9685020)
[SwiftUIでList(TableView)を作る](https://qiita.com/Hyperbolic_____/items/d2d8f047828e33fc7089)
[SwiftUIでNavigationController(NavigationView)](https://qiita.com/Hyperbolic_____/items/d991520d7b74fb33cce3)
[SwiftUIでNavigationVarとListの組み合わせ](https://qiita.com/Hyperbolic_____/items/729c40a51e5c4264a967)
[SwiftUIのAlignment構造体について](https://qiita.com/Hyperbolic_____/items/025259d4715dd08aff54)
[@State について](https://qiita.com/Hyperbolic_____/items/5caf8ebc8037bc070130)
[SwiftUIでForEachを使う時につまづいた話](https://qiita.com/Hyperbolic_____/items/d8d29e14416fd57079d2)
[SwiftUI でオブジェクトを回転させる方法](https://qiita.com/Hyperbolic_____/items/de44429445134a479ec6)
[SwiftUIで画面の左上にビューを配置する方法](https://qiita.com/Hyperbolic_____/items/45119c52522432bc4518)
[SwiftUIで大量のボタン等を設置する方法
](https://qiita.com/Hyperbolic_____/items/36213e39fc62a0a88740)[SwiftUIで「式が複雑すぎる」「式を分解しろ」と言われたら](https://qiita.com/Hyperbolic_____/items/cf808879ef8ff04a1afb)
[SwiftUIでNaivigationViewが重なったら
](https://qiita.com/Hyperbolic_____/items/262e26c6e9b26e7bcf94)[SF symbolsの使い方](https://qiita.com/Hyperbolic_____/items/f619ba4d56d721c9a0c9)
[SwiftUIでScrollViewするときに影が横に出ないなどのトラブル対処法](https://qiita.com/Hyperbolic_____/items/8105cb26c44359b193a0)

###Teratailでの質問
どうしてもエラーが解決できない時はエンジニア質問サイト[Teratail](https://teratail.com)で質問しました。JSONファイルの作り方など振り返ると基本的なことも質問していました。笑
[SwiftUIでForEachを使う時にエラーが出る時の対処方法](https://teratail.com/questions/260082)
[jsonファイルの作り方](https://teratail.com/questions/261986)
[Swiftで多重for文を回避したい](https://teratail.com/questions/262319)
[SwiftUIのNavigationLinkで画像がグレーアウトしてしまう](https://teratail.com/questions/263828)
[[SwiftUI]UserDefaultsによってデータの永続化をしたい](https://teratail.com/questions/265607)
[[SwiftUI] GeometryReaderを使ったものをScrollViewで並べると位置がずれてしまう](https://teratail.com/questions/266289)
[[SwiftUI].gesture(LongPressGesture)や.onLongPressGestureを使うとスクロールできなくなる](https://teratail.com/questions/266814)
[[SwiftUI] Sliderがある値以上で動かなくなる](https://teratail.com/questions/267130)

##終わりに
ここまで読んでくださりありがとうございました。teratailで質問に答えてくれる方、公式チュートリアルを日本語訳してくれる方、Youtubeで動画を上げてくださる方..様々な方のおかげで開発を継続できました。自分がQiita記事を投稿するのもそういった技術の共有が大事だと感じているからです。これからも勉強を続けていきます！

##アプリのリンク
https://apps.apple.com/jp/app/色検索/id1516435071