تكييف حل عملك الحالي مع SwiftUI. الجزء 4. التنقل والتكوين

يوم جيد للجميع! معك أنا ، أنا زاركوفا ، مطور جوّال رائد في Usetech.
والآن لنتحدث عن نقطة أخرى مثيرة للاهتمام في SwiftUI ، التنقل.

إذا فاتتك المقالات السابقة في السلسلة ، فيمكنك قراءتها هنا:

الجزء 1
الجزء 2
الجزء 3

مع التغيير في وصف الجزء المرئي والانتقال إلى البنية التعريفية ، فقد تغير عنصر التحكم في التنقل في تطبيق SwiftUI أيضًا. تم رفض استخدام UIViewContoller مباشرة ؛ لم يتم استخدام UINavigationController مباشرة. يتم استبداله بـ NavigationView.

@available(iOS 13.0, OSX 10.15, tvOS 13.0, *)
@available(watchOS, unavailable)
public struct NavigationView<Content> : View where Content : View {

    public init(@ViewBuilder content: () -> Content)

    //....
}

في الأساس غلاف فوق UINavigationController ووظائفه.


آلية الانتقال الرئيسية هي NavigationLink (تناظرية من المقطع) ، والتي يتم تعيينها على الفور في كود عرض الجسم.

public struct NavigationLink<Label, Destination> : View where Label : View, 
                                                                                           Destination : View {
.
    public init(destination: Destination, @ViewBuilder label: () -> Label)

    public init(destination: Destination, isActive: Binding<Bool>, 
                   @ViewBuilder label: () -> Label)

    public init<V>(destination: Destination, tag: V, selection: Binding<V?>,
                          @ViewBuilder label: () -> Label) where V : Hashable
//....
}

عند إنشاء NavigationLink ، فإنه يشير إلى العرض الذي يتم الانتقال إليه ، بالإضافة إلى العرض الذي يلفه NavigationLink ، أي عند التفاعل معه ، يتم تنشيط NavigationLink. مزيد من المعلومات حول الطرق الممكنة لتهيئة NavigationLink في وثائق Apple.

ومع ذلك ، يجب أن يؤخذ في الاعتبار أنه لا يوجد وصول مباشر إلى مكدس العرض بسبب التغليف ، وبرمجة التنقل للأمام فقط ، والعودة ممكنة بمستوى واحد فقط مرة أخرى ثم من خلال الرمز المغلف لزر "رجوع"



أيضًا في SwiftUI لا يوجد التنقل الديناميكي للبرنامج. إذا لم يكن الانتقال مرتبطًا بحدث تشغيل ، على سبيل المثال ، الضغط على زر ، ولكنه يتبع نتيجة لنوع من المنطق ، فلا يمكن فعل ذلك. يرتبط الانتقال إلى طريقة العرض التالية بالضرورة بآلية NavigationLink ، التي يتم تعيينها بشكل تعريفي على الفور عند وصف طريقة العرض التي تحتوي عليها. الكل.

إذا كان يجب أن تحتوي شاشتنا على انتقال إلى العديد من الشاشات المختلفة ، يصبح الرمز مرهقًا:

  NavigationView{
            NavigationLink(destination: ProfileView(), isActive: self.$isProfile) {
                Text("Profile")
            }
            NavigationLink(destination: Settings(), isActive: self.$isSettings) {
                           Text("Settings")
                       }
            NavigationLink(destination: Favorite(), isActive: self.$isFavorite) {
                Text("Favorite")
            }
            NavigationLink(destination: Login(), isActive: self.$isLogin) {
                Text("Login")
            }
            NavigationLink(destination: Search(), isActive: self.$isSearch) {
                Text("Search")
            }
}

يمكننا إدارة الروابط بعدة طرق:
- التحكم في نشاط NavigationLink عبر خاصيةBinding

 NavigationLink(destination: ProfileView(), isActive: self.$isProfile) {
                Text("Profile")
            }

- التحكم في إنشاء الارتباط من خلال شرط (متغيرات الحالة)

       if self.isProfile {
            NavigationLink(destination: ProfileView()) {
                Text("Profile")
            }
}

تضيف لنا الطريقة الأولى أعمال مراقبة حالة متغيرات التحكم.
إذا كنا نخطط للتنقل أكثر من مستوى واحد إلى الأمام ، فهذه مهمة صعبة للغاية.

