SwiftUI カスタムモディファイアの作成
カスタムモディファイアを作成すると、アプリ全体で一貫したスタイルを維持しやすくなる。ここでは実践的なカスタムモディファイアの作成パターンを紹介する。
シンプルなスタイルモディファイア
まずは基本的なカード風スタイルから始める。
struct CardModifier: ViewModifier {
var backgroundColor: Color = .white
var cornerRadius: CGFloat = 12
var shadowRadius: CGFloat = 4
func body(content: Content) -> some View {
content
.padding()
.background(backgroundColor)
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
.shadow(color: .black.opacity(0.1), radius: shadowRadius, y: 2)
}
}
extension View {
func card(
background: Color = .white,
radius: CGFloat = 12,
shadow: CGFloat = 4
) -> some View {
modifier(CardModifier(
backgroundColor: background,
cornerRadius: radius,
shadowRadius: shadow
))
}
}使い方はシンプルで直感的だ。
VStack {
Text("タイトル").font(.headline)
Text("説明文がここに入ります")
}
.card()
VStack {
Text("カスタムカード")
}
.card(background: .blue.opacity(0.1), radius: 20, shadow: 8)状態に応じたモディファイア
選択状態やエラー状態など、ビューの状態を反映するモディファイアは頻繁に使う。
struct SelectableModifier: ViewModifier {
var isSelected: Bool
var selectedColor: Color = .blue
func body(content: Content) -> some View {
content
.padding(12)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(isSelected ? selectedColor.opacity(0.1) : Color.clear)
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(isSelected ? selectedColor : Color.gray.opacity(0.3), lineWidth: isSelected ? 2 : 1)
)
}
}
extension View {
func selectable(_ isSelected: Bool, color: Color = .blue) -> some View {
modifier(SelectableModifier(isSelected: isSelected, selectedColor: color))
}
}ForEach(items) { item in
Text(item.name)
.selectable(selectedItem == item)
.onTapGesture { selectedItem = item }
}バリデーション用モディファイア
フォーム入力でエラーメッセージを表示するモディファイア。
struct ValidationModifier: ViewModifier {
var errorMessage: String?
func body(content: Content) -> some View {
VStack(alignment: .leading, spacing: 4) {
content
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(errorMessage != nil ? Color.red : Color.clear, lineWidth: 1)
)
if let message = errorMessage {
Text(message)
.font(.caption)
.foregroundColor(.red)
}
}
}
}
extension View {
func validation(error: String?) -> some View {
modifier(ValidationModifier(errorMessage: error))
}
}TextField("メールアドレス", text: $email)
.textFieldStyle(.roundedBorder)
.validation(error: emailError)ローディング状態モディファイア
データ読み込み中のオーバーレイを表示するモディファイア。
struct LoadingModifier: ViewModifier {
var isLoading: Bool
func body(content: Content) -> some View {
ZStack {
content
.disabled(isLoading)
.blur(radius: isLoading ? 2 : 0)
if isLoading {
ProgressView()
.scaleEffect(1.5)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.black.opacity(0.1))
}
}
}
}
extension View {
func loading(_ isLoading: Bool) -> some View {
modifier(LoadingModifier(isLoading: isLoading))
}
}List(items) { item in
ItemRow(item: item)
}
.loading(viewModel.isLoading)デバッグ用モディファイア
開発中に役立つデバッグ用モディファイアも作っておくと便利だ。
struct DebugBorderModifier: ViewModifier {
var color: Color
var width: CGFloat
func body(content: Content) -> some View {
#if DEBUG
content
.border(color, width: width)
#else
content
#endif
}
}
extension View {
func debugBorder(_ color: Color = .red, width: CGFloat = 1) -> some View {
modifier(DebugBorderModifier(color: color, width: width))
}
}DEBUG フラグにより、リリースビルドでは何も適用されない。
複合モディファイアの設計
単一責任を意識する
1 つのモディファイアは 1 つの役割に限定する。「カードスタイル」と「選択状態」は別のモディファイアにする。
デフォルト値を設ける
よく使う設定はデフォルト値として提供し、カスタマイズが必要な場合だけパラメータを指定できるようにする。
命名は動詞か形容詞で
.card()、.selectable()、.loading() のように、「何をするか」「どんな状態か」がわかる名前にする。
カスタムモディファイアはデザインシステムの基盤となる。プロジェクトの初期段階で共通スタイルを定義しておくと、開発効率と一貫性が大幅に向上する。











