[SwiftUI] カスタムビューに独自のModifierを実装する方法


アプリの複数の画面で汎用的に使えるカスタムビューを作ったが、画面によって一部分だけ色を変えたいという要件が出てきたら、どう実装すれば良いでしょうか?


例えば、ユーザ情報を表示する汎用的なビューを以下のように実装したとします(テキスト思いっきりベタ書きしてますがここでは気にしないことにします)。

struct UserView: View {
    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Image("icon")
                    .resizable()
                    .scaledToFill()
                    .frame(width: 30, height: 30)
                Text("Bamboo Hero")
            }
            Text("ブログやってます!")
        }
        .padding(8)
        .overlay(
            RoundedRectangle(cornerRadius: 16)
                .stroke(Color.black, lineWidth: 1)
        )
    }
}


こんなビューです。

f:id:bamboohero:20211223020437p:plain:w200


複数の画面でこのビューを使い回すことになったとして、ある画面では名前の部分を青色にし、ある画面では黒のままにしたいとします。

例えばこういう実装が考えられます。

struct UserView: View {
    var nameColor: Color = .black  // <- 追加

    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Image("icon")
                    .resizable()
                    .scaledToFill()
                    .frame(width: 30, height: 30)
                Text("Bamboo Hero")
                    .foregroundColor(nameColor)  // <- 追加
            }
            Text("ブログやってます!")
        }
        .padding(8)
        .overlay(
            RoundedRectangle(cornerRadius: 16)
                .stroke(Color.black, lineWidth: 1)
        )
    }
}


以下のようにすれば、名前部分が黒色のビューと青色のビューを表示できます。

UserView(nameColor: .blue)
UserView()

f:id:bamboohero:20211223020926p:plain:w200


上記実装でも要件は満たせますが、もうちょっとSwiftUIライクに書きたいですね。

そこで、こういう実装に変えてみます。

struct UserView: View {
    private var nameColor: Color = .black  // <- privateにする

    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Image("icon")
                    .resizable()
                    .scaledToFill()
                    .frame(width: 30, height: 30)
                Text("Bamboo Hero")
                    .foregroundColor(nameColor)
            }
            Text("ブログやってます!")
        }
        .padding(8)
        .overlay(
            RoundedRectangle(cornerRadius: 16)
                .stroke(Color.black, lineWidth: 1)
        )
    }

    // 名前の色を変更するためのModifier
    func nameColor(_ color: Color) -> CustomView {
        var view = self
        view.nameColor = color
        return view
    }
}


こんな感じでSwiftUIライクに実装できるようになりました。

UserView()
    .nameColor(.blue)
UserView()


可変の項目が増えてきてもModifierを追加実装していけば良く、SwiftUIらしい可読性の良いコードが書けます。

UserView()
    .nameColor(.blue)
    .bodyTextColor(.red)
    .borderColor(.yellow)