Swift自定义UITabBar

来源:互联网 发布:并查集数据结构优化 编辑:程序博客网 时间:2024/06/06 00:57

前言

很多时候,系统原生的 UITabBar 并不能满足我们的需求,譬如我们想要给图标做动态的改变,或者比较炫一点的展示,原生的处理起来都很麻烦。所以很多时候都需要自定义一个 UITabBar,里面的图标、颜色、背景等等都可以根据需求去改变。

效果展示:

自定义UITabBar

从零开始

先说一下思路

页面继承自 UITabBarController ,然后自定义一个 UIView ,添加到 TabBar 上。取消原本的控制按钮。创建自定义按钮,即重写 UIButtonimageView 、和 titleLabelframe ,完成图片、文字的重新布局。最后实现不同按钮的协议方法。

效果图中,只有两边的两个页面在 UITabBarController 的管理下,中间三个都是通过自定义按钮实现的模态页面,即 present 过去的。多用于拍摄图片、录制视频、发表动态等功能。

Demo文件

代码实现:

  1. 首先不妨先建立三个基础文件,然后在丰富代码。其中, IWCustomButton 继承自 UIButtonIWCustomTabBarView 继承自 UIViewIWCustomTabBarController 继承自 UITabBarController

  2. 修改 AppDelegate 文件中 didFinishLaunchingWithOptions 方法,保证启动时没有异常:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {    //  创建Window    window = UIWindow(frame: UIScreen.main.bounds)    //  初始化一个tabbar    let customTabBar = IWCustomTabBarController()    //  设置根控制器    window?.rootViewController = customTabBar    window?.makeKeyAndVisible()    return true}
  3. 首先在 IWCustomTabBarController 文件中添加代码:

    //  IWCustomTabBarController.swiftimport UIKitclass IWCustomTabBarController: UITabBarController {//  MARK: - Properties//  图片fileprivate let tabBarImageNames = ["tb_home","tb_person"]fileprivate let tabBarTitles = ["首页","我的"]//  MARK: - LifeCycleoverride func viewDidLoad() {    super.viewDidLoad()    //  自定义 TabBar 外观    createCustomTabBar(addHeight: 0)    //  创建子控制器    addDefaultChildViewControllers()    //  设置每一个子页面的按钮展示    setChildViewControllerItem()}//  MARK: - Private Methods/// 添加默认的页面fileprivate func addDefaultChildViewControllers() {    let vc1 = UIViewController()    vc1.view.backgroundColor = UIColor.white    let vc2 = UIViewController()    vc2.view.backgroundColor = UIColor.lightGray    viewControllers = [vc1, vc2]}/// 设置外观////// - parameter addHeight: 增加高度,0 为默认fileprivate let customTabBarView = IWCustomTabBarView()fileprivate func createCustomTabBar(addHeight: CGFloat) {    //  改变tabbar 大小    var oriTabBarFrame = tabBar.frame    oriTabBarFrame.origin.y -= addHeight    oriTabBarFrame.size.height += addHeight    tabBar.frame = oriTabBarFrame    customTabBarView.frame = tabBar.bounds    customTabBarView.frame.origin.y -= addHeight    customTabBarView.backgroundColor = UIColor.groupTableViewBackground    customTabBarView.frame.size.height = tabBar.frame.size.height + addHeight    customTabBarView.isUserInteractionEnabled = true    tabBar.addSubview(customTabBarView)}/// 设置子页面的item项fileprivate func setChildViewControllerItem() {    guard let containViewControllers = viewControllers else {        print("⚠️  设置子页面 item 项失败  ⚠️")        return    }    if containViewControllers.count != tabBarImageNames.count {        fatalError("子页面数量和设置的tabBarItem数量不一致,请检查!!")    }    //  遍历子页面    for (index, singleVC) in containViewControllers.enumerated() {        singleVC.tabBarItem.image = UIImage(named: tabBarImageNames[index])        singleVC.tabBarItem.selectedImage = UIImage(named: tabBarImageNames[index] + "_selected")        singleVC.tabBarItem.title = tabBarTitles[index]    }}}

    上面就是一个基本的纯代码创建的 UITabBarController 的实际效果了,运行后,查看效果:

    基本的运行效果

    现在明显的问题就是我们的原始图片是红色的,为什么现在都是灰、蓝色,因为 UITabBar 使用图片时渲染了,如果我们需要使用原始图片,则对 UIImage 方法扩展:

    extension UIImage {var originalImage: UIImage {    return self.withRenderingMode(.alwaysOriginal)}}

    然后修改遍历子页面的代码:

    //  遍历子页面    for (index, singleVC) in containViewControllers.enumerated() {        singleVC.tabBarItem.image = UIImage(named: tabBarImageNames[index]).originalImage        singleVC.tabBarItem.selectedImage = UIImage(named: tabBarImageNames[index] + "_selected").originalImage        singleVC.tabBarItem.title = tabBarTitles[index]    }

    运行后便可查看到原始的图片效果。

  4. 编写文件 IWCustomTabBarView :

    import UIKit//  自定义按钮功能enum IWCustomButtonOperation {case customRecordingVideo       //  录像case customTakePhoto            //  拍照case customMakeTape             //  录音}/// 页面按钮点击协议protocol IWCustomTabBarViewDelegate {/// 点击tabBar 管理下的按钮////// - parameter customTabBarView:     当前视图/// - parameter didSelectedButtonTag: 点击tag,这个是区分标识func iwCustomTabBarView(customTabBarView: IWCustomTabBarView, _ didSelectedButtonTag: Int)/// 点击自定义的纯按钮////// - parameter customTabBarView:      当前视图/// - parameter didSelectedOpertaionButtonType: 按钮类型,拍照、摄像、录音func iwCustomTabBarView(customTabBarView: IWCustomTabBarView, _ didSelectedOpertaionButtonType: IWCustomButtonOperation)}class IWCustomTabBarView: UIView {//  MARK: - Properties//  协议var delegate: IWCustomTabBarViewDelegate?//  操作按钮数组fileprivate var operationButtons = [IWCustomButton]()//  tabbar 管理的按钮数组fileprivate var customButtons = [IWCustomButton]()//  自定义按钮图片、标题fileprivate let operationImageNames = ["tb_normol","tb_normol","tb_normol"]fileprivate let operationTitls = ["摄像", "拍照", "录音"]//  MARK: - Initoverride init(frame: CGRect) {    super.init(frame: frame)    //  添加自定义按钮    addOperationButtons()}required init?(coder aDecoder: NSCoder) {    super.init(coder: aDecoder)    print("IWCustomTabBarView 页面 init(coder:) 方法没有实现")}/// 布局控件override func layoutSubviews() {    super.layoutSubviews()    //  设置位置    let btnY: CGFloat = 0    let btnWidth = bounds.width / CGFloat(subviews.count)    let btnHeight = bounds.height    //  这里其实就两个    for (index, customButton) in customButtons.enumerated() {        switch index {        case 0:            customButton.frame = CGRect(x: 0, y: 0, width: btnWidth, height: btnHeight)            customButton.tag = index        case 1:            customButton.frame = CGRect(x: btnWidth * 4, y: 0, width: btnWidth, height: btnHeight)            customButton.tag = index        default:            break        }    }    //  这里有三个    for (index, operBtn) in operationButtons.enumerated() {        let btnX = (CGFloat(index) + 1) * btnWidth        operBtn.frame = CGRect(x: btnX, y: btnY, width: btnWidth, height: btnHeight)    }}//  MARK: - Public Methods/// 根据原始的 TabBarItem 设置自定义Button////// - parameter originalTabBarItem: 原始数据func addCustomTabBarButton(by originalTabBarItem: UITabBarItem) {    //  添加初始按钮    let customButton = IWCustomButton()    customButtons.append(customButton)    addSubview(customButton)    //  添加点击事件    customButton.addTarget(self, action: #selector(customButtonClickedAction(customBtn:)), for: .touchUpInside)    //  默认展示第一个页面    if customButtons.count == 1 {        customButtonClickedAction(customBtn: customButton)    }}//  MARK: - Private Methods/// 添加操作按钮fileprivate func addOperationButtons() {    for index in 0 ..< 3 {        let operationBtn = IWCustomButton()        operationButtons.append(operationBtn)        operationBtn.setImage(UIImage(named: operationImageNames[index]), for: .normal)        operationBtn.setImage(UIImage(named: operationImageNames[index]), for: .highlighted)        operationBtn.setTitle(operationTitls[index], for: .normal)        operationBtn.tag = 100 + index        operationBtn.addTarget(self, action: #selector(operationButtonClickedAction(operBtn:)), for: .touchUpInside)        addSubview(operationBtn)    }}///  操作按钮点击事件@objc fileprivate func operationButtonClickedAction(operBtn: IWCustomButton) {    switch operBtn.tag {    case 100:        delegate?.iwCustomTabBarView(customTabBarView: self, .customRecordingVideo)    case 101:        delegate?.iwCustomTabBarView(customTabBarView: self, .customTakePhoto)    case 102:        delegate?.iwCustomTabBarView(customTabBarView: self, .customMakeTape)    default:        break    }}//  保证按钮的状态正常显示fileprivate var lastCustomButton = IWCustomButton()///  tabbar 管理下按钮的点击事件@objc fileprivate func customButtonClickedAction(customBtn: IWCustomButton) {    delegate?.iwCustomTabBarView(customTabBarView: self, customBtn.tag)    lastCustomButton.isSelected = false    customBtn.isSelected = true    lastCustomButton = customBtn}}

    IWCustomTabBarController 文件的 setChildViewControllerItem() 方法中,修改遍历子页面的代码,获取当前的 UITabBarItem

    // 遍历子页面 for (index, singleVC) in containViewControllers.enumerated() { singleVC.tabBarItem.image = UIImage(named: tabBarImageNames[index]) singleVC.tabBarItem.selectedImage = UIImage(named: tabBarImageNames[index] + "_selected") singleVC.tabBarItem.title = tabBarTitles[index]//  添加相对应的自定义按钮        customTabBarView.addCustomTabBarButton(by: singleVC.tabBarItem)}

    运行后,看到效果好像乱乱的,暂时不用在意,在后面的代码中会慢慢整理出理想的效果。

    乱糟糟的

    简单分析上面的代码:这里我在中间加入了三个自定义的按钮。这样的话,最下面应该是有5个按钮的。当然也可以加入一个或者两个等,只需要修改上面对应的数值就可以了。这里面比较主要的就是自定义协议 IWCustomTabBarViewDelegate 和布局方法 layoutSubviews,布局方法里如果能理解两个 for 循环和对应数组中的数据来源、作用,那么问题就简单很多了。

    这里要说一个属性 lastCustomButton ,这个属性会让我们避免不必要的遍历按钮,有些时候多个按钮只能有一个被选中时,有种常见的方法就是遍历按钮数组,令其中一个 isSelected = true ,其他按钮的 isSelected = false ,而这个属性就能取代遍历。

    其实存在的问题也很明显,就是这么写的话很难去扩展,譬如如果上面的代码已经完成了,但是临时需要减少一个自定义按钮,那么就需要改动多个地方。这里只是提供一种自定义的思路,只是说还有很多可以优化的地方。

  5. 关于自定义的 UIButotn ,是个很有意思的地方。因为视觉上的改变都是在这里发生,先使用默认的设置:

    import UIKitclass IWCustomButton: UIButton {override init(frame: CGRect) {    super.init(frame: frame)    titleLabel?.textAlignment = .center    setTitleColor(UIColor.gray, for: .normal)    setTitleColor(UIColor.red, for: .selected)    titleLabel?.font = UIFont.italicSystemFont(ofSize: 12)}required init?(coder aDecoder: NSCoder) {    super.init(coder: aDecoder)    print("⚠️⚠️⚠️ init(coder:) 方法没有实现")}/// 根据传入的 UITabBarItem 设置数据显示////// - parameter tabBarItem: 数据来源func setTabBarItem(tabBarItem: UITabBarItem) {    setTitle(tabBarItem.title, for: .normal)    setImage(tabBarItem.image, for: .normal)    setImage(tabBarItem.selectedImage, for: .highlighted)    setImage(tabBarItem.selectedImage, for: .selected)}}

    修改 IWCustomTabBarView 文件的 addCustomTabBarButton(by: ) 方法:

    //  MARK: - Public Methods/// 根据原始的 TabBarItem 设置自定义Button////// - parameter originalTabBarItem: 原始数据func addCustomTabBarButton(by originalTabBarItem: UITabBarItem) {    //  添加初始按钮    let customButton = IWCustomButton()    customButtons.append(customButton)    addSubview(customButton)    //  传值    customButton.setTabBarItem(tabBarItem: originalTabBarItem)    //  添加点击事件    customButton.addTarget(self, action: #selector(customButtonClickedAction(customBtn:)), for: .touchUpInside)    //  默认展示第一个页面    if customButtons.count == 1 {        customButtonClickedAction(customBtn: customButton)    }}

    看看运行结果:

    自定义按钮后

    首先,我们发现了乱的原因,就是自定义的按钮和原本的 UITabBarItem 的显示起了冲突。那么先修改这个问题:在 IWCustomTabBarController 方法中页面即将出现时添加方法:

    override func viewWillAppear(_ animated: Bool) {    super.viewWillAppear(animated)    //  移除原生的 TabBarItem ,否则会出现覆盖现象    tabBar.subviews.forEach { (subView) in        if subView is UIControl {            subView.removeFromSuperview()        }    }}

    那么上面重复显示的原生项此时就移除了。下一个问题:发现自定义按钮图像的大小不一致。其实中间图片本身的大小就是比两边的大的。以 2x.png 为例,中间的图标是 70x70,而两边的是 48x48。如果在没有文字显示的情况下,在按钮的初始化方法中添加 imageView?.contentMode = .center ,图片居中展示,自定义按钮到这个地方就可以结束了(可以尝试不要 title ,查看运行效果)。甚至可以在自定义按钮的初始化方法里使用仿射变换来放大、缩小图片。

    这里为了控制图片、文字的位置,重写 UIButton 的两个方法:

    /// 重写 UIButton 的 UIImageView 位置////// - parameter contentRect: 始位置////// - returns: 修改后override func imageRect(forContentRect contentRect: CGRect) -> CGRect {    let imageWidth = contentRect.size.height * 4 / 9    let imageHeight = contentRect.size.height    return CGRect(x: bounds.width / 2 - imageWidth / 2, y: imageHeight / 9, width: imageWidth, height: imageWidth)}/// 重写 UIButton 的 TitleLabel 的位置////// - parameter contentRect: 原始位置////// - returns: 修改后override func titleRect(forContentRect contentRect: CGRect) -> CGRect {    let titleWidth = contentRect.size.width    let titleHeight = contentRect.size.height / 3    return CGRect(x: bounds.width / 2 - titleWidth / 2, y: bounds.height - titleHeight, width: titleWidth, height: titleHeight)}  

    对上面代码做简单地说明,首先说方法中 contentRect 这个变量,它的 size 是这个 UIButton 的大小,而不是单独的 UIImageView ,或者 titleLabel 的大小。上面的一些具体数值,譬如 4 / 9 等这种奇葩的比例数值,仅仅是我根据自己的审美观随便写入的一些数值,至于到具体的开发中,可以固定大小,也可以使用更加细致的比例,因为 tabBar 默认的高度是 49 ,那么很多数据就可以使用了。现在看看效果:

    修改自定义按钮后

  6. IWCustomTabBarController 文件中实现 IWCustomTabBarView 文件中的协议方法,首先添加协议,然后实现方法,别忘了令 customTabBarView.delegate = self

    //  MARK: - IWCustomTabBarViewDelegate///  点击 tabbar 管理下的按钮func iwCustomTabBarView(customTabBarView: IWCustomTabBarView, _ didSelectedButtonTag: Int) {    selectedIndex = didSelectedButtonTag}///  点击自定义添加的的按钮func iwCustomTabBarView(customTabBarView: IWCustomTabBarView, _ didSelectedOpertaionButtonType: IWCustomButtonOperation) {    switch didSelectedOpertaionButtonType {    case .customRecordingVideo:        print("摄像")        let vc = UIViewController()        vc.view.backgroundColor = UIColor.orange        addBackButton(on: vc.view)        present(vc, animated: true, completion: nil)    case .customTakePhoto:        print("拍照")        let vc = UIViewController()        vc.view.backgroundColor = UIColor.green        addBackButton(on: vc.view)        present(vc, animated: true, completion: nil)    case .customMakeTape:        print("录音")        let vc = UIViewController()        vc.view.backgroundColor = UIColor.cyan        addBackButton(on: vc.view)        present(vc, animated: true, completion: nil)    }}fileprivate func addBackButton(on superView: UIView) {    let btn = UIButton()    btn.frame = CGRect(x: 100, y: 100, width: 100, height: 50)    btn.backgroundColor = UIColor.blue    btn.setTitle("返回", for: .normal)    btn.setTitleColor(UIColor.white, for: .normal)    btn.addTarget(self, action: #selector(dismissAction), for: .touchUpInside)    superView.addSubview(btn)}@objc func dismissAction() {    dismiss(animated: true, completion: nil)}

    上面的代码,只单独说一点,就是协议方法 iwCustomTabBarView(customTabBarView : , _ didSelectedButtonTag) 中, selectedIndex 这个属性并非我们自己定义的变量,而是系统设置的,所以这时候 didSelectedButtonTag 所代表值就显得很有意思了,它正是我们在 UITabBar 管理下 ViewController 是下标值。看看这时候的效果吧:

    完成后

  7. 最后再说一点,有时候我们需要给自定义的 IWCustomTabBarView 添加背景图片,那么这时候会出现一个问题,就是原本的 TabBar 的浅灰色背景始终会有一条线,此时在 IWCustomTabBarController 文件的 viewDidLoad() 方法中添加下面的代码即可。

        //  去除 TabBar 阴影    let originalTabBar = UITabBar.appearance()    originalTabBar.shadowImage = UIImage()    originalTabBar.backgroundImage = UIImage()

完了

1 0
原创粉丝点击