في حالة شاشة قائمة العناصر المتشابهة ، يبدو كل شيء مضغوطًا:

 NavigationView{
        List(model.data) { item in
            NavigationLink(destination: NewsItemView(item:item)) {
            NewsItemRow(data: item)
            }
        }

أخطر مشكلة في NavigationLink ، في رأيي ، هي أن جميع روابط العرض المحددة ليست كسولة. يتم إنشاؤها ليس في لحظة تشغيل الرابط ، ولكن في وقت الإنشاء. إذا كان لدينا قائمة بالعديد من العناصر أو التحولات للعديد من المحتويات الثقيلة المختلفة ، فلن يؤثر ذلك على أداء تطبيقنا بأفضل طريقة. إذا كان لا يزال لدينا ViewModel مرتبطًا بهذا العرض مع منطق في تنفيذ طريقة عرض دورة الحياة التي لا يتم أخذها في الاعتبار أو عدم أخذها في الاعتبار بشكل صحيح ، فإن الوضع يصبح صعبًا للغاية.

على سبيل المثال ، لدينا قائمة أخبار تحتوي على عناصر من نفس النوع. لم ننتقل بعد إلى شاشة واحدة لعنصر إخباري واحد ، والنماذج معلقة بالفعل في ذاكرتنا:



ما الذي يمكننا فعله في هذه الحالة لجعل حياتنا أسهل؟

أولاً ، تذكر أن العرض غير موجود في فراغ ، ولكن يتم تقديمه في UIHostingController.

open class UIHostingController<Content> : UIViewController where Content : View {

    public init(rootView: Content)

    public var rootView: Content
//...
}

وهذا هو UIViewController. حتى نتمكن من القيام بما يلي. سننقل كل المسؤولية عن الانتقال إلى العرض التالي داخل UIHostingController الجديد إلى وحدة تحكم العرض الحالي. لنقم بإنشاء وحدات التنقل والتكوين التي سنتصل بها من طريقة العرض الخاصة بنا.

سيبدو المتصفح الذي يعمل مع UIViewController كما يلي:

class Navigator {
    private init(){}
    
    static let shared = Navigator()
    
    private weak var view: UIViewController?
    
    internal weak var nc: UINavigationController?
        
   func setup(view: UIViewController) {
        self.view = view
    }
     
  internal func open<Content:View>(screen: Content.Type, _ data: Any? = nil) {
     if let vc = ModuleConfig.shared.config(screen: screen)?
        .createScreen(data) {
        self.nc?.pushViewController(vc, animated: true)
        }
   }

وفقًا للمبدأ نفسه ، سننشئ مصنعًا للمكوّنات ، والذي سيعطينا تنفيذ مكوّن وحدة معيّنة:

protocol IConfugator: class {
    func createScreen(_ data: Any?)->UIViewController
}

class ModuleConfig{
    private init(){}
    static let shared = ModuleConfig()
    
    func config<Content:View>(screen: Content.Type)->IConfugator? {
        if screen == NewsListView.self {
            return NewsListConfigurator.shared
        }
      // -
        return nil
    }
}

المستكشف ، حسب نوع الشاشة ، يسأل عن مكون وحدة معينة ، ينقل إليها جميع المعلومات الضرورية.

class NewsListConfigurator: IConfugator {
    static let shared = NewsListConfigurator()
    
    func createScreen(_ data: Any?) -> UIViewController {
         var view = NewsListView()
        let presenter = NewsListPresenter()
         let interactor = NewsListInteractor()
        
        interactor.output = presenter
        presenter.output = view
        view.output = interactor
        
        let vc = UIHostingController<ContainerView<NewsListView>>
                    (rootView: ContainerView(content: view))
        return vc
    }
}

يعطي المكوّن UIViewController ، وهو المستكشف ويدفع UINavigationController إلى المكدس المشترك.



استبدل NavigationLink في الرمز باستدعاء Navigator. كمشغل ، سيكون لدينا حدث النقر فوق عنصر قائمة:

  List(model.data) { item in
            NewsItemRow(data: item)
                .onTapGesture {
                Navigator.shared.open(screen: NewsItemView.self, item)
            }
        }

لا شيء يمنعنا من استدعاء Navigator في أي طريقة عرض بنفس الطريقة. ليس فقط داخل الجسم.

بالإضافة إلى حقيقة أن الشفرة أصبحت أكثر نظافة بشكل ملحوظ ، قمنا أيضًا بإفراغ الذاكرة. بعد كل شيء ، باستخدام هذا النهج ، سيتم إنشاء طريقة العرض فقط عند استدعائها.



الآن أصبح تطبيق SwiftUI الخاص بنا أسهل للتوسيع والتعديل. الكود نظيف وجميل.
يمكنك العثور على رمز المثال هنا .

في المرة القادمة سنتحدث عن التطبيق الأعمق لـ Combine.

All Articles