SwiftUIのStyleの作り方 - 共通のUIデザインはStyleに切り出す
Table of Contents
ボタンやラベルの共通デザインをViewクラスとして切り出して作ることもできますが、SwiftUIではStyleとして切り出す方が都合がよさそうだなと感じています。
クラスとして切り出す場合の問題点 #
例えば、下の画像のようなボタンUIを、クラスで切り出すとします。この場合は、例えば CustomButton
などの適当な名前でViewを切り出しつつ、それを使用するViewクラスでは CustomButton
のイニシャライザを呼び出して使用します。
// 切り出したUI
struct CustomButton: View {
let title: LocalizedStringKey
let action: () -> Void
var body: some View {
Button(action: action, label: {
Text(title)
.padding()
})
.font(.system(size: 17, weight: .bold))
.foregroundColor(.white)
.background {
RoundedRectangle(
cornerSize: .init(width: 8, height: 8),
style: .continuous
)
.fill(.green)
}
}
}
// 使用する側
struct ContentView: View {
var body: some View {
VStack {
CustomButton(title: "Custom", action: executeSomething)
}
.padding()
}
private func executeSomething() {}
}
こうすることで便利になったようにも感じますが、不都合があります。イニシャライザが固定されてしまいます。Button
自体には、状況に応じて使えるイニシャライザがいくつもありますが、それらを使用できなくなります。
Styleとして切り出すと都合が良い #
Buttonのイニシャライザはそのままに、共通するデザインを切り出すには、Styleとして切り出すと便利です。 上の例をそのままStyleで書き直します。
// カスタムスタイルの定義
struct CustomButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding()
.font(.system(size: 17, weight: .bold))
.foregroundColor(.white)
.background {
RoundedRectangle(
cornerSize: .init(width: 8, height: 8),
style: .continuous
)
.fill(.green) // .fill(.tint)に変更すると便利
}
.opacity(configuration.isPressed ? 0.7 : 1.0)
}
}
// .buttonStyleで指定できるようにする
extension ButtonStyle where Self == CustomButtonStyle {
static var custom: CustomButtonStyle {
CustomButtonStyle()
}
}
struct ContentView: View {
var body: some View {
VStack {
Button("Custom", action: executeSomething)
.buttonStyle(.custom)
}
.padding()
}
private func executeSomething() {}
}
こうすることで、Button
のイニシャライザを制限することなく、デザインを変更することができます。
応用的な使い方 #
tint #
この方法の良さは、Styleの定義中に tint
を使えることです。RoundedRectangle
のfill
で指定する色を.fill(.tint)
に変更することで、ボタンを使用する側がボタンの色を指定できるようになります。
Button("Custom", action: executeSomething)
.buttonStyle(.custom)
.tint(.green)
パラメータ #
Style
も普通のstruct
なので、プロパティを持つことができます。Styleのイニシャライザをextensionに実装していますが、その初期化時点でパラメータを渡すことができるということです。
すなわち、下のようにextensionを組めば、パラメータを渡すことが可能になります。
struct CustomButtonStyle: ButtonStyle {
let cornerRadius: Int // 角丸の大きさを指定できるようにする
...
}
extension ButtonStyle where Self == CustomButtonStyle {
static func custom(cornerRadius value: Int) -> CustomButtonStyle {
CustomButtonStyle(cornerRadius: value)
}
}
// 使用する側
struct ContentView: View {
var body: some View {
VStack {
Button("Custom", action: executeSomething)
.buttonStyle(.custom(cornerRadius: 25))
.tint(.green)
}
...
}
スタイルにどんどん切り出してViewをスッキリさせよう #
SwiftUIは、ViewのデザインをViewに書くと、すぐにコードの意図が読みづらくなります。 積極的にStyleに切り出せば、コードの意図がわかりやすい状態を保つことができます。 また、UIのプリミティブなイニシャライザも活用できるようになるので、非常に便利です。