[SwiftUI]ボタンをカスタムしてみた

## はじめに
SwiftUIを使った開発を経験して、ボタンとかイメージとかもう少し読みやすくできんかなと思い立ちました。
宣言的UIあるあるかとは思うんですが、「()」や「{}」がどうしても多く、ネストが増えたりするのがちょっと嫌だったので自分なりに改良してみました。

## SwiftUIのボタンコンポーネント
まずは通常のボタンの実装をしてみます。
```swift
// 標準ボタン1: テキストのみ
Button(action: { print("Standard: Login tapped") }) {
     Text("Login")
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)

// 標準ボタン2: イメージ付き
Button(action: { print("Standard: Settings tapped") }) {
    HStack {
        Image(systemName: "gear")
            .font(.system(size: 20))
        Text("Settings")
    }
}
.padding()
.background(Color.gray)
.foregroundColor(.white)
.cornerRadius(10)

```
テキストのみのシンプルなボタンの場合、引数にactionを渡し{ }でボタンに載せるテキストを書きます。
更にアイコンを載せたい場合はHStackやVStackの中にアイコンとテキストを　{ }内に記述します。

![スクリーンショット 2024-09-20 12.20.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562437/78335cbb-6ff0-4ee6-54a1-e9230a536ee3.png)

こんなボタンが出来上がります。

まあこの程度見慣れてしまえば何てことなくね？って話なんですけど
( )や{ }が多くてインデントも少々複雑で見にくいなあと思った次第です。

なので引数に全部入れられればちょっとは見やすくなるかなと思い少し改造してみました。

## カスタムボタンの作成
大した拡張ではないのですが、カスタムボタン構造体を作ってそいつの引数で値を受け取って反映させればインデントが揃って見やすくなるかなとイメージしました。
アイコンありのパターンもシンプルにしたいので、Image()も拡張しています。

```swift
struct CustomImage: View {
    let systemName: String
    var foregroundColor: Color = .primary
    var font: Font = .body
    
    var body: some View {
        Image(systemName: systemName)
            .foregroundColor(foregroundColor)
            .font(font)
    }
}

struct CustomButton: View {
    let label: String
    let action: () -> Void
    var image: CustomImage?
    
    var body: some View {
        Button(action: action) {
            HStack {
                image
                Text(label)
            }
        }
    }
}
```
現状カスタムイメージはHStackになってますがこれのVStack版も作って使い分けたいですね。
ボタン上のアイコンとテキストが縦並びなことあまりない気がしますが。
アイコンに適用するスタイルが今は色とフォントですが、これを元に必要に応じて増やしていこうかなと思っています。

改良版がこちらです
```swift
// カスタムボタン1: テキストのみ
 CustomButton(
     label: "Login",
     action: { print("Custom: Login tapped") }
)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)

// カスタムボタン2: イメージ付き
CustomButton(
    label: "Settings",
    action: { print("Custom: Settings tapped") },
    image: CustomImage(
        systemName: "gear",
        foregroundColor: .white,
        font: .system(size: 20)
    )
)
.padding()
.background(Color.gray)
.foregroundColor(.white)
.cornerRadius(10)
```
ラベル、アクション、イメージのインデントが揃うだけでも少し見やすくなったかなと思います。
画像ありの方を見比べてみます。
```swift
Button(action: { print("Standard: Settings tapped") }) {
    HStack {
        Image(systemName: "gear")
            .font(.system(size: 20))
        Text("Settings")
    }
}

CustomButton(
    label: "Settings",
    action: { print("Custom: Settings tapped") },
    image: CustomImage(
        systemName: "gear",
        foregroundColor: .white,
        font: .system(size: 20)
    )
)
```
だいぶ直感的になったんではないでしょうか。
各プロパティが引数になったことで( )が減りシンプルになりました。
個人的にはこの方が好みです。

こんな感じでコンポーネントのカスタムもすんなりできて便利ですね。
まだ思い立ってちょこっとやってみただけなので、これから色々考えてみよーと思いました。

以下コードとプレビュー
```swift
//
//  ContentView.swift
//  UItest
//
//  Created by yukio on 2024/09/20.
//

import SwiftUI

struct CustomImage: View {
    let systemName: String
    var foregroundColor: Color = .primary
    var font: Font = .body
    
    var body: some View {
        Image(systemName: systemName)
            .foregroundColor(foregroundColor)
            .font(font)
    }
}

struct CustomButton: View {
    let label: String
    let action: () -> Void
    var image: CustomImage?
    
    var body: some View {
        Button(action: action) {
            HStack {
                image
                Text(label)
            }
        }
    }
}

struct ContentView: View {
    var body: some View {
        VStack(spacing: 30) {
            Group {
                Text("Custom Buttons").font(.headline)
                
                // カスタムボタン1: テキストのみ
                CustomButton(
                    label: "Login",
                    action: { print("Custom: Login tapped") }
                )
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(10)
                
                // カスタムボタン2: イメージ付き
                CustomButton(
                    label: "Settings",
                    action: { print("Custom: Settings tapped") },
                    image: CustomImage(
                        systemName: "gear",
                        foregroundColor: .white,
                        font: .system(size: 20)
                    )
                )
                .padding()
                .background(Color.gray)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
            
            Divider().padding()
            
            Group {
                Text("Standard Buttons").font(.headline)
                
                // 標準ボタン1: テキストのみ
                Button(action: { print("Standard: Login tapped") }) {
                    Text("Login")
                }
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(10)
                
                // 標準ボタン2: イメージ付き
                Button(action: { print("Standard: Settings tapped") }) {
                    HStack {
                        Image(systemName: "gear")
                            .font(.system(size: 20))
                        Text("Settings")
                    }
                }
                .padding()
                .background(Color.gray)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```
![スクリーンショット 2024-09-20 12.40.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3562437/df73480b-e327-3784-43a6-e99b08882f45.png)