如何从头开始创建可滑动的UITabBar

本文概述

  • 搜索简单的UITabBar修复程序
  • 精心设计的UITabBar解决方案
  • 以编程方式使用可滑动选项卡栏
  • 简单架构
  • 本文总结
如你所知, Apple的iOS SDK包含无数内置UI组件。你可以为按钮, 容器, 导航, 选项卡式布局命名-几乎所有需要的东西都在那里。还是?
【如何从头开始创建可滑动的UITabBar】所有这些基本组件都允许我们创建基本的结构化UI, 但是如果需要跳出框框, 该怎么办?当iOS开发人员需要构建某种默认情况下SDK不支持的行为时?
其中一种情况是UITabBar, 你不能在选项卡之间滑动, 也没有在选项卡之间切换的动画。
搜索简单的UITabBar修复程序 经过大量搜索后, 我设法在Github上仅找到一个有用的库。不幸的是, 该库在运行应用程序时产生了很多问题, 尽管乍看之下它似乎是一个优雅的解决方案。
换句话说, 我发现该库非常易于使用, 但存在bug, 显然超过了其易用性并容易引起问题。如果你仍然感兴趣, 可以在此链接下找到lib。
因此, 经过一番思考和大量搜索之后, 我开始实施自己的解决方案, 对自己说:” 嘿, 如果我们使用页面视图控制器进行滑动操作和本机UITabBar, 该怎么办。如果我们将这两件事放在一起, 处理页面索引滑动或点击标签栏怎么办?”
最终, 我提出了一个解决方案, 尽管事实证明它有些棘手, 我将在后面解释。
精心设计的UITabBar解决方案 假设你要构建三个选项卡项, 这自动意味着每个选项卡项将显示三个页面/控制器。
在这种情况下, 你将需要实例化这三个视图控制器, 并且还需要为选项卡栏使用两个占位符/空视图控制器, 以制作选项卡栏项, 在按下选项卡时或在用户想要更改时更改其状态。标签索引以编程方式。
为此, 让我们深入Xcode并编写几个类, 以了解这些工作原理。
在选项卡之间滑动的示例
如何从头开始创建可滑动的UITabBar

文章图片
在这些屏幕截图中, 你可以看到第一个选项卡栏项目为蓝色, 然后用户滑动到右侧的选项卡(黄色), 最后一个屏幕显示第三个选项被选中, 因此整个页面显示为黄色。
以编程方式使用可滑动选项卡栏 因此, 让我们深入研究此功能并编写一个适用于iOS的可滑动标签栏的简单示例。首先, 我们需要创建一个新项目。
我们项目所需的先决条件是非常基本的:Mac上安装了Xcode和Xcode构建工具。
要创建新项目, 请在Mac上打开Xcode应用程序, 然后选择” 创建新的Xcode项目” , 然后为你的项目命名, 最后选择要创建的应用程序的类型。只需选择” Single View App” , 然后按下一步。
如何从头开始创建可滑动的UITabBar

文章图片
如你所见, 下一个屏幕将要求你提供一些基本信息:
  • 产品名称:我将其命名为SwipeableTabbar。
  • 团队:如果你想在真实设备上运行此应用程序, 则必须拥有一个开发人员帐户。就我而言, 我将使用自己的帐户。
注意:如果你没有开发者帐户, 则也可以在Simulator上运行它。
  • 组织名称:我将其命名为srcmini。
  • 组织标识符:我将其命名为com.srcmini。
  • 语言:选择Swift。
  • 取消选中:” 使用核心数据” , “ 包括单元测试” 和” 包括UI测试” 。
按下[下一步]按钮, 即可开始建立可滑动式标签列。
简单架构 到现在为止你已经知道, 在创建新应用程序时, 你已经拥有Main ViewController类和Main.Storyboard。
在开始设计之前, 让我们首先创建所有必要的类和文件, 以确保我们已完成所有设置并正在运行, 然后再进行作业的UI部分。
在项目中的某个位置, 只需创建几个新文件-> TabbarController.swift, NavigationController.swift, PageViewController.swift。
就我而言, 它看起来像这样。
如何从头开始创建可滑动的UITabBar

文章图片
在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 UIKitclass NavigationController: UINavigationController { override func viewDidLoad() { super.viewDidLoad() navigationBar.isTranslucent = true navigationBar.tintColor = .gray } }

这样, 我们刚刚创建了一个简单的UINavigationController, 其中有一个带有灰色TintColor的半透明栏。就在这里。
现在, 我们可以继续使用PageViewController。
在其中, 我们需要比我们之前讨论的文件中的代码多一点。
该文件包含一个类, 一个协议, 一些UIPageViewController数据源和委托方法。
生成的文件需要如下所示:
如何从头开始创建可滑动的UITabBar

文章图片
如你所见, 我们已经声明了自己的协议PageViewControllerDelegate, 该协议应该告诉标签栏控制器在处理了滑动之后页面索引已更改。
import Foundation import UIKitprotocol PageViewControllerDelegate: class { func pageDidSwipe(to index: Int) }

然后, 我们需要创建一个名为PageViewController的新类, 该类将容纳我们的视图控制器, 选择特定索引处的页面, 并处理滑动。
假设我们第一次运行时选择的第一个控制器应该是中心视图控制器。在这种情况下, 我们分配默认索引值等于1。
class PageViewController: UIPageViewController {weak var swipeDelegate: PageViewControllerDelegate?var pages = [UIViewController]()var prevIndex: Int = 1override 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 }}

如你在这里看到的, 我们有可变页面, 其中包含所有视图控制器的引用。
变量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 UIKitclass TabbarController: UITabBarController {let selectedColor = UIColor.blue let deselectedColor = UIColor.graylet tabBarImages = [ UIImage(named: "ic_music")!, UIImage(named: "ic_play")!, UIImage(named: "ic_star")! ]override func viewDidLoad() {view.backgroundColor = .grayself.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 = .centeredsetUp()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方法内部, 你看到我们创建了两个占位符视图控制器。 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 = 2leftController.view.backgroundColor = .red centerController.view.backgroundColor = .blue rightController.view.backgroundColor = .yellowlet 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 = selfreturn pageViewController }private func tabbarItem(at index: Int) -> UITabBarItem { return UITabBarItem(title: nil, image: self.tabBarImages[index], selectedImage: nil) }

上面描述的第一种和第二种方法是我们的??网页浏览控制器的init方法。
方法选项卡项仅返回索引处的选项卡项。
如你所见, 在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 = selectedColorfor 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存储库。编码愉快!

    推荐阅读