วิธีสร้าง UITabBar แบบกวาดนิ้วได้ตั้งแต่เริ่มต้น

เผยแพร่แล้ว: 2022-03-11

อย่างที่คุณทราบ iOS SDK ของ Apple มีส่วนประกอบ UI ในตัวมากมาย ปุ่ม คอนเทนเนอร์ การนำทาง เลย์เอาต์แบบแท็บ คุณตั้งชื่อมัน—เกือบทุกอย่างที่คุณต้องการอยู่ที่นั่น หรือว่า?

ส่วนประกอบพื้นฐานทั้งหมดเหล่านี้ช่วยให้เราสร้าง UI ที่มีโครงสร้างพื้นฐานได้ แต่จะเกิดอะไรขึ้นหากจำเป็นต้องดำเนินการนอกกรอบ เมื่อนักพัฒนา iOS ต้องการสร้างพฤติกรรมบางอย่างที่ SDK ไม่รองรับโดยค่าเริ่มต้น

กรณีหนึ่งเหล่านี้คือ UITabBar ซึ่งคุณไม่มีความสามารถในการปัดระหว่างแท็บ และคุณยังไม่มีภาพเคลื่อนไหวสำหรับการสลับระหว่างแท็บ

ค้นหา UITabBar Fix ง่าย ๆ

หลังจากค้นหามาพอสมควร ฉันก็พบว่ามีห้องสมุดที่มีประโยชน์เพียงแห่งเดียวบน Github น่าเสียดายที่ไลบรารี่สร้างปัญหามากมายในขณะที่เรียกใช้แอปพลิเคชัน แม้ว่าจะดูเหมือนเป็นวิธีแก้ปัญหาที่ยอดเยี่ยมในแวบแรก

กล่าวอีกนัยหนึ่ง ฉันพบว่าห้องสมุดใช้งานง่ายมาก แต่มีข้อผิดพลาด ซึ่งเห็นได้ชัดว่ามีมากกว่าความสะดวกในการใช้งานและมีแนวโน้มที่จะก่อให้เกิดปัญหา ในกรณีที่คุณยังสนใจอยู่ สามารถดู lib ได้ที่ลิงค์นี้

หลังจากครุ่นคิดและค้นหาอย่างถี่ถ้วนแล้ว ฉันก็เริ่มใช้โซลูชันของตัวเองและพูดกับตัวเองว่า: "จะเกิดอะไรขึ้นถ้าเราใช้ตัวควบคุมการดูหน้าเว็บสำหรับการปัด และ UITabBar แบบเนทีฟ จะเกิดอะไรขึ้นหากเราจัดกลุ่มสองสิ่งนี้เข้าด้วยกัน จัดการดัชนีหน้าในขณะที่ปัดหรือแตะบนแถบแท็บ”

ในท้ายที่สุด ฉันก็คิดวิธีแก้ปัญหาได้ แม้ว่าจะพิสูจน์แล้วว่าค่อนข้างยุ่งยาก ตามที่ฉันจะอธิบายในภายหลัง

โซลูชัน UITabBar ที่ประณีต

ลองนึกภาพว่าคุณมีรายการแถบแท็บสามรายการที่จะสร้าง ซึ่งหมายความว่าคุณมีสามหน้า/ตัวควบคุมที่จะแสดงต่อแต่ละรายการในแท็บโดยอัตโนมัติ

ในกรณีนี้ คุณจะต้องสร้างตัวอย่างตัวควบคุมมุมมองทั้งสาม และคุณจะต้องมีตัวยึดตำแหน่ง/ตัวควบคุมมุมมองว่างสองตัวสำหรับแถบแท็บ เพื่อสร้างรายการแถบแท็บ เปลี่ยนสถานะเมื่อกดแท็บ หรือเมื่อผู้ใช้ต้องการเปลี่ยน ดัชนีแท็บโดยทางโปรแกรม

สำหรับสิ่งนี้ เรามาเจาะลึกลงไปใน Xcode และเขียนคลาสสองสามคลาส เพื่อดูว่าสิ่งเหล่านี้ทำงานอย่างไร

ตัวอย่างการปัดระหว่างแท็บ

ตัวอย่างแท็บที่เลื่อนได้ใน iOS

ในภาพหน้าจอเหล่านี้ คุณจะเห็นรายการในแถบแท็บแรกเป็นสีน้ำเงิน จากนั้นผู้ใช้จะปัดไปที่แท็บด้านขวา ซึ่งเป็นสีเหลือง และหน้าจอสุดท้ายแสดงรายการที่สามถูกเลือก ดังนั้นทั้งหน้าจึงแสดงเป็นสีเหลือง

