个性化阅读
专注于IT技术分析

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

本文概述

如你所知, Apple的iOS SDK包含无数内置UI组件。你可以为按钮, 容器, 导航, 选项卡式布局命名-几乎所有需要的东西都在那里。还是?

所有这些基本组件都允许我们创建基本的结构化UI, 但是如果需要跳出框框, 该怎么办?当iOS开发人员需要构建某种默认情况下SDK不支持的行为时?

其中一种情况是UITabBar, 你不能在选项卡之间滑动, 也没有在选项卡之间切换的动画。

搜索简单的UITabBar修复程序

经过大量搜索后, 我设法在Github上仅找到一个有用的库。不幸的是, 该库在运行应用程序时产生了很多问题, 尽管乍看之下它似乎是一个优雅的解决方案。

换句话说, 我发现该库非常易于使用, 但存在bug, 显然超过了其易用性并容易引起问题。如果你仍然感兴趣, 可以在此链接下找到lib。

因此, 经过一番思考和大量搜索之后, 我开始实施自己的解决方案, 对自己说:”嘿, 如果我们使用页面视图控制器进行滑动操作和本机UITabBar, 该怎么办。如果我们将这两件事放在一起, 处理页面索引滑动或点击标签栏怎么办?”

最终, 我提出了一个解决方案, 尽管事实证明它有些棘手, 我将在后面解释。

精心设计的UITabBar解决方案

假设你要构建三个选项卡项, 这自动意味着每个选项卡项将显示三个页面/控制器。

在这种情况下, 你将需要实例化这三个视图控制器, 并且还需要为选项卡栏使用两个占位符/空视图控制器, 以制作选项卡栏项, 在按下选项卡时或在用户想要更改时更改其状态。标签索引以编程方式。

为此, 让我们深入Xcode并编写几个类, 以了解这些工作原理。

在选项卡之间滑动的示例

iOS中的可滑动标签页示例

在这些屏幕截图中, 你可以看到第一个选项卡栏项目为蓝色, 然后用户滑动到右侧的选项卡(黄色), 最后一个屏幕显示第三个选项被选中, 因此整个页面显示为黄色。

以编程方式使用可滑动选项卡栏

因此, 让我们深入研究此功能并编写一个适用于iOS的可滑动标签栏的简单示例。首先, 我们需要创建一个新项目。

我们项目所需的先决条件是非常基本的:Mac上安装了Xcode和Xcode构建工具。

要创建新项目, 请在Mac上打开Xcode应用程序, 然后选择”创建新的Xcode项目”, 然后为你的项目命名, 最后选择要创建的应用程序的类型。只需选择” Single View App”, 然后按下一步。

Xcode屏幕截图

如你所见, 下一个屏幕将要求你提供一些基本信息:

  • 产品名称:我将其命名为SwipeableTabbar。
  • 团队:如果你想在真实设备上运行此应用程序, 则必须拥有一个开发人员帐户。就我而言, 我将使用自己的帐户。

注意:如果你没有开发者帐户, 则也可以在Simulator上运行它。

  • 组织名称:我将其命名为srcmini。
  • 组织标识符:我将其命名为com.srcmini。
  • 语言:选择Swift。
  • 取消选中:”使用核心数据”, “包括单元测试”和”包括UI测试”。

按下[下一步]按钮, 即可开始建立可滑动式标签列。

简单架构

到现在为止你已经知道, 在创建新应用程序时, 你已经拥有Main ViewController类和Main.Storyboard。

在开始设计之前, 让我们首先创建所有必要的类和文件, 以确保我们已完成所有设置并正在运行, 然后再进行作业的UI部分。

在项目中的某个位置, 只需创建几个新文件-> 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
    }

}

如你在这里看到的, 我们有可变页面, 其中包含所有视图控制器的引用。

变量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存储库。编码愉快!

赞(0)
未经允许不得转载:srcmini » 如何从头开始创建可滑动的UITabBar

评论 抢沙发

评论前必须登录!