iOSのトグルをカスタムする

# 概要
デザイナーさん「このデザインで、トグルはこんな感じでお願いします」
ワイ「了解！disableの場合も設定して…出来たで！」
デザイナーさん「disableのときは灰色じゃなくて透明度を落とすだけにしてほしいです」
ワイ「…？」

# この記事を読んで嬉しい人
SwiftUIを使ってiOS開発をしている人
トグルボタンをカスタムして自由に実装したい人

# 今回のお題
今回はトグルボタンのカスタムについてです。当初は普通にモディファイアを利用して実装をしていたのですが、disableの場合はデフォルトの灰色になってしまいました。ではなく薄い赤にする必要性がありました。
この場合の実装方法について少し悩んだので共有します。

# 実装
## 修正前
では問題の実装を見ていきましょう。
もともとの実装は以下です。
```swift:ToggleView.swift
import SwiftUI
struct ToggleView: View {
    @Binding var isToggleStatus: Bool

    var body: some View {
        VStack(spacing: 0) {
            HStack {
                Text("タイトル")
                    .font(.system(size: 14))
                Toggle(isOn: $isToggleStatus) {}
                    .padding(.vertical)
                    .toggleStyle(SwitchToggleStyle(tint: .accentColor))
                    .disabled(true)
            }
        }
        
    }
}
#Preview {
    ToggleView(
        isToggleStatus: .constant(true)
    )
}
```
ToggleStyleのtintはAcccentColorを指定していますが、これは自作のカラーで設定値は以下です。

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537378/c578a5e8-58dc-4cd0-8de8-96bdb8fc6465.png" alt="color_setting">

この場合のプレビューがこちらになります。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537378/e05a449e-2ef3-4b1c-9239-ff1d48a8ea5b.png" alt="preview">

この灰色になっているのを薄い赤にしたいわけです。

## 調べてみた
どうやら細かい設定をするにはToggleStyleプロトコルを適用したCustomToggleStyleを定義する必要がありそうです。iOS13以上で動作するのですが、最近のアプリの対応バージョン的にはそこまで気にする必要はなさそうです。

やる必要がありそうなことは以下
* CustomToggleStyle構造体内にmakeBody(configuration: Configuration)メソッドを実装
* ViewにToggleを配置
* toggleStyleモディファイアに自前で実装したCustomToggleStyleを指定

ではこれに従って実装を進めてみます。

## 修正後
### まずは修正コード
```swift:ToggleView.swift
import SwiftUI

struct CustomToggleStyle: ToggleStyle {
    var onColor: Color
    var offColor: Color
    
    func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.label
                .font(.system(size: 14))
                .foregroundStyle(.gray)
            Spacer()
            Rectangle()
                .foregroundColor(configuration.isOn ? onColor : offColor)
                .frame(width: 50, height: 30)
                .overlay(
                    Circle()
                        .foregroundColor(.white)
                        .padding(2)
                        .offset(x: configuration.isOn ? 10 : -10, y: 0)
                        .animation(.easeInOut(duration: 0.2), value: configuration.isOn)
                )
                .onTapGesture {
                    configuration.isOn.toggle()
                }
                .cornerRadius(15)
        }
    }
}

struct ToggleView: View {
    let title: String
    
    @Binding var isToggleStatus: Bool
    var isToggleDisabled: Bool
    
    var body: some View {
        let onColor = isToggleDisabled ? Color.accentSecondary : Color.accent
        let offColor = Color.gray
        Toggle(title, isOn: $isToggleStatus)
            .toggleStyle(CustomToggleStyle(onColor: onColor, offColor: offColor))
            .disabled(isToggleDisabled)
    }
}

#Preview {
    ToggleView(title: "タイトル", isToggleStatus: .constant(true), isToggleDisabled: true)
}
```

オン状態のときの色をdisableかどうかで指定しています。
accentSecondaryの色指定は以下です。

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537378/d6374f0f-f929-4e65-a72d-3394ff25434a.png" alt="secondary_color_setting">

Opacityを50%に変更しただけです。

こちらのコードをpreviewした結果がこちらです。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537378/8f18999d-0572-4389-8e2c-721f7142ac67.png" alt="secondary_preview">

指定通り、透明度の落ちた赤になっていることが確認いただけるかと思います！

### 要は何をしたの？
要はCustomToggleStyleでトグルのラベルとスタイルを指定したわけです。
大きく変わった点としては二点あります。

* タイトルとトグルボタンを並べていたのを、一つのスタイルにまとめた
* トグルボタンの形からアニメーションまで指定した

一つ目の違いについて、View内のトグルをよく見ると`Toggle(title, isOn: $isToggleStatus)`と書いています。つまり、タイトル自体をボタンに渡しているわけです。
これはCustomToggleStyleのHStack内を見るとSpacer()で間を開けてタイトルとトグルを左右に並べて配置していることがわかるかと思います。

また、Rectangle()で長方形を置いて、cornerRadiusモディファイアで楕円形にしています。
その上にoverlayで円を配置することでトグルボタンの見た目を再現しています。
ここを変更すると様々な形のトグルを作成することが可能なので、自分オリジナルのボタンを作ってみてください。
そして、onColorを楕円形の背景色に指定することで、自分が再現したい色で実装が可能になったというわけです。

# まとめ
今回はCustomToggleStyleを利用して自分なりのトグルボタンを実装するという内容でした。
makeBody内で様々な実装が可能なので、より複雑な設定がボタンに必要な場合は是非利用を検討してください。

# 余談ですが
記事を書くうえでテンポラリープロジェクトにコードを書いていたところ
```swift:ToggleView.swift
import SwiftUI
struct ToggleView: View {
    @Binding var isToggleStatus: Bool

    var body: some View {
        VStack(spacing: 0) {
            HStack {
                Text("タイトル")
                    .font(.system(size: 14))
                Toggle(isOn: $isToggleStatus) {}
                    .padding(.vertical)
                    .toggleStyle(SwitchToggleStyle(tint: .red))
                    .disabled(true)
            }
        }
        
    }
}
#Preview {
    ToggleView(
        isToggleStatus: .constant(true)
    )
}
```
こちらの実装ではきちんと薄い赤になってしまいました。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537378/38852beb-a04c-41b5-a74a-eaee62c721e2.png" alt="appendix">

色指定に`.red`を利用しただけなんですが、今回はデザイナーさん指定のカスタムカラーを利用したため、今回のような煩雑な処理が必要になってしまったようです。
もしかするとColorの設定をうまくすればよいのかもしれなかったですが、今回はCustomToggleStyleの利用に関する記事なので一応これでもうまくいったよの共有までにしておきます（悔しい）。