本文概述
如你所知, Apple的iOS SDK包含无数内置UI组件。你可以为按钮, 容器, 导航, 选项卡式布局命名-几乎所有需要的东西都在那里。还是?
所有这些基本组件都允许我们创建基本的结构化UI, 但是如果需要跳出框框, 该怎么办?当iOS开发人员需要构建某种默认情况下SDK不支持的行为时?
其中一种情况是UITabBar, 你不能在选项卡之间滑动, 也没有在选项卡之间切换的动画。
搜索简单的UITabBar修复程序
经过大量搜索后, 我设法在Github上仅找到一个有用的库。不幸的是, 该库在运行应用程序时产生了很多问题, 尽管乍看之下它似乎是一个优雅的解决方案。
换句话说, 我发现该库非常易于使用, 但存在bug, 显然超过了其易用性并容易引起问题。如果你仍然感兴趣, 可以在此链接下找到lib。
因此, 经过一番思考和大量搜索之后, 我开始实施自己的解决方案, 对自己说:”嘿, 如果我们使用页面视图控制器进行滑动操作和本机UITabBar, 该怎么办。如果我们将这两件事放在一起, 处理页面索引滑动或点击标签栏怎么办?”
最终, 我提出了一个解决方案, 尽管事实证明它有些棘手, 我将在后面解释。
精心设计的UITabBar解决方案
假设你要构建三个选项卡项, 这自动意味着每个选项卡项将显示三个页面/控制器。
在这种情况下, 你将需要实例化这三个视图控制器, 并且还需要为选项卡栏使用两个占位符/空视图控制器, 以制作选项卡栏项, 在按下选项卡时或在用户想要更改时更改其状态。标签索引以编程方式。
为此, 让我们深入Xcode并编写几个类, 以了解这些工作原理。
在选项卡之间滑动的示例
在这些屏幕截图中, 你可以看到第一个选项卡栏项目为蓝色, 然后用户滑动到右侧的选项卡(黄色), 最后一个屏幕显示第三个选项被选中, 因此整个页面显示为黄色。
以编程方式使用可滑动选项卡栏
因此, 让我们深入研究此功能并编写一个适用于iOS的可滑动标签栏的简单示例。首先, 我们需要创建一个新项目。
我们项目所需的先决条件是非常基本的:Mac上安装了Xcode和Xcode构建工具。
要创建新项目, 请在Mac上打开Xcode应用程序, 然后选择”创建新的Xcode项目”, 然后为你的项目命名, 最后选择要创建的应用程序的类型。只需选择” Single View App”, 然后按下一步。
如你所见, 下一个屏幕将要求你提供一些基本信息:
- 产品名称:我将其命名为SwipeableTabbar。
- 团队:如果你想在真实设备上运行此应用程序, 则必须拥有一个开发人员帐户。就我而言, 我将使用自己的帐户。
注意:如果你没有开发者帐户, 则也可以在Simulator上运行它。
- 组织名称:我将其命名为srcmini。
- 组织标识符:我将其命名为com.srcmini。
- 语言:选择Swift。
- 取消选中:”使用核心数据”, “包括单元测试”和”包括UI测试”。
按下[下一步]按钮, 即可开始建立可滑动式标签列。
简单架构
到现在为止你已经知道, 在创建新应用程序时, 你已经拥有Main ViewController类和Main.Storyboard。
在开始设计之前, 让我们首先创建所有必要的类和文件, 以确保我们已完成所有设置并正在运行, 然后再进行作业的UI部分。
在项目中的某个位置, 只需创建几个新文件-> TabbarController.swift, NavigationController.swift, PageViewController.swift。
就我而言, 它看起来像这样。
在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数据源和委托方法。
生成的文件需要如下所示:
如你所见, 我们已经声明了自己的协议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
}
}
如你在这里看到的, 我们有可变页面, 其中包含所有视图控制器的引用。
变量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方法内部, 你看到我们创建了两个占位符视图控制器。 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方法。
方法选项卡项仅返回索引处的选项卡项。
如你所见, 在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存储库。编码愉快!
评论前必须登录!
注册