カルパス食べたい

日々の色々を書きます

SwiftUI の Custom ViewModifier

SwiftUI の ViewModifier は手軽に見た目を変更できるので便利ですね。 最近 SwiftUI を勉強していて、ViewModifier は自分で作成できることも知ったので、備忘録として残しておきます。

ちなみに SwiftUI の勉強には「100 Days of SwiftUI」というコンテンツを利用しています。

www.hackingwithswift.com

このサイトでは、SwiftUI についてはもちろんですが、それ以外にも iOS 周辺の様々な知識を身に付けることができるため、一度やっておいて損はない気がします。

ちなみに今回紹介する ViewModifier についても、このコンテンツの Day23 で詳しく紹介されています。

ViewModifier について

一応、SwiftUI の ViewModifier について軽くだけ説明を挟んでおきます。
基本的に名前の通りで、View に付けることができる修飾子であり、一つの View の見た目などを手軽に変更することができるものになります。

例えば、↓ のようなコードを書くと、

struct ContentView: View {
    var body: some View {
        Color(.red)
    }
}

↓ のような一面真っ青な画面が出来上がります。

f:id:kalupas:20200928234505p:plain:w200

これは、↓ のように .frame(width:height: ) という ViewModifier を付けてあげるだけで、青い部分のサイズを変更することができるようになります。

struct ContentView: View {
    var body: some View {
        Color(.systemBlue)
            .frame(width: 100, height: 100)
    }
}

このコードでは ↓ のように表示されるようになります。

f:id:kalupas:20200928235723p:plain:w200

非常に簡単にですが、これが基本的な ViewModifier になります。ちなみに ↑ のコードで Color を View として扱えているのは、SwiftUI においては Color は View だからになります。 完全に余談ではありますが、Color を覗いていくと ↓ のように View を継承して定義されています。

extension Color : View {

    /// The type of view representing the body of this view.
    ///
    /// When you create a custom view, Swift infers this type from your
    /// implementation of the required `body` property.
    public typealias Body = Never
}

Custom ViewModifier

最初に述べたように SwiftUI の VIewModifier は自分で作成することができます。ViewModifier は ViewModifier プロトコルに適合させることで作成することができます。

ViewModifier プロトコルを見ます。

public protocol ViewModifier {

    /// The type of view representing the body.
    associatedtype Body : View

    /// Gets the current body of the caller.
    ///
    /// `content` is a proxy for the view that will have the modifier
    /// represented by `Self` applied to it.
    func body(content: Self.Content) -> Self.Body

    /// The content view type passed to `body()`.
    typealias Content
}

↑ のプロトコルを見るに、基本的には func body(content: ) -> Self.Body を実装すれば作成することができそうです。

一つ具体例を示します。今回は Text に対して .font(.largeTitle) , .foregroundColor(.white) , .background(Color.red) を付与できるような Modifier を作成します。コードで示すと ↓ のようになります。

struct Title: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.largeTitle)
            .foregroundColor(.white)
            .background(Color.red)
    }
}

↑ で Title という名前で Modifier を定義したので、↓ のようにこの Modifier を利用することができます。

struct ContentView: View {
    var body: some View {
        Text("This is Text")
            .modifier(Title())
    }
}

すると、↓ のように自作した Modifier が適用されていることを確認できます。

f:id:kalupas:20200929002908p:plain:w200

しかし、自作した Modifier をいちいち .modifire(ModifierName()) のような形で利用するのは微妙ですよね。

これを解決するには View の extension 内で以下のように宣言するようにすると良いです。

extension View {
    func largeTitle() -> some View {
        self.modifier(Title())
    }
}

↑ のように宣言することによって、Custom Modifier を以下のような形で普通の Modfiier のように扱うことができるようになります。

struct ContentView: View {
    var body: some View {
        Text("This is Text")
            .largeTitle()
    }
}

定義方法を変えただけなので、見た目は特に変わらないです。

また、ViewModifier は Modifier だけではなく View 構造を作ることもできます。例えば、そのような ViewModifier は ↓ のように定義することができます。

struct WhiteOnTextLabel: ViewModifier {
    var text: String
    
    func body(content: Content) -> some View {
        ZStack(alignment: .bottomTrailing) {
            content
            Text(text)
                .font(.caption)
                .foregroundColor(.white)
                .padding(5)
                .background(Color.white)
        }
    }
}

extension View {
    func whiteOnTextLabel(with text: String) -> some View {
        self.modifier(WhiteOnTextLabel(text: text))
    }
}

↑ は ↓ のように使用することができます。

struct ContentView: View {
    var body: some View {
        Color.black
            .frame(width: 300, height: 300)
            .whiteOnTextLabel(with: "White text label")
    }
}

f:id:kalupas:20200929222211p:plain:w200

定義した whiteOnTextLabel(with: ) を利用するだけで、Color View の上に Text を載せるような Custom ViewModifier を作成することができました。

Custom ViewModifier は自分で ViewModifier を作成して簡単に利用することができますし、複数箇所でも利用することができます。UI が十分に共通化されてさえいれば、コードの無駄もかなり減らせるのではないでしょうか。しかし、チーム開発などで使いすぎるとかえって可読性を損なわせてしまったりもしそうなので、使いどころはしっかり考えていきたいですね。