كيفية إنشاء UITabBar قابل للتمرير السريع من الألف إلى الياء

نشرت: 2022-03-11

كما تعلم ، تحتوي iOS SDK من Apple على عدد لا يحصى من مكونات واجهة المستخدم المضمنة. الأزرار والحاويات والتنقلات والتخطيطات المبوبة ، سمها ما شئت - كل ما تحتاجه تقريبًا موجود هناك. أو هو؟

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

إحدى هذه الحالات هي UITabBar ، حيث لا يمكنك التمرير بين علامات التبويب ، وليس لديك أيضًا رسوم متحركة للتبديل بين علامات التبويب.

البحث عن إصلاح سهل لـ UITabBar

بعد قدر كبير من البحث ، تمكنت من العثور على مكتبة مفيدة واحدة فقط على Github. لسوء الحظ ، خلقت المكتبة الكثير من المشكلات أثناء تشغيل التطبيق ، على الرغم من ظهورها كحل أنيق للوهلة الأولى.

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

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

في النهاية ، توصلت إلى حل ، رغم أنه كان صعبًا إلى حد ما ، كما سأشرح لاحقًا.

حل UITabBar مفصل

تخيل أن لديك ثلاثة عناصر شريط تبويب يتم بناؤها ، مما يعني تلقائيًا أن لديك ثلاث صفحات / وحدات تحكم ليتم عرضها لكل عنصر علامة تبويب.

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

لهذا ، دعنا نتعمق في Xcode ونكتب فصلين ، فقط لنرى كيف تعمل هذه الأشياء.

مثال على الضرب بين علامات التبويب

مثال على علامات التبويب القابلة للتمرير السريع في iOS

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

استخدام برمجي لشريط علامات التبويب القابل للتمرير السريع

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

المتطلبات الأساسية اللازمة لمشروعنا أساسية تمامًا: أدوات بناء Xcode و Xcode مثبتة على جهاز Mac الخاص بك.

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

لقطة شاشة Xcode

كما ترى ، ستطلب منك الشاشة التالية تقديم بعض المعلومات الأساسية:

  • اسم المنتج: سميته SwipeableTabbar.
  • الفريق: إذا كنت ترغب في تشغيل هذا التطبيق على جهاز حقيقي ، فسيتعين عليك امتلاك حساب مطور. في حالتي ، سأستخدم حسابي الخاص لهذا الغرض.

ملاحظة: إذا لم يكن لديك حساب مطور ، فيمكنك تشغيل هذا على Simulator أيضًا.

  • اسم المنظمة: سميتها Toptal .
  • معرّف المنظمة: سميته com.toptal .
  • اللغة: حدد Swift.
  • قم بإلغاء تحديد: "استخدام البيانات الأساسية" و "تضمين اختبارات الوحدة" و "تضمين اختبارات واجهة المستخدم".

اضغط على زر التالي ، وستكون جاهزًا لبدء إنشاء شريط علامات التبويب القابل للتمرير السريع.

هندسة معمارية بسيطة

كما تعلم الآن ، عند إنشاء تطبيق جديد ، يكون لديك بالفعل فئة Main ViewController و Main.Storyboard .

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

في مكان ما داخل مشروعك ، ما عليك سوى إنشاء بعض الملفات الجديدة -> TabbarController.swift ، NavigationController.swift ، PageViewController.swift .

في حالتي ، يبدو مثل هذا.

لقطة الشاشة: وحدات تحكم Xcode

في ملف AppDelegate ، اترك didFinishLaunchingWithOptions فقط ، حيث يمكنك إزالة جميع الطرق الأخرى.

من الداخل didFinishLaunchingWithOptions ، ببساطة انسخ والصق الأسطر أدناه:

 window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = NavigationController(rootViewController: TabbarController()) window?.makeKeyAndVisible() return true

قم بإزالة كل شيء من الملف المسمى ViewController.swift . سنعود إلى هذا الملف لاحقًا.

أولاً ، دعنا نكتب رمز NavigationController.swift .

 import Foundation import UIKit class NavigationController: UINavigationController { override func viewDidLoad() { super.viewDidLoad() navigationBar.isTranslucent = true navigationBar.tintColor = .gray } }

باستخدام هذا ، أنشأنا للتو UINavigationController بسيطًا ، حيث لدينا شريط شفاف مع لون رمادي TintColor. هذا كل شيء هنا.

الآن ، يمكننا المضي قدمًا في استخدام PageViewController .

في ذلك ، نحتاج إلى ترميز أكثر قليلاً من الملفات السابقة التي ناقشناها.

يحتوي هذا الملف على فئة واحدة وبروتوكول واحد وبعض مصادر بيانات UIPageViewController وطرق التفويض.

