MENU

【SwiftUI入門】カスタムモディファイアの使い方完全ガイド|再利用可能なスタイルで開発効率UP

SwiftUIでアプリを開発していると、同じようなスタイルを何度も書くことはありませんか?

// 同じコードを繰り返し書いている...
Text("ボタン1")
    .padding()
    .background(Color.blue)
    .foregroundColor(.white)
    .cornerRadius(10)

Text("ボタン2")
    .padding()
    .background(Color.blue)
    .foregroundColor(.white)
    .cornerRadius(10)

この問題を解決するのが「カスタムモディファイア」です。この記事では、Swift初心者の方でもわかるように、カスタムモディファイアの基礎から実践的な使い方まで詳しく解説します。

目次

カスタムモディファイアとは?

カスタムモディファイアは、SwiftUIで再利用可能なビューのスタイルや装飾をまとめて定義できる機能です。

.padding().background()といった標準のモディファイアと同じように使えますが、自分で独自のスタイルを作成できる点が特徴です。

カスタムモディファイアのメリット

  • ✅ コードの重複を減らせる
  • ✅ デザインの一貫性を保てる
  • ✅ 変更が一箇所で済む(メンテナンス性向上)
  • ✅ チーム開発でスタイルを共有しやすい

カスタムモディファイアの基本的な作り方

ステップ1: ViewModifierプロトコルに準拠した構造体を作る

カスタムモディファイアを作るには、ViewModifierプロトコルに準拠した構造体を定義します。

import SwiftUI

struct PrimaryButtonModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
    }
}

ポイント解説:

  • ViewModifierプロトコルを採用
  • body(content:)メソッドで装飾を定義
  • contentは元のビュー(Textなど)を表す

ステップ2: モディファイアを使う

作成したモディファイアは.modifier()を使って適用します。

Text("ログイン")
    .modifier(PrimaryButtonModifier())

これだけで、定義したスタイルが適用されます!

より便利に使う方法:View Extension

.modifier()を毎回書くのは少し面倒です。そこで、View拡張を使ってより簡潔に書けるようにしましょう。

extension View {
    func primaryButtonStyle() -> some View {
        self.modifier(PrimaryButtonModifier())
    }
}

// 使用例:スッキリ!
Text("ログイン")
    .primaryButtonStyle()

この方法なら、標準のモディファイアと同じ感覚で使えます。

実践的なカスタムモディファイア例

ここからは、実際のアプリ開発で使える実用的な例を紹介します。

例1: カードスタイル

よく使うカードデザインをモディファイアにしてみましょう。

struct CardModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.white)
            .cornerRadius(12)
            .shadow(color: .gray.opacity(0.3), radius: 5, x: 0, y: 2)
    }
}

extension View {
    func cardStyle() -> some View {
        modifier(CardModifier())
    }
}

// 使用例
VStack(alignment: .leading, spacing: 10) {
    Text("商品名")
        .font(.headline)
    Text("¥1,980")
        .font(.title)
        .foregroundColor(.blue)
}
.cardStyle()

例2: パラメータ付きカスタムボーダー

パラメータを受け取ることで、より柔軟なモディファイアが作れます。

struct CustomBorderModifier: ViewModifier {
    let color: Color
    let width: CGFloat
    let cornerRadius: CGFloat
    
    func body(content: Content) -> some View {
        content
            .padding()
            .overlay(
                RoundedRectangle(cornerRadius: cornerRadius)
                    .stroke(color, lineWidth: width)
            )
    }
}

extension View {
    func customBorder(
        color: Color = .blue,
        width: CGFloat = 2,
        cornerRadius: CGFloat = 8
    ) -> some View {
        modifier(CustomBorderModifier(
            color: color,
            width: width,
            cornerRadius: cornerRadius
        ))
    }
}

// 使用例
Text("重要な通知")
    .customBorder(color: .red, width: 3)

Text("通常の通知")
    .customBorder() // デフォルト値で青いボーダー

例3: 条件付きスタイル

環境変数や状態に応じてスタイルを変えることもできます。

struct ConditionalHighlightModifier: ViewModifier {
    let isHighlighted: Bool
    
    func body(content: Content) -> some View {
        content
            .padding()
            .background(isHighlighted ? Color.yellow.opacity(0.3) : Color.clear)
            .cornerRadius(8)
    }
}

extension View {
    func highlight(if condition: Bool) -> some View {
        modifier(ConditionalHighlightModifier(isHighlighted: condition))
    }
}

// 使用例
struct ContentView: View {
    @State private var isSelected = false
    
    var body: some View {
        Text("選択可能なアイテム")
            .highlight(if: isSelected)
            .onTapGesture {
                isSelected.toggle()
            }
    }
}

