Implementasi pola koordinator yang sederhana, kuat dan elegan di SwiftUi. Stinsen ditulis menggunakan 100% SwiftUi yang membuatnya bekerja dengan mulus di seluruh perangkat iOS, TVOS, WatchOS dan MacOS.
Kita semua tahu perutean di Uikit bisa sulit dilakukan dengan elegan ketika bekerja dengan aplikasi dengan ukuran yang lebih besar atau ketika mencoba menerapkan pola arsitektur seperti MVVM. Sayangnya, SwiftUi di luar kotak menderita banyak masalah yang sama seperti UIKIT: konsep-konsep seperti NavigationLink hidup dalam lapisan-pandang, kami masih belum memiliki konsep aliran dan rute yang jelas, dan sebagainya. Stinsen diciptakan untuk mengurangi rasa sakit ini, dan merupakan implementasi dari pola koordinator . Ditulis dalam SwiftUi, itu benar-benar lintas platform dan menggunakan alat asli seperti @EnvironmentObject . Tujuannya adalah untuk membuat Stinsen merasa seperti alat yang hilang di SwiftUi, menyesuaikan dengan gaya pengkodean dan prinsip -prinsip umum.
Biasanya di SwiftUi, tampilan harus menangani menambahkan tampilan lain ke tumpukan navigasi menggunakan NavigationLink . Apa yang kita miliki di sini adalah kopling yang ketat di antara pandangan, karena pandangan harus diketahui sebelumnya semua pandangan lain yang dapat dinavigasi di antaranya. Juga, pandangan tersebut melanggar prinsip responsibilitas tunggal (SRP). Menggunakan pola koordinator, yang disajikan kepada komunitas iOS oleh Soroush Khanlou di Konferensi NSSPAIN pada tahun 2015, kami dapat mendelegasikan tanggung jawab ini kepada kelas yang lebih tinggi: Koordinator.
Contoh Menggunakan Tumpukan Navigasi:
final class UnauthenticatedCoordinator : NavigationCoordinatable {
let stack = NavigationStack ( initial : UnauthenticatedCoordinator . start )
@ Root var start = makeStart
@ Route ( . modal ) var forgotPassword = makeForgotPassword
@ Route ( . push ) var registration = makeRegistration
func makeRegistration ( ) -> RegistrationCoordinator {
return RegistrationCoordinator ( )
}
@ ViewBuilder func makeForgotPassword ( ) -> some View {
ForgotPasswordScreen ( )
}
@ ViewBuilder func makeStart ( ) -> some View {
LoginScreen ( )
}
} @Route S mendefinisikan semua rute yang mungkin dapat dilakukan dari koordinator saat ini dan transisi yang akan dilakukan. Nilai di sisi kanan adalah fungsi pabrik yang akan dieksekusi saat routing. Fungsi dapat mengembalikan tampilan SwiftUi atau koordinator lain. @Root jenis rute lain yang tidak memiliki transisi, dan digunakan untuk mendefinisikan tampilan pertama tumpukan navigasi koordinator, yang dirujuk oleh NavigationStack -Class.
Stinsen Out of the Box memiliki dua jenis protokol Coordinatable yang dapat diimplementasikan koordinator Anda:
NavigationCoordinatable - untuk aliran navigasi. Pastikan untuk membungkus ini dalam navigationViewCoordinator jika Anda ingin mendorong tumpukan navigasi.TabCoordinatable - untuk Tabviews. Selain itu, Stinsen juga memiliki dua koordinator yang dapat Anda gunakan, ViewWrapperCoordinator dan NavigationViewCoordinator . ViewWrapperCoordinator adalah koordinator yang dapat Anda subkelas atau gunakan segera untuk membungkus koordinator Anda dalam sebuah tampilan, dan NavigationViewCoordinator adalah subkelas ViewWrapperCoordinator yang membungkus koordinator Anda dalam NavigationView .
Tampilan untuk koordinator dapat dibuat menggunakan .view() , jadi untuk menunjukkan koordinator kepada pengguna Anda hanya akan melakukan sesuatu seperti:
struct StinsenApp : App {
var body : some Scene {
WindowGroup {
MainCoordinator ( ) . view ( )
}
}
} Stinsen dapat digunakan untuk memberi daya pada seluruh aplikasi Anda, atau hanya bagian dari aplikasi Anda. Anda masih dapat menggunakan NavigationLink SwiftUi yang biasa dan menyajikan lembar modal di dalam tampilan yang dikelola oleh Stinsen , jika Anda ingin melakukannya.
Menggunakan router, yang memiliki referensi ke koordinator dan tampilan, kami dapat melakukan transisi dari tampilan. Di dalam tampilan, router dapat diambil menggunakan @EnvironmentObject . Menggunakan router seseorang dapat beralih ke rute lain:
struct TodosScreen : View {
@ EnvironmentObject var todosRouter : TodosCoordinator . Router
var body : some View {
List {
/* ... */
}
. navigationBarItems (
trailing : Button (
action : {
// Transition to the screen to create a todo:
todosRouter . route ( to : . createTodo )
} ,
label : {
Image ( systemName : " doc.badge.plus " )
}
)
)
}
} Anda juga dapat menjemput router yang merujuk koordinator yang muncul lebih awal di pohon. Misalnya, Anda mungkin ingin mengganti tab dari tampilan yang ada di dalam TabView .
Routing dapat dilakukan secara langsung pada koordinator itu sendiri, yang dapat bermanfaat jika Anda ingin koordinator Anda memiliki beberapa logika, atau jika Anda melewati koordinator di sekitar:
final class MainCoordinator : NavigationCoordinatable {
@ Root var unauthenticated = makeUnauthenticated
@ Root var authenticated = makeAuthenticated
/* ... */
init ( ) {
/* ... */
cancellable = AuthenticationService . shared . status . sink { [ weak self ] status in
switch status {
case . authenticated ( let user ) :
self ? . root ( . authentiated , user )
case . unauthenticated :
self ? . root ( . unauthentiated )
}
}
}
} Tindakan apa yang dapat Anda lakukan dari router/koordinator tergantung pada jenis koordinator yang digunakan. Misalnya, menggunakan NavigationCoordinatable , beberapa fungsi yang dapat Anda lakukan adalah:
popLast - Menghapus item terakhir dari tumpukan. Perhatikan bahwa Stinsen tidak peduli jika tampilan disajikan secara modal atau didorong, fungsi yang sama digunakan untuk keduanya.pop - Menghapus pemandangan dari tumpukan. Fungsi ini hanya dapat dilakukan oleh router, karena hanya router yang tahu tentang pandangan mana yang Anda coba muncul.popToRoot - Membersihkan tumpukan.root - Mengubah root (yaitu tampilan pertama tumpukan). Jika root sudah menjadi root aktif, tidak akan melakukan apa pun.route - menavigasi ke rute lain.focusFirst - Menemukan rute yang ditentukan jika ada di tumpukan, mulai dari item pertama. Jika ditemukan, akan menghapus semuanya setelah itu.dismissCoordinator - Menghapus seluruh koordinator dan itu adalah anak -anak yang terkait dari pohon.
Kloning repo dan jalankan StinsenApp dalam contoh/aplikasi untuk merasakan bagaimana Stinsen dapat digunakan. StinsenApp bekerja di iOS, TVOS, WatchOS dan MacOS. Ia mencoba untuk menampilkan banyak fitur yang tersedia Stinsen untuk Anda gunakan. Sebagian besar kode dari readme ini berasal dari aplikasi sampel. Ada juga contoh yang menunjukkan bagaimana Stinsen dapat digunakan untuk menerapkan arsitektur MVVM-C yang dapat diuji di SwiftUi, yang tersedia dalam contoh/MVVM .
Karena @EnvironmentObject saja dapat diakses dalam sebuah View , Stinsen menyediakan beberapa cara untuk merutekan dari ViewModel. Anda dapat menyuntikkan koordinator melalui ìnitializer, atau mendaftarkannya saat pembuatan dan menyelesaikannya di viewmodel melalui kerangka injeksi ketergantungan. Ini adalah cara yang disarankan untuk melakukan ini, karena Anda akan memiliki kontrol dan fungsionalitas maksimum.
Cara lain adalah melewati router menggunakan fungsi onAppear :
struct TodosScreen : View {
@ StateObject var viewModel = TodosViewModel ( )
@ EnvironmentObject var projects : TodosCoordinator . Router
var body : some View {
List {
/* ... */
}
. onAppear {
viewModel . router = projects
}
}
} Anda juga dapat menggunakan apa yang disebut RouterStore untuk mengambil kembali router. RouterStore menyimpan instance router dan Anda bisa mendapatkannya melalui PropertyWrapper khusus.
Untuk mengambil router:
class LoginScreenViewModel : ObservableObject {
// directly via the RouterStore
var main : MainCoordinator . Router ? = RouterStore . shared . retrieve ( )
// via the RouterObject property wrapper
@ RouterObject
var unauthenticated : Unauthenticated . Router ?
init ( ) {
}
func loginButtonPressed ( ) {
main ? . root ( . authenticated )
}
func forgotPasswordButtonPressed ( ) {
unauthenticated ? . route ( to : . forgotPassword )
}
}Untuk melihat contoh ini beraksi, silakan periksa MVVM-APP dalam contoh/MVVM .
Terkadang Anda ingin menyesuaikan tampilan yang dihasilkan oleh koordinator Anda. NavigationCoordinatable dan tabcoordinatable memiliki customize -fungsi yang dapat Anda terapkan untuk melakukannya:
final class AuthenticatedCoordinator : TabCoordinatable {
/* ... */
@ ViewBuilder func customize ( _ view : AnyView ) -> some View {
view
. onReceive ( Services . shared . $authentication ) { authentication in
switch authentication {
case . authenticated :
self . root ( . authenticated )
case . unauthenticated :
self . root ( . unauthenticated )
}
}
}
}
} Ada juga ViewWrapperCoordinator yang dapat Anda gunakan untuk menyesuaikan juga.
Karena sebagian besar fungsi pada koordinator/router mengembalikan koordinator, Anda dapat menggunakan hasilnya dan rantai mereka bersama untuk melakukan routing yang lebih maju, jika diperlukan. Misalnya, untuk membuat tombol SwiftUi yang akan mengubah tab dan memilih TODO khusus dari mana saja di aplikasi setelah login:
VStack {
ForEach ( todosStore . favorites ) { todo in
Button ( todo . name ) {
authenticatedRouter
. focusFirst ( . todos )
. child
. popToRoot ( )
. route ( to : . todo , todo . id )
}
}
} AuthenticatedCoordinator yang dirujuk oleh authenticatedRouter adalah TabCoordinatable , sehingga fungsinya akan:
focusFirst : Kembalikan tab pertama yang diwakili oleh rute todos dan jadikan itu tab aktif, kecuali sudah adalah yang aktif.child : Akan Mengembalikan Anaknya, Todos -Tab adalah NavigationViewCoordinator dan The Child adalah NavigationCoordinatable .popToRoot : Akan mengeluarkan anak -anak yang mungkin atau mungkin tidak ada.route : Akan rute ke rute Todo dengan ID yang ditentukan. Karena Stinsen menggunakan tombol untuk mewakili rute, fungsinya adalah jenis yang aman dan tidak valid tidak dapat dibuat. Ini berarti: Jika Anda memiliki rute di A ke B dan di B ke C , aplikasi tidak akan dikompilasi jika Anda mencoba untuk merutekan dari A ke C tanpa rute ke B terlebih dahulu. Juga, Anda tidak dapat melakukan tindakan seperti popToRoot() pada TabCoordinatable dan sebagainya.
Menggunakan nilai yang dikembalikan, Anda dapat dengan mudah mengalami penolakan di dalam aplikasi:
final class MainCoordinator : NavigationCoordinatable {
@ ViewBuilder func customize ( _ view : AnyView ) -> some View {
view . onOpenURL { url in
if let coordinator = self . hasRoot ( . authenticated ) {
do {
// Create a DeepLink-enum
let deepLink = try DeepLink ( url : url , todosStore : coordinator . todosStore )
switch deepLink {
case . todo ( let id ) :
coordinator
. focusFirst ( . todos )
. child
. route ( to : . todo , id )
}
} catch {
print ( error . localizedDescription )
}
}
}
}
} Stinsen hadir dengan beberapa koordinatables untuk tampilan SwiftUi standar. Jika Anda misalnya ingin menggunakannya untuk hamburger-menu, Anda perlu membuat sendiri. Periksa kode sumber untuk mendapatkan inspirasi.
Stinsen mendukung dua cara pemasangan, Cocoapods dan SPM.
Buka XCODE dan proyek Anda, klik File / Swift Packages / Add package dependency... Di TextField " Masukkan URL Repositori Paket ", tulis https://github.com/rundfunk47/stinsen dan tekan berikutnya dua kali
Buat Podfile di direktori root aplikasi Anda. Menambahkan
# Podfile
use_frameworks!
target 'YOUR_TARGET_NAME' do
pod 'Stinsen'
end
DoubleColumnNavigationViewStyle . Alasan untuk ini adalah karena itu tidak berfungsi seperti yang diharapkan karena masalah dengan isActive di SwiftUi. Penanganan Masalah: Gunakan UIViewRepresentable atau buat implementasi Anda sendiri.Di BYVA kami berusaha untuk membuat aplikasi SWIFTUI 100%, jadi wajar jika kami perlu membuat kerangka kerja koordinator yang memenuhi ini dan kebutuhan lain yang kami miliki. Kerangka kerja ini digunakan dalam produksi dan mengelola ~ 50 aliran dan ~ 100 layar. Kerangka kerja dipertahankan oleh @rundfunk47.
Stins pendek dalam bahasa Swedia untuk "station master", dan Stinsen adalah artikel yang pasti, "master stasiun". Bahasa sehari -hari istilah ini sebagian besar digunakan untuk merujuk pada operator kereta api, yang bertanggung jawab untuk merutekan kereta. Logo ini didasarkan pada patung kayu dari celah yang terletak di dekat stasiun kereta di Linköping, Swedia.
Perubahan terbesar di Stinsen V2 adalah bahwa ia lebih aman daripada Stinsen V1, yang memungkinkan untuk lebih mudah rantai dan ikatan dalam, antara lain.
AnyCoordinatable telah diganti dengan protokol. Itu tidak melakukan tugas yang sama seperti yang lama dan tidak AnyCoordinatable dan tidak cocok dengan perutean yang lebih aman dari versi 2, jadi hapus dari proyek Anda.route(to: .a) kami menggunakan route(to: .a) ..view() .@Root S dan beralih di antara mereka menggunakan .root() untuk mendapatkan fungsionalitas yang sama.Stinsen dirilis di bawah lisensi MIT. Lihat lisensi untuk informasi lebih lanjut.