การใช้งานที่เรียบง่ายทรงพลังและสง่างามของรูปแบบผู้ประสานงานใน Swiftui Stinsen เขียนขึ้นโดยใช้ Swiftui 100% ซึ่งทำให้มันทำงานได้อย่างราบรื่นตลอด iOS, TVOS, WatchOs และอุปกรณ์ MacOS
เราทุกคนรู้ว่าการกำหนดเส้นทางใน Uikit อาจเป็นเรื่องยากที่จะทำอย่างสง่างามเมื่อทำงานกับแอปพลิเคชันที่มีขนาดใหญ่ขึ้นหรือเมื่อพยายามใช้รูปแบบสถาปัตยกรรมเช่น MVVM น่าเสียดายที่ Swiftui ออกจากกล่องทนทุกข์ทรมานจากปัญหาเช่นเดียวกับ Uikit: แนวคิดเช่น NavigationLink อาศัยอยู่ในมุมมองเรายังไม่มีแนวคิดที่ชัดเจนเกี่ยวกับการไหลและเส้นทางและอื่น ๆ Stinsen ถูกสร้างขึ้นเพื่อบรรเทาความเจ็บปวดเหล่านี้และเป็นการดำเนินการตาม รูปแบบผู้ประสานงาน เมื่อเขียนใน Swiftui มันเป็นข้ามแพลตฟอร์มอย่างสมบูรณ์และใช้เครื่องมือดั้งเดิมเช่น @EnvironmentObject เป้าหมายคือการทำให้ Stinsen รู้สึกเหมือนเป็นเครื่องมือที่หายไปใน Swiftui ซึ่งสอดคล้องกับรูปแบบการเข้ารหัสและหลักการทั่วไป
โดยปกติใน Swiftui มุมมองจะต้องจัดการกับการเพิ่มมุมมองอื่น ๆ ลงในสแต็กการนำทางโดยใช้ NavigationLink สิ่งที่เรามีที่นี่คือการมีเพศสัมพันธ์ที่แน่นหนาระหว่างมุมมองเนื่องจากมุมมองจะต้องรู้ล่วงหน้ามุมมองอื่น ๆ ทั้งหมดที่สามารถนำทางระหว่าง นอกจากนี้มุมมองยังเป็นการละเมิด หลักการตอบสนองเดียว (SRP) การใช้รูปแบบผู้ประสานงานนำเสนอต่อชุมชน iOS โดย Soroush Khanlou ในการประชุม NSSPain ในปี 2558 เราสามารถมอบหมายความรับผิดชอบนี้ให้กับชั้นเรียนที่สูงขึ้น: ผู้ประสานงาน
ตัวอย่างการใช้การนำทางสแต็ก:
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 กำหนดเส้นทางที่เป็นไปได้ทั้งหมดที่สามารถทำได้จากผู้ประสานงานปัจจุบันและการเปลี่ยนแปลงที่จะดำเนินการ ค่าทางด้านขวามือคือฟังก์ชั่นโรงงานที่จะดำเนินการเมื่อกำหนดเส้นทาง ฟังก์ชั่นสามารถส่งคืนทั้งมุมมอง Swiftui หรือผู้ประสานงานอื่น @Root เส้นทางประเภทอื่นที่ไม่มีการเปลี่ยนแปลงและใช้สำหรับการกำหนดมุมมองแรกของสแต็กการนำทางของผู้ประสานงานซึ่งอ้างอิงโดย NavigationStack -class
Stinsen ออกจากกล่องมีโปรโตคอล Coordinatable สองชนิดที่แตกต่างกันผู้ประสานงานของคุณสามารถนำไปใช้ได้:
NavigationCoordinatable - สำหรับการไหลของการนำทาง ตรวจสอบให้แน่ใจว่าได้ห่อสิ่งเหล่านี้ใน NavigationViewCoordinator หากคุณต้องการผลักดันสแต็กการนำทางTabCoordinatable - สำหรับ tabviews นอกจากนี้ Stinsen ยังมีผู้ประสานงานสองคนที่คุณสามารถใช้ได้คือ ViewWrapperCoordinator และ NavigationViewCoordinator ViewWrapperCoordinator เป็นผู้ประสานงานที่คุณสามารถ subclass หรือใช้ทันทีเพื่อห่อผู้ประสานงานของคุณในมุมมองและ NavigationViewCoordinator เป็นคลาสย่อย ViewWrapperCoordinator ที่ห่อหุ้มผู้ประสานงานของคุณใน NavigationView
มุมมองสำหรับผู้ประสานงานสามารถสร้างได้โดยใช้ .view() ดังนั้นเพื่อแสดงผู้ประสานงานให้กับผู้ใช้คุณจะทำอะไรบางอย่างเช่น:
struct StinsenApp : App {
var body : some Scene {
WindowGroup {
MainCoordinator ( ) . view ( )
}
}
} Stinsen สามารถใช้เพื่อเพิ่มพลังแอพทั้งหมดของคุณหรือเพียงส่วนหนึ่งของแอพของคุณ คุณยังสามารถใช้ Swiftui NavigationLink S และนำเสนอแผ่น Modal ภายในมุมมองที่จัดการโดย Stinsen หากคุณต้องการทำเช่นนั้น
การใช้เราเตอร์ซึ่งมีการอ้างอิงถึงทั้งผู้ประสานงานและมุมมองเราสามารถทำการเปลี่ยนจากมุมมอง ภายในมุมมองเราเตอร์สามารถดึงข้อมูลได้โดยใช้ @EnvironmentObject การใช้เราเตอร์หนึ่งสามารถเปลี่ยนเป็นเส้นทางอื่น:
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 " )
}
)
)
}
} นอกจากนี้คุณยังสามารถดึงเราเตอร์อ้างอิงผู้ประสานงานที่ปรากฏก่อนหน้านี้ในต้นไม้ ตัวอย่างเช่นคุณอาจต้องการสลับแท็บจากมุมมองที่อยู่ภายใน TabView
การกำหนดเส้นทางสามารถทำได้โดยตรงกับผู้ประสานงานซึ่งจะเป็นประโยชน์หากคุณต้องการให้ผู้ประสานงานของคุณมีตรรกะหรือถ้าคุณผ่านผู้ประสานงานไปรอบ ๆ :
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 )
}
}
}
} การกระทำใดที่คุณสามารถทำได้จากเราเตอร์/ผู้ประสานงานขึ้นอยู่กับประเภทของผู้ประสานงานที่ใช้ ตัวอย่างเช่นการใช้ NavigationCoordinatable ฟังก์ชั่นบางอย่างที่คุณสามารถทำได้คือ:
popLast - ลบรายการสุดท้ายออกจากสแต็ก โปรดทราบว่า stinsen ไม่สนใจว่ามุมมองถูกนำเสนอแบบโมฆะหรือผลักดันฟังก์ชั่นเดียวกันนี้ใช้สำหรับทั้งคู่pop - ลบมุมมองจากสแต็ก ฟังก์ชั่นนี้สามารถทำได้โดยเราเตอร์เท่านั้นเนื่องจากเราเตอร์เท่านั้นที่รู้ว่าคุณกำลังพยายามที่จะปรากฏตัวใดpopToRoot - ล้างสแต็กroot - เปลี่ยนรูท (เช่นมุมมองแรกของสแต็ก) หากรูทเป็นรูทที่ใช้งานอยู่แล้วจะไม่ทำอะไรเลยroute - นำทางไปยังเส้นทางอื่นfocusFirst - ค้นหาเส้นทางที่ระบุหากมีอยู่ในสแต็กเริ่มต้นจากรายการแรก หากพบจะลบทุกอย่างหลังจากนั้นdismissCoordinator - ลบผู้ประสานงานทั้งหมดและเป็นเด็กที่เกี่ยวข้องจากต้นไม้
โคลน repo และเรียกใช้ stinsenapp ใน ตัวอย่าง/แอพ เพื่อรับความรู้สึกว่าสามารถใช้ stinsen ได้อย่างไร Stinsenapp ทำงานบน iOS, TVOS, Watchos และ MacOS มันพยายามที่จะแสดงคุณสมบัติหลายอย่าง Stinsen มีให้คุณใช้ รหัสส่วนใหญ่จาก readme นี้มาจากแอพตัวอย่าง นอกจากนี้ยังมีตัวอย่างที่แสดงให้เห็นว่า stinsen สามารถใช้ในการใช้สถาปัตยกรรม MVVM-C ที่ทดสอบได้ใน Swiftui ซึ่งมีอยู่ใน ตัวอย่าง/MVVM
เนื่องจาก @EnvironmentObject สามารถเข้าถึงได้ภายใน View เท่านั้น Stinsen จึงมีวิธีการกำหนดเส้นทางสองวิธีจาก ViewModel คุณสามารถฉีดผู้ประสานงานผ่านìnitializerหรือลงทะเบียนที่การสร้างและแก้ไขใน ViewModel ผ่านกรอบการฉีดพึ่งพา นี่เป็นวิธีที่แนะนำในการทำเช่นนี้เนื่องจากคุณจะมีการควบคุมและฟังก์ชั่นสูงสุด
วิธีอื่น ๆ กำลังผ่านเราเตอร์โดยใช้ฟังก์ชัน onAppear :
struct TodosScreen : View {
@ StateObject var viewModel = TodosViewModel ( )
@ EnvironmentObject var projects : TodosCoordinator . Router
var body : some View {
List {
/* ... */
}
. onAppear {
viewModel . router = projects
}
}
} นอกจากนี้คุณยังสามารถใช้สิ่งที่เรียกว่า RouterStore เพื่อทำการแนะนำเราเตอร์ RouterStore บันทึกอินสแตนซ์ของเราเตอร์และคุณสามารถรับได้ผ่าน PropertyWrapper ที่กำหนดเอง
เพื่อดึงเราเตอร์:
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 )
}
}หากต้องการดูตัวอย่างนี้ในการดำเนินการโปรดตรวจสอบ MVVM-APP ใน ตัวอย่าง/MVVM
บางครั้งคุณต้องการปรับแต่งมุมมองที่สร้างโดยผู้ประสานงานของคุณ Navigation CORDINATABLE และ TABOCORDINATABLE มีการ customize -ฟังก์ชั่นที่คุณสามารถนำไปใช้เพื่อทำเช่นนั้น:
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 )
}
}
}
}
} นอกจากนี้ยังมี ViewWrapperCoordinator ที่คุณสามารถใช้ในการปรับแต่งได้เช่นกัน
เนื่องจากฟังก์ชั่นส่วนใหญ่ของผู้ประสานงาน/เราเตอร์ส่งคืนผู้ประสานงานคุณสามารถใช้ผลลัพธ์และห่วงโซ่พวกเขาเข้าด้วยกันเพื่อดำเนินการกำหนดเส้นทางขั้นสูงมากขึ้นหากจำเป็น ตัวอย่างเช่นในการสร้างปุ่ม Swiftui ที่จะเปลี่ยนแท็บและเลือกสิ่งที่ต้องทำเฉพาะจากทุกที่ในแอพหลังจากเข้าสู่ระบบ:
VStack {
ForEach ( todosStore . favorites ) { todo in
Button ( todo . name ) {
authenticatedRouter
. focusFirst ( . todos )
. child
. popToRoot ( )
. route ( to : . todo , todo . id )
}
}
} AuthenticatedCoordinator ที่อ้างอิงโดย authenticatedRouter เป็น TabCoordinatable ดังนั้นฟังก์ชั่นจะ:
focusFirst : ส่งคืนแท็บแรกที่แสดงโดยเส้นทาง todos และทำให้เป็นแท็บที่ใช้งานอยู่เว้นแต่จะเป็นแท็บที่ใช้งานอยู่แล้วchild : จะกลับมาเป็นเด็ก Todos -Tab เป็น NavigationViewCoordinator และเด็กเป็น NavigationCoordinatablepopToRoot : จะปรากฏเด็กทุกคนที่อาจจะหรือไม่ได้อยู่route : จะเดินทางไปยังเส้นทาง Todo ด้วยรหัสที่ระบุ เนื่องจาก stinsen ใช้พจนานุกรมเพื่อเป็นตัวแทนของเส้นทางฟังก์ชั่นจึงไม่สามารถสร้างโซ่ที่ปลอดภัยและไม่ถูกต้อง ซึ่งหมายความว่า: หากคุณมีเส้นทางใน A ถึง B และใน B ถึง C แอปจะไม่รวบรวมหากคุณพยายามกำหนดเส้นทางจาก A ไป C โดยไม่ต้องกำหนดเส้นทางไป B ก่อน นอกจากนี้คุณไม่สามารถดำเนินการเช่น popToRoot() บน TabCoordinatable และอื่น ๆ
การใช้ค่าที่ส่งคืนคุณสามารถ deeplink ภายในแอพได้อย่างง่ายดาย:
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 มาพร้อมกับ Coordinatables สองสามอย่างสำหรับมุมมอง Swiftui มาตรฐาน หากคุณต้องการใช้สำหรับ Hamburger-Menu คุณต้องสร้างของคุณเอง ตรวจสอบรหัสแหล่งที่มาเพื่อรับแรงบันดาลใจ
Stinsen รองรับการติดตั้งสองวิธี Cocoapods และ SPM
เปิด Xcode และโครงการของคุณคลิก File / Swift Packages / Add package dependency... ในฟิลด์ข้อความ " ป้อน URL ที่เก็บแพ็คเกจ " เขียน https://github.com/rundfunk47/stinsen แล้วกด ถัดไป สองครั้ง
สร้าง Podfile ในไดเรกทอรีรากของแอปของคุณ เพิ่ม
# Podfile
use_frameworks!
target 'YOUR_TARGET_NAME' do
pod 'Stinsen'
end
DoubleColumnNavigationViewStyle เหตุผลนี้ก็คือมันไม่ทำงานตามที่คาดไว้เนื่องจากปัญหาเกี่ยวกับ isActive ใน Swiftui วิธีแก้ปัญหา: ใช้ UIViewRepresentable หรือสร้างการใช้งานของคุณเองที่ BYVA เรามุ่งมั่นที่จะสร้างแอปพลิเคชัน Swiftui 100% ดังนั้นจึงเป็นเรื่องธรรมดาที่เราต้องสร้างกรอบการทำงานของผู้ประสานงานที่ตอบสนองความต้องการนี้และความต้องการอื่น ๆ ที่เรามี เฟรมเวิร์กใช้ในการผลิตและจัดการ ~ 50 การไหลและหน้าจอ ~ 100 เฟรมเวิร์กได้รับการดูแลโดย @rundfunk47
Stins สั้นในภาษาสวีเดนสำหรับ "Station Master" และ Stinsen เป็นบทความที่ชัดเจน "The Station Master" คำพูดส่วนใหญ่ถูกใช้เพื่ออ้างถึงผู้ส่งรถไฟซึ่งรับผิดชอบในการกำหนดเส้นทางรถไฟ โลโก้ขึ้นอยู่กับรูปปั้นไม้ของ สทิน ที่ตั้งอยู่ใกล้กับสถานีรถไฟในLinköpingประเทศสวีเดน
การเปลี่ยนแปลงที่ยิ่งใหญ่ที่สุดใน Stinsen V2 คือมันเป็นประเภทที่ปลอดภัยกว่า Stinsen V1 ซึ่งช่วยให้การผูกมัดและการเชื่อมโยงลึกง่ายขึ้นเหนือสิ่งอื่นใด
AnyCoordinatable ถูกแทนที่ด้วยโปรโตคอล มันไม่ได้ทำหน้าที่เดียวกันกับที่เก่า AnyCoordinatable และไม่สอดคล้องกับการกำหนดเส้นทางที่ปลอดภัยกว่าของเวอร์ชัน 2 ดังนั้นลบออกจากโครงการของคุณroute(to: .a) เราใช้ route(to: .a).view()@Root S และสลับระหว่างพวกเขาโดยใช้ .root() เพื่อรับฟังก์ชันการทำงานเดียวกันStinsen ได้รับการปล่อยตัวภายใต้ใบอนุญาต MIT ดูใบอนุญาตสำหรับข้อมูลเพิ่มเติม