การใช้โปรแกรมของแถบแท็บแบบกวาดนิ้วได้

มาดูคุณสมบัตินี้และเขียนตัวอย่างง่ายๆ ของแถบแท็บแบบเลื่อนนิ้วได้สำหรับ iOS ก่อนอื่นเราต้องสร้างโครงการใหม่

ข้อกำหนดเบื้องต้นที่จำเป็นสำหรับโครงการของเรานั้นค่อนข้างพื้นฐาน: เครื่องมือสร้าง Xcode และ Xcode ที่ติดตั้งบน Mac ของคุณ

ในการสร้างโครงการใหม่ ให้เปิดแอปพลิเคชัน Xcode บน Mac ของคุณแล้วเลือก "สร้างโครงการ Xcode ใหม่" จากนั้นตั้งชื่อโครงการของคุณ และสุดท้ายเลือกประเภทของแอปพลิเคชันที่จะสร้าง เพียงเลือก “แอปมุมมองเดียว” แล้วกดถัดไป

ภาพหน้าจอ Xcode

ดังที่คุณเห็น ในหน้าจอถัดไป คุณจะต้องให้ข้อมูลพื้นฐานบางอย่าง:

  • ชื่อสินค้า: ฉันตั้งชื่อมันว่า SwipeableTabbar
  • ทีม: หากคุณต้องการเรียกใช้แอปพลิเคชันนี้บนอุปกรณ์จริง คุณจะต้องมีบัญชีนักพัฒนาซอฟต์แวร์ ในกรณีของฉัน ฉันจะใช้บัญชีของตัวเองเพื่อสิ่งนี้

หมายเหตุ: หากคุณไม่มีบัญชีนักพัฒนา คุณสามารถเรียกใช้บน Simulator ได้เช่นกัน

  • ชื่อองค์กร: ฉันตั้งชื่อมันว่า Toptal
  • ตัวระบุองค์กร: ฉันตั้งชื่อมันว่า com.toptal
  • ภาษา: เลือก Swift
  • ยกเลิกการเลือก: "ใช้ข้อมูลหลัก" "รวมการทดสอบหน่วย" และ "รวมการทดสอบ UI"

กดปุ่ม ถัดไป และคุณพร้อมที่จะเริ่มสร้างแถบแท็บแบบเลื่อนนิ้วได้

สถาปัตยกรรมที่เรียบง่าย

ตามที่คุณรู้อยู่แล้ว เมื่อคุณสร้างแอปใหม่ แสดงว่าคุณมีคลาส Main ViewController และ Main.Storyboard แล้ว

ก่อนที่เราจะเริ่มออกแบบ เรามาสร้างคลาสและไฟล์ที่จำเป็นทั้งหมดก่อนเพื่อให้แน่ใจว่าเรามีทุกอย่างที่ตั้งค่าและทำงาน ก่อนที่เราจะไปยังส่วน UI ของงาน

ภายในโครงการของคุณ เพียงสร้างไฟล์ใหม่สองสามไฟล์ -> TabbarController.swift , NavigationController.swift , PageViewController.swift

ในกรณีของฉันดูเหมือนว่านี้

ภาพหน้าจอ: Xcode Controllers

ในไฟล์ 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 ด้วยคุณสมบัติและสไตล์เริ่มต้น เราจำเป็นต้องกำหนดสองสี สำหรับรายการแถบที่เลือกและยกเลิกการเลือก นอกจากนี้ ฉันได้แนะนำสามภาพสำหรับรายการแถบแท็บ

ใน 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 คุณจะเห็นว่าเราได้สร้าง placeholder view controller สองตัว ตัวควบคุมตัวยึดตำแหน่งเหล่านี้จำเป็นสำหรับ 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) }

วิธีแรกและวิธีที่สองที่อธิบายข้างต้นเป็นวิธีเริ่มต้นของตัวควบคุมการ ดูหน้าเว็บ ของเรา

รายการ 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) } }

ในวิธีนี้ ฉันใช้ view controller เป็นพารามิเตอร์ จากตัวควบคุมมุมมองนี้ ฉันได้รับแท็กเป็นดัชนีที่เลือก สำหรับแถบแท็บ เราจำเป็นต้องตั้งค่าสีที่เลือกและไม่ได้เลือก

ตอนนี้เราต้องวนซ้ำคอนโทรลเลอร์ทั้งหมดของเราและตรวจสอบว่า 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 ของฉันได้ มีความสุขในการเข้ารหัส!