Ana içeriğe geç
  1. 100 Günde SwiftUI Notları/

43.Gün - SwiftUI Navigation: Navigation Giriş

Bu proje ile SwiftUI Navigation konusuna odaklanacağız. Navigation iki ana türe ayrılır.

  • Navigation drive by user interaction (kullanıcı etkileşimi ile yönlendirilen navigasyon)
  • Programmatic navigation (kendi tetiklediğimiz programatik navigasyon)

Giriş #

Bir ekrandan diğer ekrana geçiş için NavigationStack kullanılabilmektedir.

En basit biçimde NavigationLink kullanarak şöyle bir navigation oluşturabiliriz.

NavigationStack {
    NavigationLink("Tap Me") {
        Text("Detail View")
    }
}

Fakat burada gizli bir sorun karşımıza çıkıyor. Bu bizim için ileride ciddi performans sorunlarına yol açabilir. Örnek olarak şöyle bir DetailView’imiz olduğunu varsayalım;

struct DetailView: View {
    var number: Int

    var body: some View {
        Text("Detail View \(number)")
    }

    init(number: Int) {
        self.number = number
        print("Creating detail view \(number)")
    }
}

ContentView içerisinden de aşağıdaki şekilde NavigationLink kullanarak DetailView’e erişebiliriz.

NavigationStack {
    List(0..<1000) { i in
        NavigationLink("Tap Me") {
            DetailView(number: i)
        }
    }
}

ContentView Navigation

Görüldüğü gibi 1000 tane detay’a gidebildiğimiz bir navigation’ımız var.

İşte sorun burada başlıyor: navigation’ı tetikleyecek herhangi bir yere tıklamayın. Xcode debug console alanına baktığınızda bir çok DetailView instance’ının oluşturulduğunu göreceksiniz. Hatta List’i aşağıya doğru scroll ettiğinizde hala daha instance oluşturulduğunu görebilirsiniz.

Not: DetailView initializer’da print("Creating detail view \(number)") satırı nedeniyle, her instance oluşturulduğunda console bu yazı yazdırılmaktadır.

Debug Console Created DetailView

NavigationStack {
    NavigationLink("Tap Me") {
        Text("Detail View")
    }
}

Yukarıdaki kod bloğunda gördüğümüz gibi basit bir navigation oluşturmak için NavigationLink içinde her bir label hem de destination sağlarız. Fakat daha gelişmiş bir navigation için, destination’ı değerden ayırabiliriz, bu sayede destination sadece ihtiyaç duyulduğunda yüklenir.

Bu işlemi iki adımda gerçekleştirebiliriz;

  1. NavigationLink ’e Hashable protokole uygun bir değer ekleriz.
  2. Navigation stack içine navigationDestination() modifier’ını ekleyerek, verilerimizi aldığında ne yapacağını söyleriz.

Swift’in yerleşik türlerinin çoğu Hashable protokolüne uygundur. Örneğin, Int, String, Date, URL, Array ve Dictionary protokole uygundur.

İlk olarak, her biri NavigationLink olan ve 100 sayıdan oluşan bir List oluşturalım, dolayısıyla SwiftUI’ye bir sayıya gitmek istediğimizi söylüyoruz.

NavigationStack {
    List(0..<100) { i in
        NavigationLink("Select \(i)", value: i)
    }
}

Yukarıdaki kodumuz tam olarak çalışmayacaktır. Çünkü NavigationLink’in göstereceği label’i var fakat destination’ı yok.

navigationDestination() modifier’ı burada devreye giriyor: “bir tamsayıya gitmeniz istendiğinde, işte yapmanız gereken şey….” diyebiliriz. Kodumuzu şu şekilde değiştirelim;

NavigationStack {
    List(0..<100) { i in
        NavigationLink("Select \(i)", value: i)
    }
    .navigationDestination(for: Int.self) { selection in
        Text("You selected \(selection)")
    }
}

Bu sayede herhangi bir Int değeri ile navigation yapmak istediğimizde, bu değer bize selection ile verilmektedir. Bizde hedef view’ımız ile bu değeri kullanabiliriz.

Not: Birkaç farklı veri türümüz varsa, bunlara gitmek için birkaç navigationDestination() modifier eklememiz yeterlidir. Gerçekte “bir tamsayıya gitmek istediğinizde bunu yapın, ancak bir array’e gitmek istediğinizde şunu yapın” demiş oluruz.

Yukarıdaki sistem birçok veri için harika çalışır. Ancak özel struct’lar gibi daha karmaşık veriler için hashing kullanmamız gerekir.

Hashable Nedir? #

Hashing nedir? Örneğin internetten 10GB’lık veri indirdiğimizi varsayalım. Peki her bir veri parçasının başarıyla indirildiğinden nasıl emin olabiliriz? Hashing fonksiyonu kullanarak dosyanın hash’ini alırsak kısa bir string elde ederiz. Böylelikle sunucudaki hash ile bizim indirdiğimiz dosyanın hash’ını karşılaştırabiliriz. 10GB’lık iki dosyayı karşılaştırmak yerine iki kısa string’i karşılaştırmak elbette daha kolay. Hash fonksiyonları sayesinde her biri veri parçası için hash değeri benzersizdir ve tutarlıdır. Böylece aynı dosya için her defasında aynı hash değerini elde ederiz.

Swift’te tüm property’leri Hashable’a uygun özel bir struct oluşturursak, tüm struct’ı da Hashable’a çok kolay bir şekilde uygun hale getirebiliriz.

Örneğin, bu struct bir UUID, bir array ve bir tamsayı içerir;

struct Student {
    var id = UUID()
    var name: String
    var age: Int
}

Bu struct’ı Hashable’a uygun hale getirmek için şu şekilde bir ekleme yapmamız yeterlidir.

struct Student: Hashable {
    var id = UUID()
    var name: String
    var age: Int
}

Artık Student struct’ımız Hashable’a uygun olduğuna göre, tamsayılar veya array’ler gibi hem NavigationLink hem de navigationDestination() ile kullanılabililir.


Bu yazıyı İngilizce olarak da okuyabilirsiniz.
You can also read this article in English.

Bu yazı, SwiftUI Day 43 adresinde bulunan yazılardan kendim için aldığım notları içermektedir. Orjinal dersi takip etmek için lütfen bağlantıya tıklayın.