Closed7

[SwiftUI]NavigationBarをカスタマイズしたい

ほへとほへと

バックボタンのTitleを消すのと、Imageを別のものにしたい。

https://qiita.com/kaito-seita/items/5c847be63fd4748b58e3


元ある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として作っておけば、楽とのこと。

ほへとほへと

.toolbarModifierについて

https://developer.apple.com/documentation/swiftui/view/toolbar(content:)-5w0tj


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を作成することもできる。
以下を読む。

https://www.hackingwithswift.com/swift/5.4/result-builders

@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

https://abdulahd1996.medium.com/understanding-result-builders-view-builders-in-swiftui-part-ac6f189f0261

ほへとほへと

以下のように、navigationBarBackButtonHiddenをせずに設定すると、元々のBackButtonの横に、設定したボタンが表示されたので、新しいものが上部に作られたのではなく、ここで設定したToolbarの設定が追加されたみたいなイメージなのかな。

    var body: some View {
        Text("Second View")
//            .navigationBarBackButtonHidden()
            .toolbar {
                ToolbarItem(placement: .topBarLeading) {
                    Button(action: {
                        dismiss()
                    }) {
                        Image(systemName: "chevron.backward.circle")
                    }
                }
            }
    }

ほへとほへと

BackButtonをカスタマイズすると、横スワイプで戻る処理がなくなった。

スワイプバックを実装している人もいたが、
デフォルトのような感じにはなっていないもよう、、

そこは少し面倒臭い、、

このスクラップは4ヶ月前にクローズされました