يجب أن يبدو الملف الناتج كما يلي:

لقطة شاشة Xcode: الطرق

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

 import Foundation import UIKit protocol PageViewControllerDelegate: class { func pageDidSwipe(to index: Int) }

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

دعنا نتخيل أن أول وحدة تحكم محددة في أول تشغيل لنا يجب أن تكون وحدة التحكم في العرض المركزي. في هذه الحالة ، نقوم بتعيين قيمة الفهرس الافتراضية الخاصة بنا ، والتي تساوي 1.

 class PageViewController: UIPageViewController { weak var swipeDelegate: PageViewControllerDelegate? var pages = [UIViewController]() var prevIndex: Int = 1 override func viewDidLoad() { super.viewDidLoad() self.dataSource = self self.delegate = self } func selectPage(at index: Int) { self.setViewControllers( [self.pages[index]], direction: self.direction(for: index), animated: true, completion: nil ) self.prevIndex = index } private func direction(for index: Int) -> UIPageViewController.NavigationDirection { return index > self.prevIndex ? .forward : .reverse } }

كما ترى هنا ، لدينا pages متغيرة تحتوي على مراجع لجميع أدوات التحكم في العرض الخاصة بنا.

المتغير prevIndex يستخدم لتخزين آخر فهرس محدد.

يمكنك ببساطة استدعاء طريقة selectPage لتعيين الفهرس المحدد.

إذا كنت ترغب في الاستماع إلى تغييرات فهرس الصفحات ، فيجب عليك الاشتراك في swipeDelegate ، وفي كل مرة يتم فيها تمرير الصفحة ، سيتم إعلامك بتغيير فهرس الصفحة ، بالإضافة إلى أنك ستتلقى أيضًا الفهرس الحالي.

سيعود اتجاه الطريقة إلى اتجاه التمرير السريع لـ UIPageViewController . الجزء الأخير من اللغز في هذه الفئة هو المفوض / تطبيقات مصدر البيانات.

لحسن الحظ ، هذه التطبيقات بسيطة للغاية.

 extension PageViewController: UIPageViewControllerDataSource { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let viewControllerIndex = pages.firstIndex(of: viewController) else { return nil } let previousIndex = viewControllerIndex - 1 guard previousIndex >= 0 else { return nil } guard pages.count > previousIndex else { return nil } return pages[previousIndex] } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let viewControllerIndex = pages.firstIndex(of: viewController) else { return nil } let nextIndex = viewControllerIndex + 1 guard nextIndex < pages.count else { return nil } guard pages.count > nextIndex else { return nil } return pages[nextIndex] } } extension PageViewController: UIPageViewControllerDelegate { func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { if completed { guard let currentPageIndex = self.viewControllers?.first?.view.tag else { return } self.prevIndex = currentPageIndex self.swipeDelegate?.pageDidSwipe(to: currentPageIndex) } } }

كما ترى أعلاه ، هناك ثلاث طرق في اللعب:

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

الآن يمكننا أخيرًا كتابة تنفيذ UITabBarController بنا:

 import UIKit class TabbarController: UITabBarController { let selectedColor = UIColor.blue let deselectedColor = UIColor.gray let tabBarImages = [ UIImage(named: "ic_music")!, UIImage(named: "ic_play")!, UIImage(named: "ic_star")! ] override func viewDidLoad() { view.backgroundColor = .gray self.delegate = self tabBar.isTranslucent = true tabBar.tintColor = deselectedColor tabBar.unselectedItemTintColor = deselectedColor tabBar.barTintColor = UIColor.white.withAlphaComponent(0.92) tabBar.itemSpacing = 10.0 tabBar.itemWidth = 76.0 tabBar.itemPositioning = .centered setUp() self.selectPage(at: 1) } }

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

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

 private func setUp() { guard let centerPageViewController = createCenterPageViewController() else { return } var controllers: [UIViewController] = [] controllers.append(createPlaceholderViewController(forIndex: 0)) controllers.append(centerPageViewController) controllers.append(createPlaceholderViewController(forIndex: 2)) setViewControllers(controllers, animated: false) selectedViewController = centerPageViewController } private func selectPage(at index: Int) { guard let viewController = self.viewControllers?[index] else { return } self.handleTabbarItemChange(viewController: viewController) guard let PageViewController = (self.viewControllers?[1] as? PageViewController) else { return } PageViewController.selectPage(at: index) }

داخل طريقة setUp ، كما ترى ، أنشأنا جهازي تحكم في عرض العنصر النائب. عناصر التحكم في العناصر النائبة هذه مطلوبة لـ UITabBar لأن عدد عناصر شريط علامات التبويب يجب أن يكون مساويًا لعدد عناصر التحكم في العرض لديك.