例4: 複数のスタイルを組み合わせる

モディファイアは組み合わせて使うこともできます。

struct TitleModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.title)
            .fontWeight(.bold)
    }
}

struct AccentModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .foregroundColor(.blue)
            .padding(.horizontal)
    }
}

extension View {
    func titleStyle() -> some View {
        modifier(TitleModifier())
    }
    
    func accentStyle() -> some View {
        modifier(AccentModifier())
    }
}

// 使用例:複数のモディファイアを連結
Text("見出し")
    .titleStyle()
    .accentStyle()

よくある使用例とベストプラクティス

デザインシステムの構築

アプリ全体で統一感を出すために、デザインシステムとしてモディファイアをまとめておくと便利です。

// DesignSystem.swift
import SwiftUI

// ボタンスタイル
extension View {
    func primaryButton() -> some View {
        modifier(PrimaryButtonModifier())
    }
    
    func secondaryButton() -> some View {
        modifier(SecondaryButtonModifier())
    }
    
    func dangerButton() -> some View {
        modifier(DangerButtonModifier())
    }
}

struct PrimaryButtonModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
            .font(.headline)
    }
}

struct SecondaryButtonModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.gray.opacity(0.2))
            .foregroundColor(.primary)
            .cornerRadius(10)
            .font(.headline)
    }
}

struct DangerButtonModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.red)
            .foregroundColor(.white)
            .cornerRadius(10)
            .font(.headline)
    }
}

アクセシビリティへの対応

カスタムモディファイアでアクセシビリティ設定も一元管理できます。

struct AccessibleButtonModifier: ViewModifier {
    let label: String
    let hint: String
    
    func body(content: Content) -> some View {
        content
            .accessibilityLabel(label)
            .accessibilityHint(hint)
            .accessibilityAddTraits(.isButton)
    }
}

extension View {
    func accessibleButton(label: String, hint: String = "") -> some View {
        modifier(AccessibleButtonModifier(label: label, hint: hint))
    }
}

カスタムモディファイア vs 通常の関数:どちらを使うべき?

カスタムモディファイアと似た機能として、View拡張で関数を作る方法もあります。

// 方法1: カスタムモディファイア
struct CardModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.white)
            .cornerRadius(12)
    }
}

// 方法2: View拡張の関数
extension View {
    func cardStyleFunc() -> some View {
        self
            .padding()
            .background(Color.white)
            .cornerRadius(12)
    }
}

使い分けのポイント

カスタムモディファイアを使うべき場合:

  • パラメータが複数ある
  • 状態を持つ必要がある
  • 複雑なロジックを含む
  • 再利用性を重視する

通常の関数で十分な場合:

  • シンプルなスタイルの組み合わせ
  • パラメータがない、または少ない
  • プロジェクト内でのみ使用

トラブルシューティング

エラー: “Type ‘XXX’ does not conform to protocol ‘ViewModifier'”

原因: body(content:)メソッドが正しく実装されていない

解決策:

// ❌ 間違い
struct MyModifier: ViewModifier {
    func body() -> some View { // contentパラメータがない
        Text("Error")
    }
}

// ✅ 正解
struct MyModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
    }
}

モディファイアが適用されない

原因: 戻り値の型が正しくない、またはモディファイアの呼び出し順序

解決策:

// モディファイアは適用順序が重要
Text("Hello")
    .background(Color.blue) // 先に背景
    .padding() // 後でパディング(青い背景の外側に余白)

// vs

Text("Hello")
    .padding() // 先にパディング
    .background(Color.blue) // 後で背景(パディング込みで青い背景)

まとめ

カスタムモディファイアは、SwiftUIで効率的にアプリを開発するための強力なツールです。

この記事のポイント:

  • ViewModifierプロトコルで独自のスタイルを定義できる
  • View拡張と組み合わせて使いやすくする
  • パラメータを追加して柔軟性を高める
  • デザインシステムとして活用するとチーム開発に便利

最初は小さなスタイルから始めて、徐々にアプリ全体で使える汎用的なモディファイアを増やしていくのがおすすめです。

参考リンク

プログラミングの独学におすすめ
プログラミング言語の人気オンラインコース
独学でプログラミングを学習している方で、エラーなどが発生して効率よく勉強ができないと悩む方は多いはず。Udemyは、プロの講師が動画で実際のプログラムを動かしながら教えてくれるオンライン講座です。講座の価格は、セール期間中には専門書籍を1冊買うよりも安く済むことが多いです。新しく学びたいプログラミング言語がある方は、ぜひUdemyでオンライン講座を探してみてください。
目次