如何创建自定义弯曲 iOS UITabBar?

How to create custom curved iOS UITabBar?

这是我的设计师为我们的项目制作的导航。 TabBar 的高度为 70.

到目前为止我已经尝试过什么。 我的尝试基于 Philipp Weiss 的教程。

https://betterprogramming.pub/draw-a-custom-ios-tabbar-shape-27d298a7f4fa

它基于创建自定义 IBDesignable UITabBar class 和覆盖绘制方法的想法。

@IBDesignable
class CustomizedTabBar: UITabBar {

    private var shapeLayer: CALayer?
    
    override func draw(_ rect: CGRect) {
        self.addShape()
    }

    private func addShape() {
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = createPath()
        shapeLayer.strokeColor = UIColor.blueMenu2.cgColor
        shapeLayer.fillColor = UIColor.blueMenu2.cgColor
        shapeLayer.lineWidth = 1.0

        if let oldShapeLayer = self.shapeLayer {
            self.layer.replaceSublayer(oldShapeLayer, with: shapeLayer)
        } else {
            self.layer.insertSublayer(shapeLayer, at: 0)
        }

        self.shapeLayer = shapeLayer
    }


    func createPath() -> CGPath {

        let height: CGFloat = 37.0
        let path = UIBezierPath()
        let centerWidth = self.frame.width / 2

        path.move(to: CGPoint(x: 0, y: 0)) // start top left
        path.addLine(to: CGPoint(x: (centerWidth - height * 2), y: 0)) // the beginning of the trough

        // first curve down
        path.addCurve(to: CGPoint(x: centerWidth, y: height),
                      controlPoint1: CGPoint(x: (centerWidth - 30), y: 0), controlPoint2: CGPoint(x: centerWidth - 35, y: height))
        // second curve up
        path.addCurve(to: CGPoint(x: (centerWidth + height * 2), y: 0),
                      controlPoint1: CGPoint(x: centerWidth + 35, y: height), controlPoint2: CGPoint(x: (centerWidth + 30), y: 0))

        // complete the rect
        path.addLine(to: CGPoint(x: self.frame.width, y: 0))
        path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height))
        path.addLine(to: CGPoint(x: 0, y: self.frame.height))
        path.close()

        return path.cgPath
    }

我试图编辑贝塞尔曲线路径以达到我的目标,但没有成功。 我不确定这种方法是否适用于这种特定的 TabBar 设计。

将导航高度设置为 70 没有问题。

@IBInspectable var height: CGFloat = 70

    override open func sizeThatFits(_ size: CGSize) -> CGSize {
            guard let window = UIApplication.shared.keyWindow else {
                return super.sizeThatFits(size)
            }
            var sizeThatFits = super.sizeThatFits(size)
            if #available(iOS 11.0, *) {
                sizeThatFits.height = height + window.safeAreaInsets.bottom
            } else {
                sizeThatFits.height = height
            }
            return sizeThatFits
        }

如何创建这个弯曲的 TabBar?

你知道如何使用贝塞尔曲线来制作相似的形状吗?

您的子类可能无法正常工作,因为 UITabBar 不会在 drawRect() 中绘制标签栏本身。但是从多个内部子视图中获取。

我建议使用 UITabBarController,但隐藏 UITabBar 本身。

self.tabBarController.tabBar.hidden = true

然后将您自己的自定义标签栏放置在屏幕按钮的视图中。 添加 additionalSafeAreaInsets 以使内容向上移出新视图,就像真正的标签栏一样。

然后只需在按下按钮时自己更改选项卡索引即可。

self.tabBarController.selectedIndex = 1

要为您想要的形状创建 UIBezierPath...

  • 移动到 1
  • 以c1为圆心顺时针加90°圆弧
  • 向 2 添加行
  • 以c2为圆心顺时针加90°圆弧
  • 添加 180° counter-clockwise 圆心 c3
  • 以c4为圆心顺时针加90°圆弧
  • 向 3 添加行
  • 以c5为圆心顺时针加90°圆弧
  • 将行添加到 4
  • 以c6为圆心顺时针加90°圆弧
  • 将行添加到 5
  • 以c7为圆心顺时针加90°圆弧
  • 关闭路径

这是一些示例代码 - 它是一个 UIView 子类,所有路径元素都在 layoutSubviews():

class TabBarShapeView: UIView {
    var shapeLayer: CAShapeLayer!
    override class var layerClass: AnyClass {
        return CAShapeLayer.self
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() {
        shapeLayer = self.layer as? CAShapeLayer
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.gray.cgColor
        shapeLayer.lineWidth = 1
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let middleRad: CGFloat = bounds.height - 10.0
        
        let cornerRad: CGFloat = 12.0
        
        let pth = UIBezierPath()
        
        let topLeftC: CGPoint = CGPoint(x: bounds.minX + cornerRad, y: bounds.minY + cornerRad)
        let topRightC: CGPoint = CGPoint(x: bounds.maxX - cornerRad, y: bounds.minY + cornerRad)
        let botRightC: CGPoint = CGPoint(x: bounds.maxX - cornerRad, y: bounds.maxY - cornerRad)
        let botLeftC: CGPoint = CGPoint(x: bounds.minX + cornerRad, y: bounds.maxY - cornerRad)

        var pt: CGPoint!

        // 1
        pt = CGPoint(x: bounds.minX, y: bounds.minY + cornerRad)
        pth.move(to: pt)
        
        // c1
        pth.addArc(withCenter: topLeftC, radius: cornerRad, startAngle: .pi * 1.0, endAngle: .pi * 1.5, clockwise: true)

        // 2
        pt = CGPoint(x: bounds.midX - middleRad, y: bounds.minY)
        pth.addLine(to: pt)

        // c2
        pt.y += middleRad * 0.5
        pth.addArc(withCenter: pt, radius: middleRad * 0.5, startAngle: -.pi * 0.5, endAngle: 0.0, clockwise: true)
        
        // c3
        pt.x += middleRad * 1.0
        pth.addArc(withCenter: pt, radius: middleRad * 0.5, startAngle: .pi * 1.0, endAngle: 0.0, clockwise: false)
        
        // c4
        pt.x += middleRad * 1.0
        pth.addArc(withCenter: pt, radius: middleRad * 0.5, startAngle: .pi * 1.0, endAngle: .pi * 1.5, clockwise: true)

        // 3
        pt = CGPoint(x: bounds.maxX - cornerRad, y: bounds.minY)
        pth.addLine(to: pt)

        // c5
        pth.addArc(withCenter: topRightC, radius: cornerRad, startAngle: -.pi * 0.5, endAngle: 0.0, clockwise: true)

        // 4
        pt = CGPoint(x: bounds.maxX, y: bounds.maxY - cornerRad)
        pth.addLine(to: pt)
        
        // c6
        pth.addArc(withCenter: botRightC, radius: cornerRad, startAngle: 0.0, endAngle: .pi * 0.5, clockwise: true)
        
        // 5
        pt = CGPoint(x: bounds.minX + cornerRad, y: bounds.maxY)
        pth.addLine(to: pt)
        
        // c7
        pth.addArc(withCenter: botLeftC, radius: cornerRad, startAngle: .pi * 0.5, endAngle: .pi * 1.0, clockwise: true)
        
        pth.close()
        
        shapeLayer.path = pth.cgPath
        
    }
}