🧰

【SwiftUI】if節でToolbarを差し替える(ゴリ押し)

2 min read

Result Builder

SwiftUIの魔法のようなDSLの裏では、ViewBuilderというResult Builderが頑張ってくれている(詳細は省く)。SwiftUIにはViewBuilderだけではなく、SceneBuilderTableColumnBuilderToolbarContentBuilderなど様々なResult Builderが存在する。

ToolbarContentBuilderとif節

ところで、buildEitherメソッドを実装していないResult Builderでは、DSL内でif節を使用できない。そして残念なことに、ToolbarContentBuilderにはbuildEitherメソッドがない。
よって、下のようにEditModeによってツールバーを変化させるようなことができない。

content
    .toolbar {
        if editMode?.wrappedValue.isEditing ?? false {
            editModeToolbar
        } else {
            defaultToolbar
        }
    }

しかしToolbarContentBuilderではなくViewBuilderにif文を任せることで、この機能を実現することができる。

方法

1.Viewifモディフィア(メソッド)を生やす

View+If.swift
import SwiftUI

extension View {
    @ViewBuilder func `if`<TrueContent: View, FalseContent: View>(
        _ condition: Bool,
        @ViewBuilder trueContent: (Self) -> TrueContent,
        @ViewBuilder else falseContent: (Self) -> FalseContent
    ) -> some View {
        if condition {
            trueContent(self)
        } else {
            falseContent(self)
        }
    }
}

if condition { ... } else { ... }の部分で、内部的にViewBuilderbuildEitherを呼び出している。

SwiftUIの慣習的にはモディフィアを作る場合、ViewModifierを用意してからそれを呼び出すメソッドをViewのextensionとして追加する、という手法の方が好まれる。

2.ifモディフィア内でtoolbarをくっつける

@Environment(\.editMode) var editMode

@ToolbarContentBuilder
var defaultToolbar: some ToolbarContent {
    ToolbarItem(placement: .navigationBarTrailing) {
        ...
    }
    
    ToolbarItem(placement: .navigationBarTrailing) {
        ...
    }
}

@ToolbarContentBuilder
var editModeToolbar: some ToolbarContent {
    ...
}

//body内で
content
    .if(editMode?.wrappedValue.isEditing ?? false) { view in
        view.toolbar { editModeToolbar }
    } else: { view in
        view.toolbar { defaultToolbar }
    }

このような形を取ることで、toolbar(content:)に対して渡す引数は単一のToolbarContentで済むようになり、buildEitherがなくとも困ることはない。

参考記事

SwiftLee - How to create a Conditional View Modifier in SwiftUI

Discussion

ログインするとコメントできます