الفرق بين navigationTitle و ToolbarItem

الاثنين راح يعملوا نفس الامر اضافة عنوان للـ NanvigationStack او NavigationView

لكن في فرق جوهري بينهم

اولا navigationTitle فقط يستقبل String
بمعنى ماتقدر تضيف صور او غيره
ايضا مافي طريقة مباشره لتغير لون النص، او حجمه الخ

مثال للكود بإستخدام navigationTitle

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("Go to detail A", value: "Show A")
            }

            .navigationDestination(for: String.self) { textValue in
                if textValue == "Show A" {
                    DetailView(text: textValue)
                } else {
                    FinalView(text: textValue)
                }
            }
            .navigationTitle("A View")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}

struct DetailView: View {
    let text: String
    var body: some View {
        List {
            NavigationLink("Go to detail B", value: "Show B")
        }
        .navigationTitle("B View")
    }
}

struct FinalView: View {
    let text: String
    var body: some View {
        VStack {
            Text("Detail view showing")
            Text(text)
        }
        .navigationTitle("C View")
    }
}

مثال اخر لنفس الكود بإستخدام

ToolbarItem(placement: .principal)
struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("Go to detail A", value: "Show A")
            }

            .navigationDestination(for: String.self) { textValue in
                if textValue == "Show A" {
                    DetailView(text: textValue)
                } else {
                    FinalView(text: textValue)
                }
            }
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .principal) {
                    Text ("A View")
                        .bold()
                }
            }
        }
    }
}

struct DetailView: View {
    let text: String
    var body: some View {
        List {
            NavigationLink("Go to detail B", value: "Show B")
        }
        .toolbar {
            ToolbarItem(placement: .principal) {
                Text ("B View")
                    .bold()
            }
        }

    }
}

struct FinalView: View {
    let text: String
    var body: some View {
        VStack {
            Text("Detail view showing")
            Text(text)
        }
        .toolbar {
            ToolbarItem(placement: .principal) {
                Text ("C View")
                    .bold()
            }
        }

    }
}

هل اختلف شي ؟ ظاهريا لا

لكن بسبب استخدام

ToolbarItem(placement: .principal)

صار عباره عن View بمعنى اقدر اغير لون الخط ، حجمه الخ ايضا اقدر اغيره لصورة مثل كذا

  .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .principal) {
                    Image(systemName: "house.fill")
                        .bold()
                }
            }

ايضا اقدر اخليه عند الضغط على العنوان يظهر Menu

مثل كذا

  .toolbar {
                ToolbarItem(placement: .principal) {
                    Menu( "Menu" ) {
                        Button( "Duplicate", action: {})
                        Button("Rename", action: {})
                        Button("Delete...", action: {})
                    }
                }
            }

مثل كذا بإختصار اقدر اسوي اي شي ابغاه

لكن هناك عيب لـ

ToolbarItem(placement: .principal) 

وميزة لـ navigationTitle

الميزة لـ navigationTitle عند التعليق على زر الرجوع
تستطيع التنقل بين الصفحات وعنوان الصفحات يظل ظاهر

لكن عند استخدام ToolbarItem

العنواين تختفي ولكن تستطيع التنقل بين الصفحات بدون مشاكل

كيف تستفيد من الميزتين ؟ حرية التخصيص بإستخدام ToolbarItem

مع ظهور العنوان عند الضغط على زر التراجع !

بإختصار استخدام الاثنين مع بعض !
لانه ToolBar راح يغطي على NavigationTitle او تقدر تقول راح يعمل overriding عليه

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("Go to detail A", value: "Show A")
            }

            .navigationDestination(for: String.self) { textValue in
                if textValue == "Show A" {
                    DetailView(text: textValue)
                } else {
                    FinalView(text: textValue)
                }
            }
            .navigationBarTitleDisplayMode(.inline)
            .navigationTitle("A View")
            .toolbar {
                ToolbarItem(placement: .principal) {
                    Image(systemName: "house.fill")
                        .bold()
                }
            }
            
        }
    }
}

struct DetailView: View {
    let text: String
    var body: some View {
        List {
            NavigationLink("Go to detail B", value: "Show B")
        }
        .navigationTitle("B View")
        .toolbar {
            ToolbarItem(placement: .principal) {
                Text ("B View")
                    .bold()
            }
        }

    }
}

struct FinalView: View {
    let text: String
    var body: some View {
        VStack {
            Text("Detail view showing")
            Text(text)
        }
        .navigationTitle("C View")
        .toolbar {
            ToolbarItem(placement: .principal) {
                Text ("C View")
                    .bold()
            }
        }

    }
}

النتيجة

كيف احسن الموضوع هذا ؟
بحيث اقدر استخدمه في كل مكان

المميزات التي اريدها

١- اقدر اضيف عنوان للـ Navigation بحيث يظهر في زر الرجوع

٢- اقدر اضيف اي نوع من الـ View بمعنى يكون عام وماهو محدد بنوع معين

نقدر نعمل Generic modifier
بهذه الطريقة

struct CustomNavigationTitleModifier<CustomView: View>: ViewModifier {
    let title: String
    let customView: CustomView

    init(title: String, @ViewBuilder customView: () -> CustomView) {
        self.title = title
        self.customView = customView()
    }

    func body(content: Content) -> some View {
        content
            .navigationBarTitleDisplayMode(.inline)
            .navigationTitle(title)
            .toolbar {
                ToolbarItem(placement: .principal) {
                    customView
                }
            }
    }
}

extension View {
    func customNavigationTitle<CustomView: View>(title: String ,@ViewBuilder customView: () -> CustomView) -> some View {
        self.modifier(CustomNavigationTitleModifier(title: title, customView: customView))
    }
}

ونستخدمها هذه الطريقة

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("Go to detail A", value: "Show A")
            }

            .navigationDestination(for: String.self) { textValue in
                if textValue == "Show A" {
                    DetailView(text: textValue)
                } else {
                    FinalView(text: textValue)
                }
            }
            .customNavigationTitle(title: "A View") {
                Image(systemName: "house.fill")
                     .bold()
            }

        }
    }
}

struct DetailView: View {
    let text: String
    var body: some View {
        List {
            NavigationLink("Go to detail B", value: "Show B")
        }
        .customNavigationTitle(title: "B View") {
            Text("B View")
                .bold()
        }
    }
}

struct FinalView: View {
    let text: String
    var body: some View {
        VStack {
            Text("Detail view showing")
            Text(text)
        }
        .customNavigationTitle(title: "C View") {
            Text("C View")
                .bold()
        }

    }
}