带有活动圆圈的自定义 TabBarController

Custom TabBarController with active circle

在阅读了一些关于自定义 UITabBarControllers 的文章后,我什至在开始研究之前更加困惑。

我的目标是创建一个具有 3 个重要属性的自定义 TabBar:

这是我想要实现的目标:

我已经能够通过遵循另一个 Whosebug 答案 (Remove tab bar item text, show only image) 来删除文本并使图标居中,尽管这个解决方案对我来说似乎是一个 hack。

如何在项目后面创建一个圆圈并更改活动项目的颜色?

另外,有人介意解释 XCode 检查器部分 "Tab Bar Item" 和 "Bar Item" 之间的区别吗?

最简单、实际上最干净的方法是设计图标并将它们作为图像导入到 .xcassets 文件夹中。然后你可以为每个 viewControllers 的不同状态设置不同的图标:

ViewController.tabBarItem = UITabBarItem(title: "", image: yourImage.withRenderingMode(.alwaysOriginal), selectedImage: yourImage)

您选择的图片将是带圆圈的图片,而没有圆圈的图片。它比在 xcode 中操作图像要容易得多,而且成本也更低,因为编译器只需要渲染图像而不必操作它们。

关于另一个问题 UIBarItem 是

An abstract superclass for items that can be added to a bar that appears at the bottom of the screen.

UITabBarItem 是 UIBarItem 的子类,可提供额外的功能。

第一步很简单:将 UITabbarItem 的标题 属性 留空应该隐藏标签。

你的第二步其实可以分为两步:改变图标的​​颜色,在后面加一个圆圈。

这里的第一步又很简单:您可以为当前选择的图标设置不同的图标ViewController(我使用 Storyboards,这个过程非常简单)。您要做的是添加一个白色版本的图标,以便在选择该菜单选项时显示。

最后一步是显示圆圈。为此,我们需要以下信息:

  • 当前选择了哪个项目?
  • 图标在屏幕上的位置是什么?

这两个中的第一个很容易找出,但第二个提出了一个问题:UITabBar 中的图标在屏幕周围的间距不均,所以我们不能只划分标签栏的宽度按其中的项目数量,然后取一半以找到图标的中心。相反,我们将 subclass UITabBarController.

Note: the tabBar property of a UITabBarController does have a .selectionIndicatorImage property. You can assign an image to this and it will be shown behind your icon. However, you can't easily control the placement of this image, and that is why we still resort to subclassing UITabBarController.

class CircledTabBarController: UITabBarController {

    var circle: UIView?

    override func viewDidLoad() {
        super.viewDidLoad()
        let numberOfItems = CGFloat(tabBar.items!.count)
        let tabBarItemSize = CGSize(width: (tabBar.frame.width / numberOfItems) - 20, height: tabBar.frame.height)
        circle = UIView(frame: CGRect(x: 0, y: 0, width: tabBarItemSize.height, height: tabBarItemSize.height))
        circle?.backgroundColor = .darkGray
        circle?.layer.cornerRadius = circle!.frame.width/2
        circle?.alpha = 0
        tabBar.addSubview(circle!)
        tabBar.sendSubview(toBack: circle!)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        let index = -(tabBar.items?.index(of: tabBar.selectedItem!)?.distance(to: 0))!
        let frame = frameForTabAtIndex(index: index)
        circle?.center.x = frame.origin.x + frame.width/2
        circle?.alpha = 1
    }

    override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
        let index = -(tabBar.items?.index(of: item)?.distance(to: 0))!
        let frame = frameForTabAtIndex(index: index)
        self.circle?.center.x = frame.origin.x + frame.width/2
    }

    func frameForTabAtIndex(index: Int) -> CGRect {
        var frames = tabBar.subviews.compactMap { (view:UIView) -> CGRect? in
            if let view = view as? UIControl {
                for item in view.subviews {
                    if let image = item as? UIImageView {
                        return image.superview!.convert(image.frame, to: tabBar)
                    }
                }
                return view.frame
            }
            return nil
        }
        frames.sort { [=10=].origin.x < .origin.x }
        if frames.count > index {
            return frames[index]
        }
        return frames.last ?? CGRect.zero
    }

}

现在使用 UITabBarController 的子class 代替基础 class。

那么为什么这种方法不是简单地将图标更改为带圆圈的图标呢?因为你可以用它做很多不同的事情。 I wrote an article about animating the UITabBarController in a similar manner,如果你愿意,你也可以轻松地使用上面的实现来为你的添加动画。