いろは2986023 views
中学数学621382 views
小学社会308636 views
Computer365120 views
LaTeX957300 views
りんご192546 views
教育148875 views
小学理科717236 views
高校国語785655 views
MathPython491378 views
Help
Tools

English

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() のように、「何をするか」「どんな状態か」がわかる名前にする。

カスタムモディファイアはデザインシステムの基盤となる。プロジェクトの初期段階で共通スタイルを定義しておくと、開発効率と一貫性が大幅に向上する。