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

English

SwiftUI .modifier() と View extension

カスタムモディファイアを作る方法には、.modifier() を使う方法と View の extension を使う方法がある。それぞれの特性を理解して、適切に使い分けよう。

.modifier() の基本

ViewModifier プロトコルを実装した型を、.modifier() で適用する。

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

// 使用
Text("タイトル")
    .modifier(BoldTitleModifier())

View extension の基本

View の extension としてメソッドを定義し、内部でモディファイアを適用する。

extension View {
    func boldTitle() -> some View {
        self
            .font(.title)
            .fontWeight(.bold)
    }
}

// 使用
Text("タイトル")
    .boldTitle()

こちらの方が呼び出しがシンプルで、標準のモディファイアと同じ見た目になる。

どちらを使うべきか

ViewModifier を使う場合

内部で状態(@State)や環境変数(@Environment)を使いたい。複雑なロジックを含む。他のモディファイアと concat で連結したい。

View extension を使う場合

シンプルなモディファイアのチェーン。標準モディファイアと同じ API を提供したい。@ViewBuilder を使った条件分岐が必要。

ViewModifier で状態を持つ

ViewModifier 内では @State や @Environment を使える。

struct HoverEffectModifier: ViewModifier {
    @State private var isHovered = false
    
    func body(content: Content) -> some View {
        content
            .scaleEffect(isHovered ? 1.05 : 1.0)
            .animation(.easeInOut(duration: 0.2), value: isHovered)
            .onHover { hovering in
                isHovered = hovering
            }
    }
}

View extension では @State を直接使えないため、このようなケースでは ViewModifier が必要になる。

@ViewBuilder を使った extension

条件分岐を含む場合は @ViewBuilder を使う。

extension View {
    @ViewBuilder
    func visible(_ isVisible: Bool) -> some View {
        if isVisible {
            self
        } else {
            self.hidden()
        }
    }
}

Text("表示/非表示")
    .visible(shouldShow)

@ViewBuilder により、異なる型を返す分岐が可能になる。

ジェネリックを使った柔軟な extension

extension View {
    func overlay<V: View>(
        alignment: Alignment = .center,
        @ViewBuilder content: () -> V
    ) -> some View {
        self.overlay(content(), alignment: alignment)
    }
}

ジェネリクスと @ViewBuilder を組み合わせることで、柔軟な API を設計できる。

実践的なパターン:ラッパーとしての ViewModifier

ViewModifier を使って、ビューを別のビューでラップするパターン。

struct CardWrapperModifier: ViewModifier {
    var padding: CGFloat = 16
    
    func body(content: Content) -> some View {
        VStack(alignment: .leading, spacing: 8) {
            content
        }
        .padding(padding)
        .background(Color(.systemBackground))
        .cornerRadius(12)
        .shadow(color: .black.opacity(0.1), radius: 4, y: 2)
    }
}

extension View {
    func cardWrapper(padding: CGFloat = 16) -> some View {
        modifier(CardWrapperModifier(padding: padding))
    }
}

これにより、任意のコンテンツをカード風にラップできる。

VStack {
    Text("タイトル").font(.headline)
    Text("本文がここに入ります")
}
.cardWrapper()

組み合わせのベストプラクティス

// ViewModifier で実装
struct PrimaryButtonModifier: ViewModifier {
    @Environment(\.isEnabled) var isEnabled
    
    func body(content: Content) -> some View {
        content
            .padding(.horizontal, 24)
            .padding(.vertical, 12)
            .background(isEnabled ? Color.blue : Color.gray)
            .foregroundColor(.white)
            .cornerRadius(8)
    }
}

// View extension で公開
extension View {
    func primaryButtonStyle() -> some View {
        modifier(PrimaryButtonModifier())
    }
}

ViewModifier で実装し、View extension で呼び出しやすい API を提供するという組み合わせが実用的だ。内部実装は ViewModifier でカプセル化しつつ、使う側は .primaryButtonStyle() のようにシンプルに呼び出せる。

どちらのアプローチも有効であり、要件に応じて使い分けることが重要である。状態や環境変数が必要なら ViewModifier、シンプルな変換なら extension と覚えておくとよい。