SwiftUI: NavigationView
NavigationView elemanı ile uygulamanızın navigasyonunu basit bir şekilde sağlayabilirsiniz.
Bir uygulamanın navigasyonunun sağlanmasında, istenilen bilgilerin gösterilmesinde ve aksiyon alımında NavigationView
elemanı bir uygulama için önemli UI elemanıdır. Bu yazımızda, NavigationView
elemanını kullanmayı öğreneceğiz. Hazırsanız hiç vakit kaybetmeden başlayalım. 🤟🏻
NavigationView Oluşturma
İlk olarak yeni bir Xcode projesi oluşturalım. Projemiz tabii ki bir SwiftUI projesi olacak. Projemizi oluşturduktan sonra Xcode’un bizim için varsayılan olarak verdiği kodlardan Text
elemanını ve padding
“modifier”ını silelim. Sonrasında body
içerisine NavigationView
elemanını oluşturalım.
Ardından NavigationView
elemanı içerisine bir VStack
oluşturduktan sonra içerisine bu kodları ekleyelim:
Image(systemName: "music.quarternote.3")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 100, alignment: .center)
.padding()
Burada ne yaptık? Sistem resimlerinden “music.quarternote.3” adında olan resmi kullandık. resizable()
“modifier”ını kullanarak yeniden boyutlandırma iznini verdik. Resmin en-boy oranını düzgün bir hale getirmek için aspectRatio
değerimizi .fit
olarak ayarladık. frame
“modifier”ı ile resmimizin genişliğini ve yüksekliği 100 olarak ayarladık, hizasının ortalanması için alignment
özelliğini .center
olarak tanımladık.
VStack
elemanımızı kapattıktan sonra NavigationView
elemanına bir başlık eklemek için navigationBarTitle
“modifier”ını yazalım: Başlık “Müzik” olsun. displayMod
özelliğini .inline
olarak ayarlayalım. Girdiğimiz kodlar sonrasında uygulamamızın son hali bu şekilde olacaktır.👇🏻
Ardından body
içerisindeki padding
“modifier”ının altına gelerek aşağıdaki kodları yazalım.
Text("Günün Müziği")
.font(.system(size: 25, weight: .light, design: .default))
.padding()NavigationLink(
destination: Text("Kid Cudi-Another Day"),
label: {
Text("Dinlemek için tıkla!")
.frame(width: 200, height: 50, alignment: .center)
.background(Color.purple)
.foregroundColor(.white)
.cornerRadius(8)
}
)
Burada ne yaptık? Resmimizin alına Text
ekledik ve font
“modifier”ı ile yazımızın görüşünü değiştirdik. Ardından NavigationLink
elemanını ve görüşünüşünü değiştirmek için dört tane “modifier” kullandık.
NavigationLink Nedir?
NavigationLink, kullanıcının bir butona tıkladığı zaman yeni bir sayfaya geçiş yapmasını sağlayan arayüz elemanıdır.
Artık “Preview” ekranından uygulamamızın son halini görebiliriz.
Şimdi ise NavigationView
elemanında butonları nasıl kullanabileceğimizi öğrenelim.
NavigationView Elemanına Bar Buttonları Ekleme
NavigationView
elemanı aracılığı ile sayfalar arasında gezinmek ve ekstra aksiyonlar alabilmek için NavigationBarItem
elemanlarına ihtiyacımız vardır.)
Bu elemanlara örnek olarak Apple’ın “Takvim” ve “Anımsatıcılar” uygulaması verilebilir.
Fark edeceğiniz üzere, az önce oluşturduğumuz butona tıkladıktan sonra ortaya çıkan ekranda tıpkı “Anımsatıcılar” uygulamasındaki gibi bir -geri- butonu vardı. Şimdi oluşturacağımız NavigationView
elemanları ise daha çok “Takvim” uygulamasının sağ tarafındaki gibi olacaklar.
İlk önce A elemanlarını eklemek için navigationBarItems
“modifier”ını yazalım.
.navigationBarTitle("Müzik", displayMode: .large)
.navigationBarItems(
leading: trailing:)
Hem sağa hem de sola iki buton koyacağımız için “leading” ve “trailing” özellikerini yazdık: Bu özelliklerin içerisine iki tane buton yazalım.
.navigationBarTitle("Müzik", displayMode: .large)
.navigationBarItems(
leading:
Button(action: { }) {
Image(systemName: "gearshape.fill")
.font(.system(size: 21))
}, trailing:
Button(action: { }) {
Image(systemName: "plus.circle.fill")
.font(.system(size: 21))
}
)
Bu butonlara tıkladığımızda belli aksiyonlar alabilmek için belli değerleri değiştirmemiz gerekiyor. Bu butonlara tıklayınca olmasını istediğimiz aksiyon yeni -sheet- ekranların ortaya çıkması olduğu için iki farklı Bool
türünden değer tanımlayalım.
@State var showSheet = false
@State var showModally = false
Bu değerler true
değerini aldığı zaman istediğimiz ekranın ortaya çıkmasını sağlamak için bu değerlerin true
değeri alması gerekiyor. Bu aksiyon da tam olarak butonlara tıklandığı zaman olacağı için butonların action
özelliklerinde bu iki değeri de true
değerine eşitliyoruz.
.navigationBarTitle("Müzik", displayMode: .large)
.navigationBarItems(
leading:
Button(action: {
showSheet = true
showModally = true
}) {
Image(systemName: "gearshape.fill")
.font(.system(size: 21))
}, trailing:
Button(action: {
showSheet = true
showModally = true
}) {
Image(systemName: "plus.circle.fill")
.font(.system(size: 21))
}
)
Şimdi uygulamayı çalıştırdığımızda uygulamamız aşağıdaki görsel gibi olacaktır, ama şu an butonlara tıkladığımızda yaptığımız değişiklikler arayüze yansımıyor. Az önce bu butonların yeni bir sayfaya geçiş yapmak için kullanılacağını söylemiştik. Bunun olabilmesi için sheet
“modifier”ına ihtiyacımız var: Onun sayesinde butonlara bastığımızda ortaya çıkacak ekranların kart görünümünde olmasında sağlayabiliyoruz. Uygulamanızda yapacağınız işlemlere göre bu şekilde yeni bir ekran çıkartmanız kullanıcı deneyimini iyi yönde etkileyebilir. Bu yüzden sheet
“modifier”ını genellikle kullanırız.
Sheet
Bu “modifier”ın çalışabilmesi için en büyük kapsama alanına sahip elemanın -Burada o NavigationView
oluyor- sonunda yazmamız gerekiyor.
NavigationView {
...
}
.sheet(isPresented: $showSheet, content: {
Text("SheetView")
})
Artık NavigationBarItem
elemanlarından birine bastığımız zaman yukarıdaki Text
elemanı ortaya çıkacaktır, çünkü o butonlara basılarak ancak showSheet
değerini değiştirebiliyoruz.
Text
yerine gerçek bir ekranı ortaya çıkartabilmek için yeni bir “SwiftUI View” dosyası oluşturalım. Burada yaptığımız uygulama küçücük bir uygulama olduğu için bu sayfada da uygulamaya uyan birkaç içerik serpiştirdim.
struct FirstSheetView: View { // MARK: - Properties
@Binding var showSheet: Bool // MARK: - UI Elements
var body: some View {
NavigationView {
Form {
Text("Playlist oluşturabilirsin.")
Text("Albüm olarak kayıt edebilirsin.")
Text("Sanatçı olarak kaydedebilirsin.")
}
.navigationBarTitle("Nasıl Düzenlerim?", displayMode: .inline)
}
}
}
Her iki butonun da farklı ekranları açması için üstteki yeni bir “SwiftUI View” dosyası daha oluşturalım.
struct SecondSheetView: View { // MARK: - Properties
@State var musicName: String = ""
// MARK: - UI Elements
var body: some View {
NavigationView {
Form {
Section(
header: Text("Yeni Müzik")
) {
VStack {
Image(systemName: "music.quarternote.3")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30, alignment: .center)
.padding() HStack {
Text("Name: \(musicName)")
Spacer()
}
TextField("Müzik Adı", text: $musicName)
}
}
}
.navigationBarTitle("Müzik Ekle")
}
}
}
Aynı ekranın içerisinden iki farklı “sheet” ekranı çıkartmak için SwiftUI’da biraz dolanmamız gerekecek çünkü bizim sheet
“modifier”ına bir tane çıktı değer vermemiz gerekecek. Bunu verebilmek için de belli bir değere göre bu iki View
elemanımızdan birini seçmemiz gerekecek. Bunun için “SheetNavigator” adında bir sınıf oluşturalım. Bu sınıf sayesinde hangi ekranın açılıp hangisinin açılmayacağını kaydedeceğiz.
class SheetNavigator: ObservableObject { // MARK: - Enumaretions
enum SheetDestination {
case firstView
case secondView
} // MARK: - Properties
static let shared = SheetNavigator()
@Published var firstViewIsPresented = false
@Published var secondViewIsPresented = false
var sheetDestination: SheetDestination = .secondView
}
“SheetNavigator” sınıfımızı “ContentView” elemanında tutmak için bu şekilde tanımlamalıyız.
@ObservedObject var sheetNavigator = SheetNavigator.shared
Hangi butona tıklandığı zaman hangi ekranın ortaya çıkacağını belirtmek için aşağıdaki gibi navigationBarItems
“modifier”ını değiştirelim.
.navigationBarItems(leading:
Button(action: {
sheetNavigator.firstViewIsPresented = true
sheetNavigator.secondViewIsPresented = false
showSheet = true
showModally = true
}) {
Image(systemName: "gearshape.fill")
.font(.system(size: 21))
}, trailing:
Button(action: {
sheetNavigator.firstViewIsPresented = false
sheetNavigator.secondViewIsPresented = true
showSheet = true
showModally = true
}) {
Image(systemName: "plus.circle.fill")
.font(.system(size: 21))
}
)
Artık sheet
“modifier”ını değiştirebiliriz. Aslında yapacağımız tek şey bir if
yapısı ile sheetNavigatior
özelliğinin içerisindeki değerlere göre ekran göstermesini sağlamak.
.sheet(isPresented: $showSheet, content: {
if sheetNavigator.firstViewIsPresented {
FirstSheetView()
} else {
SecondSheetView()
}
})
Son olarak, “sheet” ekranlarından kullanıcıların nasıl geri dönebileceklerini kodlamaya geldi.
Normalde “sheet” olmayan ekranlarda, makalenin başında olduğu gördüğünüz gibi, yeni bir ekrana gidildiği zaman geri gitme butonları oluşur ama “sheet” ekranlarda bu böyle olmuyor. Onun yerine “sheet” ekranlardan kaydırarak veya butona basarak kaldırabiliyoruz. Şu ana kadar yaptığımız “sheet” ekranlarını da sadece kaydırarak kaldırabilirsiniz. Bu çoğu uygulama için pek de iyi bir kullanıcı deneyimi oluşturmaz. Genellikle kullanıcılar “sheet” ekranlardan çıkmak için bir butona tıklamayı tercih ederler. Yani “sheet” ekranlarımız için birer “Vazgeç” butonuna ihtiyacımız var. Bu butonları NavigationBarItem
olarak tanımlayabiliriz.
İlk önce “FirstView” elemanına bir buton tanımlayalım aynı aşağıdaki gibi.
// MARK: - UI Elements
var body: some View {
NavigationView {
Form {
Text("Playlist oluşturabilirsin.")
Text("Albüm olarak kayıt edebilirsin.")
Text("Sanatçı olarak kaydedebilirsin.")
}
.navigationBarTitle("Nasıl Düzenlerim?", displayMode: .inline)
.navigationBarItems(
leading: Button(action: {
}) {
Text("Vazgeç")
}
)
}
}
Ekranımızı kaldırmak için SwiftUI’ın bize sunmuş olduğu bir elemanı kullanacağız: presentationMode
. Hemen onu “FirstView” içerisinde tanımalayalım.
// MARK: - Properties
@Environment(\.presentationMode) var presentationMode
Sonrasında az önce oluşturduğumuz butonun action özelliğine presentationMode
elemanının dismiss()
metodunu çağıralım.
.navigationBarItems(
leading:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Vazgeç")
}
)
“FirstView” ekranımızın artık tamamen hazır! Sıra “SecondSheetView” ekranında. Şimdiden söyleyeyim, o birazcık daha uzun sürecek. 😋
Bazı durumlarda uygulamanızda kullandığınız bir “sheet” ekranının kullanıcının istediği gibi kaldırabilmesini istemeyebilirsiniz. Örneğin, biz de “SecondSheetView” için bunu istiyoruz: Kullanıcı “SecondSheetView”dan çıkabilmek için yapabileceği tek şey az önce oluşturduğumuz gibi bir “Vazgeç” butonu olsun ve başka türlü o ekrandan çıkamasın, yani kaydırma işlemi yaparak ekrandan çıkamasın. Bunu yapabilmek için ekstra elemanlara ihtiyacımız olacak çünkü normalde SwiftUI bu yapacağımız işi kendi içerisinde barındırmıyor.
SwiftUI’da yapamadığımız ama UIKit’te yapabildiğimiz neredeyse bütün özellikleri SwiftUI’da da yapabilmemizi sağlayan UIViewControllerRepresentable
yardımıyla “ModelView” adında bir sınıf oluşturalım.
struct ModalView<T: View>: UIViewControllerRepresentable {}
Bu sınıf, bir ekranın kaydırılabilir olup olmayacağını tanımlamamızı sağlayacak. Bunun için temel üç tane özelliğe ihtiyacımız var.
struct ModalView<T: View>: UIViewControllerRepresentable { // MARK: - Properties
let view: T
@Binding var isModal: Bool
let onDismissalAttempt: (()->())?
}
view
özelliği sınıfımızın cenerik bir sınıf olmasını sağlamak içindir. isModel
ise View
elemanının kaydırabilir olup olmadığını belirlememize yarar. onDismissalAttempt
ise bir opsiyonel Void
elemanıdır. Onun sayesinde bu söylediğimiz aksiyonları alabileceğiz.
struct ModalView<T: View>: UIViewControllerRepresentable { // MARK: - Properties
let view: T
@Binding var isModal: Bool
let onDismissalAttempt: (()->())? // MARK: - Functions
func makeUIViewController(context: Context) -> UIHostingController<T> {
UIHostingController(rootView: view)
}
}
SwiftUI elemanlarını UIKit elemanlarına çevirmek için kullanılan UIHostingController
ile “ModelView” sınıfının çıktısını alıyoruz denilebilir.
Geri kalanlar ise “ModalView” sınıfının temel olarak çalışabilmesini sağlamak içindir.
struct ModalView<T: View>: UIViewControllerRepresentable { // MARK: - Properties
let view: T
@Binding var isModal: Bool
let onDismissalAttempt: (()->())? // MARK: - Functions
func makeUIViewController(context: Context) -> UIHostingController<T> {
UIHostingController(rootView: view)
} func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) {
uiViewController.parent?.presentationController?.delegate = context.coordinator
} func makeCoordinator() -> Coordinator {
Coordinator(self)
} class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate {
let modalView: ModalView init(_ modalView: ModalView) {
self.modalView = modalView
} func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
!modalView.isModal
} func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
modalView.onDismissalAttempt?()
}
}
}
Şimdi bu sınıfı bir View
metodunun içerisinde kullanacağız. Bunun için aşağıdaki View
“extension”ınını oluşturalım.
extension View {}
Sonrasında “presentation” adında bir metod tanımlayalım. Metodun çıktısı “some View” olduğu için bu metod ile bir arayüz elemanı oluşturabiliyoruz.
extension View { // MARK: - Functions
func presentation(isModal: Binding<Bool>, onDismissalAttempt: (()->())? = nil) -> some View {
ModalView(view: self, isModal: isModal, onDismissalAttempt: onDismissalAttempt)
}
}
Bu metodu kullanmadan önce “SecondSheetView” elemanın içerisinde @Binding
operatörü ile bir Bool
değeri tanımlayalım.
@Binding var showSheet: Bool
Bu değer sayesinde objeler arasında veri alışverişi yapabileceğiz.
@Binding
Nedir?
@Binding
bir özelliği doğrudan veri depolamak yerine başka bir yerde saklanan bir veri kaynağına bağlayan bir property wrapper ifadesidir.@Binding
ile alakalı daha fazla bilgi için aşağıdaki yazımızı okuyabilirsiniz:
“SecondSheetView” için de bir “Vazgeç” NavigationBarItem
elemanı oluşturalım ve action özelliğinde showSheet
değerini false
olarak ayarlayalım.
.navigationBarItems(
leading: Button(action: {
showSheet = false
}) {
Text("Vazgeç")
}
)
Bu sayede “Vazgeç” butonuna basınca ekran ortadan kaybolacak.
Şu an Xcode’dan hata mesajı alıyor olmasınız çünkü “ContentView” içerisinde kullandığınız “SecondSheetView” elemanı sizden showSheet
özelliğini tanımlamanızı isteyecektir. Hatayı düzeltmek için “SecondSheetView” elemanını aşağıdaki gibi değiştirmeliyiz.
.sheet(isPresented: $showSheet, content: {
if sheetNavigator.firstViewIsPresented {
FirstSheetView()
} else {
SecondSheetView(showSheet: $showSheet) // Burası!
}
})
Uygulamamız neredeyse bitti sayılır. “SecondSheetView” elemanının kaydırılamaz olmasını sağlamak için tek yapmamız gereken üstteki “SecondSheetView” elemanında presentation
metodunu kullanmaktır.
SecondSheetView(showSheet: $showSheet)
.presentation(isModal: $showModally)
Artık projemizi çalıştırıp uygulamamızı deneyimleyebiliriz!
Bu yazımızda yapmış olduğumuz projemize buradan ulaşabilirsiniz:
Apple SwiftUI’ı tanıttığı ilk sene SwiftUI’da özellikle uygulama navigasyonu ile alakalı gerçekten büyük problemler vardı. Bu makaleden de görebileceğimiz üzere artık SwiftUI’da bu konu ile alakalı pek bir sorun yaşamıyoruz.
Umarım sizler için faydalı bir yazı olmuştur. Bir sonraki yazımızda görüşmek üzere! 🤠
Yazı Önerisi 👀
NavigationView
elemanı aslında UINavigationBar
elemanının başka bir versiyonudur: UINavigationBar
bir UIKit elemanıdır. UINavigationBar
ile alakalı daha fazla bilgi öğrenmek için aşağıdaki makalemizi okuyabilirsiniz.