🪢

SwiftUIで特定のViewからしか呼べないViewのextensionを作る

2023/12/11に公開

きっかけ

SwiftUIのリストの並び替えや削除処理を書く際、onMoveやonDeleteはForEachにしか書くことが出来ないことを知り、どういう仕組みか気になったので調べてみました。

DynamicViewContent

定義を見てみるとonMoveはDynamicViewContentの拡張で、DynamicViewContentを返す関数となっています。

onMove

DynamicViewContentはViewに準拠したprotocolです。プロパティとしてdataを持っているのは並べ替えや削除の判定を行うためだと思われます。

DynamicViewContent

そしてDynamicViewContentに準拠しているのはForEachModifiedContentです。上で見たようにonMoveはDynamicViewContentの拡張なので、この書き方によってonMoveが利用できる箇所が制限されているんですね。ちなみにModifiedContentはViewModifierを利用した際の戻り値です。

ForEachのextension

ModifiedContentのextension

実際にextensionを作ってみる

簡単な例を用いて実際にextensionを作ってみます。
まずはシンプルなViewとprotocolを利用した拡張部分を用意します。

struct HelloWorldView: View {
    var body: some View {
        Text("Hello, World!")
    }
}
protocol CustomProtocol: View {} // DynamicViewContentに該当
extension HelloWorldView: CustomProtocol {} // ForEachに該当
extension ModifiedContent: CustomProtocol where Content: CustomProtocol, Modifier: ViewModifier {}

これに対してonMove部分を作っていきます。
今回はパディングをつけて青いボーダーをつけるだけのViewModifierを作りました。

struct BlueBorderModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .border(.blue)
    }
}

そしてCustomProtocolを拡張します。
これで完成です。

extension CustomProtocol {
    func addBlueBorder() -> some CustomProtocol {
        self.modifier(BlueBorderModifier())
    }
}

作ったextensionを利用する

onMoveやonDeleteと同じように特定のViewに対してのみ利用できるextensionが作成できました。
ModifiedContentの拡張があるおかげで、複数連ねて利用することもできます。

var body: some View {
    VStack {
        HelloWorldView()
            .addBlueBorder() // OK: HelloWorldViewがCustomProtocolに準拠しているため
            .addBlueBorder() // OK: ContentがCustomProtocolのModifiedContentがCustomProtocolに準拠しているため
        Text("Hello, World!")
            .addBlueBorder() // NG: Value of type 'Text' has no member 'addBlueBorder'
    }
    .addBlueBorder() // NG: Value of type 'VStack<Content>' has no member 'addBlueBorder'
}

まとめ

今までonMoveやonDeleteを書ける条件を知らずに使っていたため、自分で制約をつけられるのは新たな気づきでした。実際にはViewModifier直呼びができてしまいますが、SDKで提供するなどすれば適切な箇所でしか利用できない仕組みを提供できそうです。

自分なりの解釈ですので、なにか間違っている点がありましたらコメントいただけますと幸いです。

Discussion