إذا كنت تستطيع التذكر ، فإننا نستخدم UIPageViewController لعرض وحدات التحكم ، ولكن بالنسبة لـ UITabBar ، إذا أردنا جعلها قابلة للتطبيق بشكل كامل ، فنحن بحاجة إلى إنشاء جميع وحدات التحكم في العرض ، بحيث تعمل عناصر الشريط هذه عند النقر عليها. لذلك ، في هذا المثال ، placeholderviewcontroller # 0 و # 2 عبارة عن متحكمات عرض فارغة.

بصفتنا وحدة تحكم عرض مركزية ، نقوم بإنشاء PageViewController بثلاث وحدات تحكم في العرض.

 private func createPlaceholderViewController(forIndex index: Int) -> UIViewController { let emptyViewController = UIViewController() emptyViewController.tabBarItem = tabbarItem(at: index) emptyViewController.view.tag = index return emptyViewController } private func createCenterPageViewController() -> UIPageViewController? { let leftController = ViewController() let centerController = ViewController2() let rightController = ViewController3() leftController.view.tag = 0 centerController.view.tag = 1 rightController.view.tag = 2 leftController.view.backgroundColor = .red centerController.view.backgroundColor = .blue rightController.view.backgroundColor = .yellow let storyBoard = UIStoryboard.init(name: "Main", bundle: nil) guard let pageViewController = storyBoard.instantiateViewController(withIdentifier: "PageViewController") as? PageViewController else { return nil } pageViewController.pages = [leftController, centerController, rightController] pageViewController.tabBarItem = tabbarItem(at: 1) pageViewController.view.tag = 1 pageViewController.swipeDelegate = self return pageViewController } private func tabbarItem(at index: Int) -> UITabBarItem { return UITabBarItem(title: nil, image: self.tabBarImages[index], selectedImage: nil) }

الطريقة الأولى والثانية الموضحة أعلاه هي طريقتان init لوحدة التحكم في مشاهدة الصفحة .

يقوم عنصر tabbar الأسلوب بإرجاع عنصر tabbar في الفهرس.

كما ترى ، داخل createCenterPageViewController() أستخدم العلامات لكل وحدة تحكم في العرض. هذا يساعدني في فهم وحدة التحكم التي ظهرت على الشاشة.

بعد ذلك ، نأتي إلى ما قد يكون أهم طريقة لدينا ، handleTabbarItemChange .

 private func handleTabbarItemChange(viewController: UIViewController) { guard let viewControllers = self.viewControllers else { return } let selectedIndex = viewController.view.tag self.tabBar.tintColor = selectedColor self.tabBar.unselectedItemTintColor = selectedColor for i in 0..<viewControllers.count { let tabbarItem = viewControllers[i].tabBarItem let tabbarImage = self.tabBarImages[i] tabbarItem?.selectedImage = tabbarImage.withRenderingMode(.alwaysTemplate) tabbarItem?.image = tabbarImage.withRenderingMode( i == selectedIndex ? .alwaysOriginal : .alwaysTemplate ) } if selectedIndex == 1 { viewControllers[selectedIndex].tabBarItem.selectedImage = self.tabBarImages[1].withRenderingMode(.alwaysOriginal) } }

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

نحتاج الآن إلى إجراء حلقة عبر جميع وحدات التحكم لدينا ومعرفة ما إذا كان i == selectedIndex

ثم نحتاج إلى عرض الصورة على أنها وضع عرض أصلي ، وإلا فإننا نحتاج إلى عرض الصورة كصيغة نموذج .

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

نحن على وشك الإنتهاء. نحتاج فقط إلى تقديم طريقتين مهمتين من UITabBarControllerDelegate و PageViewControllerDelegate .

 func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { self.selectPage(at: viewController.view.tag) return false } func pageDidSwipe(to index: Int) { guard let viewController = self.viewControllers?[index] else { return } self.handleTabbarItemChange(viewController: viewController) }

يتم استدعاء العنصر الأول عند الضغط على أي عنصر علامة تبويب ، بينما يتم استدعاء العنصر الثاني عند التمرير بين علامات التبويب.

تغليف

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

التنفيذ الذي تمت مناقشته هنا ليس شيئًا سيكون مثالياً لجميع السيناريوهات ، ولكنه حل ملتوي وسريع وسهل نسبيًا يمكّنك من إنشاء هذه الميزات بقليل من التعليمات البرمجية.

أخيرًا ، إذا كنت تريد تجربة أسلوبي ، فيمكنك استخدام GitHub repo. ترميز سعيد!