[SwiftUI]NavigationBarをカスタマイズしたい
SwiftUIのNavigationBarをカスタマイズする方法を調べる。
バックボタンのTitleを消すのと、Imageを別のものにしたい。
元あるButtonをHiddenにしてから、Toolbarを自分で作成する感じらしい。
struct SecondView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Text("SecondView")
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(
action: {
dismiss()
}, label: {
Image(systemName: "arrow.uturn.backward")
}
).tint(.black)
}
}
}
}
ここら辺もModifierとして作っておけば、楽とのこと。
Toolbarらへんの動きもっと知りたい。
.toolbar
Modifierについて
toolbar
指定された項目をツールバーまたはナビゲーション バーに入力します。
nonisolated
func toolbar<Content>(@ToolbarContentBuilder content: () -> Content) -> some View where Content : ToolbarContent
resultBuilderについて
ToolbarContentBuilder
を調べる際に、必要な知識として、resultBuilder
を知る。
resultBuilderは、複数のViewを組み合わせて、一つの複雑なViewを作成する。
以下の例は、複数のViewを一つのViewにしている。
これを可能にしているのでは、Result Builder。
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, World!")
Text("Welcome to SwiftUI")
Button(action: {
print("Button tapped")
}) {
Text("Tap me")
}
}
}
}
@ViewBuilderの中身を見ても、@resultBuilderを使ってた。
buildBlock
内で、まとめる実装がされているっぽい。
@resultBuilder public struct ViewBuilder {
/// Builds an expression within the builder.
public static func buildExpression<Content>(_ content: Content) -> Content where Content : View
/// Builds an empty view from a block containing no statements.
public static func buildBlock() -> EmptyView
/// Passes a single view written as a child view through unmodified.
///
/// An example of a single view written as a child view is
/// `{ Text("Hello") }`.
public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
public static func buildBlock<each Content>(_ content: repeat each Content) -> TupleView<(repeat each Content)> where repeat each Content : View
}
resultBuilderのカスタマイズ
独自のResult Builderを作成することもできる。
以下を読む。
@resultBuilder
struct SimpleStringBuilder {
static func buildBlock(_ parts: String...) -> String {
parts.joined(separator: "\n")
}
}
@resultBuilder 属性は、次の型を結果ビルダーとして扱う必要があることを Swift に指示します。
すべての結果ビルダーは、 buildBlock() と呼ばれる少なくとも 1 つの静的メソッドを提供する必要があります。このメソッドは、何らかのデータを受け取って変換する必要があります。上記の例では、0 個以上の文字列を受け取って結合し、単一の文字列として返します。
最終結果として、SimpleStringBuilder 構造体は結果ビルダーになり、文字列結合機能が必要な場所であればどこでも @SimpleStringBuilder を使用できるようになります。
プロパティラッパーとして登録してあるので、SimpleStringBuilderで定義した内容は、
makeSentence3にも適用される。
@SimpleStringBuilder func makeSentence3() -> String {
"Why settle for a Duke"
"when you can have"
"a Prince?"
}
print(makeSentence3())
ToolbarContentBuilder
コードの中身を見るとこんなだった。
@resultBuilderを使用しているので、複数のViewをまとめていることはわかる。
@resultBuilder public struct ToolbarContentBuilder {
/// Builds an expression within the builder.
public static func buildExpression<Content>(_ content: Content) -> Content where Content : ToolbarContent
public static func buildBlock<Content>(_ content: Content) -> some ToolbarContent where Content : ToolbarContent
/// Builds an expression within the builder.
public static func buildExpression<Content>(_ content: Content) -> Content where Content : CustomizableToolbarContent
public static func buildBlock<Content>(_ content: Content) -> some CustomizableToolbarContent where Content : CustomizableToolbarContent
}
ToolbarContent
を継承している場合と、CustomizableToolbarContent
を継承ている場合があるみたい。
buildExpressionは個々のItemに影響しているみたい。
一つにまとめる前にbuildExpreseeionを通ってきているみたいだけど、何のために通ってきているのかはちょっと不明。ChatGPTに聞いてみたら、各要素が個別に評価され、適切に配置されるために必要と言われたけど、詳しいところは理解できず、、
とりあえず、こいつが一つのものとしてまとめて、
ToolbarContentとして返してくれていることはわかった。
@resultBuilder
struct StringBuilder {
static func buildBlock(_ components: String...) -> String {
return components.joined(separator: "\n")
}
static func buildExpression(_ expression: String) -> String {
return expression + " ADDED EXPRESSION"
}
}
//usage
//MARK: Result builder individual Expression
description {
"abdul"
"bilal"
}
// adbuk ADDED EXPRESSION
// bilal ADDED EXPRESSION
ToolbarItemGroup
1つのまとまりとして定義したい場合
ToolbarItem
個々のものとして定義したい場合
以下のように、navigationBarBackButtonHiddenをせずに設定すると、元々のBackButtonの横に、設定したボタンが表示されたので、新しいものが上部に作られたのではなく、ここで設定したToolbarの設定が追加されたみたいなイメージなのかな。
var body: some View {
Text("Second View")
// .navigationBarBackButtonHidden()
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button(action: {
dismiss()
}) {
Image(systemName: "chevron.backward.circle")
}
}
}
}
BackButtonをカスタマイズすると、横スワイプで戻る処理がなくなった。
スワイプバックを実装している人もいたが、
デフォルトのような感じにはなっていないもよう、、
そこは少し面倒臭い、、