見出し画像

SwiftUIのButtonStyleをカスタマイズ

SwiftUIでボタンを作る時は、よく枠線を付けたり背景色を塗りつぶしたりすることが多いが、今回はButtonStyleを使って再利用できるボタンスタイルを作ってみた。

ButtonStyleとは?

ButtonStyleとは、文字通りButtonのスタイルを変更するためのプロトコルで、これに準拠することで再利用可能なカスタムスタイルを定義することができる。
ButtonStyleプロトコルは以下のように定義されており、makeBody(configuration:)を実装して、ButtonStyleConfiguration.labelでスタイルを変更することでカスタマイズできる。

public protocol ButtonStyle {
    associatedtype Body : View
    @ViewBuilder func makeBody(configuration: Self.Configuration) -> Self.Body
    typealias Configuration = ButtonStyleConfiguration
}

public struct ButtonStyleConfiguration {
    public struct Label : View {
        public typealias Body = Never
    }

    public let role: ButtonRole?
    public let label: ButtonStyleConfiguration.Label
    public let isPressed: Bool
}

カスタムスタイル

今回はよく使う枠線付きや背景色を塗りつぶしたスタイル、非活性状態や押下時に背景色を変更するスタイルを作る。なお、ボタンの有効無効は@Environment(\.isEnabled)から、押下時かどうかはButtonStyleConfiguration.isPressedから取得できる。

/// 背景塗りつぶしで角丸なボタンスタイル
struct RoundedButtonStyle: ButtonStyle {
    @Environment(\.isEnabled) var isEnabled
    
    var color: Color = .blue
    private let disabledColor: Color = .init(uiColor: .lightGray)
    private let backgroundColor: Color = .white
    private let cornerRadius: CGFloat = 8.0
    private let lineWidth: CGFloat = 2.0
    
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .padding()
            .fontWeight(.bold)
            .foregroundColor(.white)
            // 有効無効でカラーを変更
            .background(isEnabled ? color : disabledColor)
            // 押下時かどうかで透明度を変更
            .opacity(configuration.isPressed ? 0.5 : 1.0)
            .clipShape(RoundedRectangle(cornerRadius: cornerRadius))
    }
}
/// 枠線のある角丸なボタンスタイル
struct BorderedRoundedButtonStyle: ButtonStyle {
    @Environment(\.isEnabled) var isEnabled
    
    var color: Color = .blue
    private let disabledColor: Color = .init(uiColor: .lightGray)
    private let backgroundColor: Color = .white
    private let cornerRadius: CGFloat = 8.0
    private let lineWidth: CGFloat = 2.0
    
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .padding()
            .fontWeight(.bold)
            // 有効無効でカラーを変更
            .foregroundColor(isEnabled ? color : disabledColor)
            .background(backgroundColor)
            .overlay(RoundedRectangle(cornerRadius: cornerRadius)
                        .stroke(isEnabled ? color : disabledColor, lineWidth: lineWidth))
            // 押下時かどうかで透明度を変更
            .opacity(configuration.isPressed ? 0.5 : 1.0)
    }
}
struct ContentView: View {
    @State private var isEnabled: Bool = true
    
    var body: some View {
        VStack(spacing: 40) {
            HStack {
                Text("isEnabled")
                    .padding()
                
                Toggle("", isOn: $isEnabled)
                    .labelsHidden()
            }
            
            Button("Rounded Button") {
                print("Pressed Rounded Button")
            }
            .buttonStyle(RoundedButtonStyle())
            .disabled(!isEnabled)
            
            Button("Bordered Rounded Button") {
                print("Pressed Borderd Rounded Button")
            }
            .buttonStyle(BorderedRoundedButtonStyle())
            .disabled(!isEnabled)   
        }
    }
}
カスタムボタンスタイル

補足

ButtonStyle以外にPrimitiveButtonStyleプロトコルもあるが、こちらはコードからボタンアクションを実行したい場合に使うもので、標準のボタンアクションの場合はButtonStyleを利用する。また、ButtonStyleではisPressedで押下中かどうかを取得できるので、押下中にスタイルを変更したい場合もButtonStyleを利用する。

public protocol PrimitiveButtonStyle {
    associatedtype Body : View
    @ViewBuilder func makeBody(configuration: Self.Configuration) -> Self.Body
    typealias Configuration = PrimitiveButtonStyleConfiguration
}

public struct PrimitiveButtonStyleConfiguration {
    public struct Label : View {
        public typealias Body = Never
    }

    public let role: ButtonRole?
    public let label: PrimitiveButtonStyleConfiguration.Label
    public func trigger()
}

サンプルコード

GitHubリポジトリ

参考


この記事が気に入ったらサポートをしてみませんか?