تطبيق بسيط وقوي وأنيق لنمط المنسق في سويفتوي. تتم كتابة Stinsen باستخدام Swiftui بنسبة 100 ٪ مما يجعلها تعمل بسلاسة عبر أجهزة iOS و TVOs و WatchOS و MacOS.
نعلم جميعًا أن التوجيه في Uikit قد يكون من الصعب القيام به بأناقة عند العمل مع تطبيقات حجم أكبر أو عند محاولة تطبيق نمط معماري مثل MVVM. لسوء الحظ ، تعاني Swiftui من الصندوق من العديد من المشكلات التي لا تُعقد فيها Uikit: مفاهيم مثل NavigationLink تعيش في طبقة العرض ، لا يزال لدينا مفهوم واضح للتدفقات والطرق ، وهكذا. تم إنشاء Stinsen لتخفيف هذه الآلام ، وهو تطبيق لنمط المنسق . كونه مكتوبًا في Swiftui ، فهو عبارة عن منصة تمامًا ويستخدم الأدوات الأصلية مثل @EnvironmentObject . الهدف من ذلك هو جعل Stinsen يشعر وكأنه أداة مفقودة في Swiftui ، تتوافق مع أسلوب الترميز والمبادئ العامة.
عادة في Swiftui ، يجب أن تتعامل العرض مع إضافة طرق عرض أخرى إلى مكدس التنقل باستخدام NavigationLink . ما لدينا هنا هو اقتران ضيق بين وجهات النظر ، لأن العرض يجب أن يعرف مسبقًا جميع المشاهدات الأخرى التي يمكن أن تتنقل بينها. أيضا ، فإن الرأي ينتهك مبدأ المسؤولية الواحدة (SRP). باستخدام نمط المنسق ، الذي قدم إلى مجتمع iOS بواسطة Soroush Khanlou في مؤتمر NSSPAIN في عام 2015 ، يمكننا تفويض هذه المسؤولية إلى الطبقة العليا: المنسق.
مثال باستخدام مكدس التنقل:
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 Out Of the Box على نوعين مختلفين من البروتوكولات Coordinatable التي يمكن للمنسقين تنفيذها:
NavigationCoordinatable - للتدفقات الملاحية. تأكد من لفها في NavigationViewCoordinator إذا كنت ترغب في الضغط على مكدس التنقل.TabCoordinatable - ل tabviews. بالإضافة إلى ذلك ، لدى Stinsen أيضًا منسقين يمكنك استخدامهما ، ViewWrapperCoordinator و NavigationViewCoordinator . ViewWrapperCoordinator هو منسق يمكنك إما فئة فرعية أو استخدامها على الفور لالتفاف المنسق الخاص بك في عرض ، وملاحة NavigationViewCoordinator هي فئة فرعية ViewWrapperCoordinator التي تلتف منسقك في NavigationView .
يمكن إنشاء عرض المنسق باستخدام .view() ، لذلك من أجل إظهار منسق للمستخدم الذي ستفعله فقط مثل:
struct StinsenApp : App {
var body : some Scene {
WindowGroup {
MainCoordinator ( ) . view ( )
}
}
} يمكن استخدام Stinsen لتشغيل تطبيقك بالكامل ، أو مجرد أجزاء من تطبيقك. لا يزال بإمكانك استخدام Swiftui NavigationLink المعتاد والأوراق المشروطة الحالية داخل وجهات النظر التي تديرها 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 - يحذف المنسق بأكمله ويترتب على ذلك الأطفال من الشجرة.
استنساخ الريبو وقم بتشغيل 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 في أمثلة/MVVM .
في بعض الأحيان تريد تخصيص العرض الذي تم إنشاؤه بواسطة المنسق. NAVIGINESCOORDINATABLE و TABCOORDINATABLE لديها وظيفة 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 التي ستغير علامة التبويب وتحديد todo محددة من أي مكان في التطبيق بعد تسجيل الدخول:
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 والطفل هو NavigationCoordinatable .popToRoot : سوف يخرج أي أطفال قد يكونون أو لا يكونون موجودين.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 مع اثنين من المنسقات لوجهات نظر Swiftui القياسية. إذا كنت ترغب في استخدامه على سبيل المثال ، فأنت بحاجة إلى إنشاء خاص بك. تحقق من رمز المصدر للحصول على بعض الإلهام.
يدعم Stinsen طريقتين للتركيب ، Cocoapods و SPM.
افتح Xcode ومشروعك ، انقر فوق File / Swift Packages / Add package dependency... في Textfield " أدخل عنوان 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. انظر الترخيص لمزيد من المعلومات.