Perkenalan
Setelah absen panjang tidak merilis konten blog baru, saya kembali dengan konten
iOS. Saya telah mendapatkan wawasan berharga tentang iOS dan Pemrograman Swift
di Apple Developer Academy. Sekarang, dengan pembaruan iOS 17, kita memiliki
makro baru Observable
dan Framework Observable. Selain itu, sistem navigasi
terbaru yang diperkenalkan di iOS 16, NavigationStack
, sangatlah keren, dan
kita akan memanfaatkannya sebaik mungkin.
Penggunaan NavigationStack
Implementasi biasa dari NavigationStack
adalah dengan menempatkan
NavigationLink
untuk memungkinkan pengguna menavigasi dengan menekannya.
struct ContentView: View {
let products: [Product]
var body: some View {
NavigationStack {
List(products) { product in
NavigationLink(product.title) {
ProductDetailView(product: product)
}
}
.navigationTitle("Products")
}
}
}
struct ProductDetailView: View {
let product: Product
var body: some View {
Text(product.title)
.font(.title)
.navigationTitle(product.title)
}
}swift
Pada contoh di atas, kita menyiapkan konfigurasi dasar master-detail. Kita
menempatkan NavigationStack
di bagian atas tata letak tampilan kita.
Selanjutnya, kita menuliskan daftar message, setiap berfungsi sebagai tautan ke
layar detailnya. Perhatikan, kita tetap menggunakan tipe NavigationLink
tradisional, dan hal ini berfungsi dengan baik di sini.
Navigator Pattern
Saya menikmati mengorganisir alur navigasi fitur saya di satu lokasi pusat. Itulah mengapa saya biasanya menggunakan pola Navigator untuk mengelola navigasi dengan cara yang aman berdasarkan tipe. Mengimplementasikan pola Navigator dengan API Navigasi baru SwiftUI mudah dilakukan. Pada awalnya, kita perlu membuat tipe enum yang menguraikan semua rute untuk aplikasi, fitur, atau modul kita.
enum Route: Hashable {
case first
case second
case third
case fourth
}swift
Selanjutnya, kita mendefinisikan tampilan berdasarkan rute yang kita tentukan sebelumnya.
struct Routes: View {
let route: Route
var body: some View {
switch route {
case .first:
FirstView()
case .second:
SecondView()
case .third:
ThirdView()
case .fourth:
FourthView()
}
}
}swift
Setelah itu, kita seharusnya membuat satu pengontrol tunggal untuk navigasi. Ini
dapat dilakukan dengan menggunakan ViewModel. Ingat ketika saya menyebutkan
sebelumnya bahwa kita dapat memanfaatkan Observable Framework baru? Kita akan
mendefinisikan ViewModel
dengan menggunakan makro Observable
.
@Observable
class RouteViewModel {
static var shared = RouteViewModel()
var navPath = [Route]() {
willSet {
previousRoute = navPath.last
}
}
private(set) var previousRoute: Route?
var currentRoute: Route? {
navPath.last
}
// MARK: - Append route to navigation path
/// Append AKA go to next route
func append(_ route: Route, before: (() -> Void)? = nil) {
before?()
navPath.append(route)
}
// MARK: - Pop route from navigation path
/// Pop AKA return to previous view in the navigation stack
func pop(before: (() -> Void)? = nil) {
guard !navPath.isEmpty else {
print("navPath is empty")
return
}
before?()
navPath.removeLast()
}
// MARK: - Pop multiple routes
/// Return to previous view multiple times in the navigation stack
func pop(_ count: Int, before: (() -> Void)? = nil) {
guard navPath.count >= count else {
print("count must not be greater than navPath.count")
return
}
before?()
navPath.removeLast(count)
}
// MARK: - Pop to root
/// Back to root view
func popToRoot(before: (() -> Void)? = nil) {
before?()
navPath.removeLast(navPath.count)
}
// MARK: - Append multiple routes
/// Append multiple routes to navigation stack
func append(_ routes: Route..., before: (() -> Void)? = nil) {
before?()
routes.forEach { route in
navPath.append(route)
}
}
}swift
Sepertinya kode yang cukup panjang, bukan? Variabel navPath
adalah sumber
kebenaran, menyimpan daftar route. Ini adalah array route dan kita dapat
memodifikasinya seperti array lainnya. Kita telah menentukan beberapa metode di
sana untuk membantu kita dalam menavigasi dan memodifikasi navPath
itu
sendiri. ViewModel
ini akan digunakan secara global di seluruh aplikasi, jadi
kita harus membuat Environment
.
Mendefinisikan EnvironmentValues
Untuk mendefinisikan Environment navigate
, kita dapat melakukan extend
EnvironmentValues
dari SwiftUI. Tetapi pertama, kita perlu mendefinisikan
EnvironmentKey
dengan defaultValue dari ViewModel
itu sendiri.
struct NavigationEnvironmentKey: EnvironmentKey {
static var defaultValue: RouteViewModel = .init()
}swift
Setelah itu, kita dapat mengextend EnvironmentValues
. Saya memberinya nama
navigate
, tetapi Anda dapat menggunakan nama lain sesuai keinginan Anda.
extension EnvironmentValues {
var navigate: RouteViewModel {
get { self[NavigationEnvironmentKey.self] }
set { self[NavigationEnvironmentKey.self] = newValue }
}
}swift
Sekarang kita dapat menggunakan environment secara global. Selanjutnya, kita
perlu menggunakan ViewModel
dalam NavigationStack
. Dalam implementasi ini,
kita akan melakukannya di dalam struktur App
.
Mengimplementasikan NavigationStack
Kita harus menggunakan Bindable
macro alih-alih State
karena
NavigationStack
perlu untuk melakukan bind pada NavigationPath
.
struct SwiftRoutingApp: App {
@Bindable private var routeViewModel = RouteViewModel.shared
var body: some Scene {
WindowGroup {
NavigationStack(path: $routeViewModel.navPath) {
ContentView().navigationDestination(for: Route.self) { route in
Routes(route: route)
}
}.environment(\.navigate, routeViewModel)
}
}
}swift
Sekarang kita memiliki satu NavigationStack
yang membungkus ContentView
, dan
kita melampirkan view modifier navigationDestination
serta menggunakan
struktur Routes yang sebelumnya telah kita tentukan untuk memetakan tampilan di
dalam NavigationStack
. Jangan lupa menggunakan view modifier environment
agar dapat mengakses view model di dalam child view.
Penggunaan
Anda seharusnya menyadari bahwa dalam pemetaan Routes yang sebelumnya telah ditentukan, ada beberapa views di sana, sekarang kita akan menggunakannya untuk menunjukkan sistem navigasi baru kita. Tampilan itu sendiri tidak terlalu kompleks tetapi sudah cukup untuk keperluan demonstrasi.
struct FirstView: View {
@Environment(\.navigate) private var navigate
var body: some View {
Text("First View")
Button("To second view") {
navigate.append(.second)
}
}
}swift
struct SecondView: View {
@Environment(\.navigate) private var navigate
var body: some View {
Text("Second View")
Button("To third view") {
navigate.append(.third)
}
Button("Back to root") {
navigate.popToRoot()
}
}
}swift
struct ThirdView: View {
@Environment(\.navigate) private var navigate
var body: some View {
Text("Third View")
Button("To fourth view") {
navigate.append(.fourth)
}
Button("Pop two view") {
navigate.pop(2)
}
Button("Back to root") {
navigate.popToRoot()
}
}
}swift
struct FourthView: View {
@Environment(\.navigate) private var navigate
var body: some View {
Text("Fourth View")
Button("Pop") {
navigate.pop()
}
Button("Pop to second view") {
navigate.pop(2)
}
Button("Back to root") {
navigate.popToRoot()
}
}
}swift
struct ContentView: View {
var body: some View {
FirstView()
}
}swift
Ketika Anda menjalankan aplikasi, tampilan pertama akan muncul. Tekan tombol
untuk navigasi ke tampilan kedua. Sekarang, cobalah berinteraksi dengan semua
tombol dan lihat seberapa keren sistem ini. Anda tidak perlu menggunakan
NavigationLink
di mana-mana; cukup panggil metode navigate
. Anda juga dapat
memanggil ini di dalam fungsi atau pemicu apa pun.
Tentu saja, source codenya tersedia di GitHub.
Kesimpulan
Saya sangat antusias menggunakan API Navigasi berbasis data baru di SwiftUI. Semoga Anda menikmati tulisan ini. Jangan ragu untuk menghubungi saya dan ajukan pertanyaan terkait tulisan ini. Terima kasih telah membaca, dan sampai jumpa di blog berikutnya!