0

Using iOS14.2, Swift5.3.1, Xcode 12.2,

Following this link, I achieved a custom TabBar that has a new shape.

The shape is given by a CAShapeLayer and its corresponding BezierPath (see code example below).

However, I would like to blur this custom TabBar, keeping the BezierPath-shape as its mask and at the same time, be blurry-transparent.

See the attached video at the very bottom as illustration of what I mean.

Here is the code that does the custom TabBar shape (and this part works very well)

import UIKit

@IBDesignable
class CustomTabBar: UITabBar {

    private var shapeLayer: CALayer?
    var scanTabShown = true

    override func draw(_ rect: CGRect) {
        self.addShape()
    }

    private func addShape() {
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = self.createPath()
        shapeLayer.strokeColor = UIColor.lightGray.cgColor
        shapeLayer.fillColor = #colorLiteral(red: 0.9782002568, green: 0.9782230258, blue: 0.9782107472, alpha: 0.75)
        shapeLayer.lineWidth = 0.5
        shapeLayer.shadowOffset = CGSize(width:0, height:0)
        shapeLayer.shadowRadius = 10
        shapeLayer.shadowColor = UIColor.gray.cgColor
        shapeLayer.shadowOpacity = 0.3

        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 = 72.0
        let path = UIBezierPath()
        let centerWidth = self.frame.width / 2
        if scanTabShown {
            path.move(to: CGPoint(x: 0, y: 0))
            path.addLine(to: CGPoint(x: (centerWidth - 31 ), y: 0))
            path.addCurve(to: CGPoint(x: centerWidth, y: height - 40),
                          controlPoint1: CGPoint(x: (centerWidth - 30), y: 0), controlPoint2: CGPoint(x: centerWidth - 29, y: height - 40))
            path.addCurve(to: CGPoint(x: (centerWidth + 31 ), y: 0),
                          controlPoint1: CGPoint(x: centerWidth + 29, y: height - 40), controlPoint2: CGPoint(x: (centerWidth + 30), y: 0))
            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()
        } else {
            path.move(to: CGPoint(x: 0, y: 0))
            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
    }

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard !clipsToBounds && !isHidden && alpha > 0 else { return nil }
        for member in subviews.reversed() {
            let subPoint = member.convert(point, from: self)
            guard let result = member.hitTest(subPoint, with: event) else { continue }
            return result
        }
        return nil
    }
}

extension UITabBar {
    override open func sizeThatFits(_ size: CGSize) -> CGSize {
        var sizeThatFits = super.sizeThatFits(size)
        sizeThatFits.height = 83
        return sizeThatFits
    }
}

To achieve the masked BlurView, I followed this link.

Here is my code that supposedly should add the blur effect to this custom shaped TabBar.

However the code still does not work. What is wrong ?

Inside the addShape() method, I added the following code in order to get the desired effect. But again, it does not work. Why ???

        let maskLayer = CAShapeLayer()
        maskLayer.path = shapeLayer.path
        maskLayer.fillRule = .evenOdd
        
        let maskView = UIView(frame: self.frame)
        maskView.backgroundColor = UIColor.black
        maskView.layer.mask = maskLayer
        
        let blurEffect = UIBlurEffect(style: .regular)
        let blurEffectView = UIVisualEffectView(effect: blurEffect)
        blurEffectView.frame = self.frame
        blurEffectView.alpha = 1.0
        blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        blurEffectView.mask = maskView
        
        self.layer.addSublayer(blurEffectView.layer)

Here an illustration:

Please note, the NavBar at the top shows the desired semi-transparent and blur behaviour.

The TabBar at the bottom with the BezierPath is completely white. But I would like it to be semi-transparent and blurry as well... How can I achieve this ?

enter image description here

I already tried to play with an alpha-value for the shapeLayer.fillColor. The semi-transparency is working. But not the blur !!!

Another trial was to blur the custom-shaped tabBar as a subView in the tabBarController. But again, no blur !!

Here is the code of the trial:

class SessionTabBarController: UITabBarController, UITabBarControllerDelegate {

override func viewDidLoad() {
        super.viewDidLoad()
        
        if let tabBar = self.tabBar as? CustomTabBar {
            let maskLayer = CAShapeLayer()
            maskLayer.path = tabBar.createPath()
            maskLayer.fillRule = .evenOdd
    
            let maskView = UIView(frame: tabBar.frame)
            maskView.backgroundColor = UIColor.black
            maskView.layer.mask = maskLayer
    
            let blurEffect = UIBlurEffect(style: .regular)
            let blurEffectView = UIVisualEffectView(effect: blurEffect)
            blurEffectView.backgroundColor = .green
            blurEffectView.tintColor = .yellow
            blurEffectView.frame = tabBar.frame
            blurEffectView.alpha = 1.0
            blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            blurEffectView.mask = maskView
            tabBar.addSubview(blurEffectView)
            self.view.bringSubviewToFront(tabBar)
        }
    }
}
iKK
  • 6,394
  • 10
  • 58
  • 131
  • Can you provide an image of what you're trying to achieve, and what you have managed to achieve? – Sti Dec 03 '20 at 12:23
  • @Sti: I added a short video in the original question. Please not the semi-transparent AND blur of the top NavBar. However, the bottom-TabBar does not show the same effect. How can I achieve the Blur for the custom drawn TabBar ? – iKK Dec 03 '20 at 17:34
  • 1
    Hm.. maybe try adding a UIVisualEffectView as subview to your custom tab view, and applying the mask/shape to that view? – Sti Dec 03 '20 at 21:54
  • Unfortunately your suggestion to do it as a subView-addition to the custom tab view does not work either! See the additional code in the original post that explained what I tried. Is there any other way to blur a custom-shaped tabBar ? – iKK Dec 04 '20 at 08:44

1 Answers1

0

I finally found a solution:

@Sti's answer brought me into the right direction.

The Trick is to add the custom-shaped blurView to the custom-shaped tabBar separately.

I do it inside the viewWillLayoutSubviews method as follows:

override func viewWillLayoutSubviews() {
    
    if let tabBar = self.tabBar as? CustomTabBar {
        
        let maskLayer = CAShapeLayer()
        maskLayer.path = tabBar.createPath()
        maskLayer.fillRule = .evenOdd

        let blurEffect = UIBlurEffect(style: .light)
        let blurEffectView = UIVisualEffectView(effect: blurEffect)
        blurEffectView.frame = self.tabBar.frame
        blurEffectView.layer.mask = maskLayer
        
        self.view.addSubview(blurEffectView)
        self.view.bringSubviewToFront(tabBar)
    }
}

The custom-shaped CustomTabBar-extension remains the same:

import UIKit

@IBDesignable
class CustomTabBar: UITabBar {

    private var shapeLayer: CALayer?
    var scanTabShown = true

    override func draw(_ rect: CGRect) {
        self.addShape()
    }

    private func addShape() {
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = self.createPath()
        shapeLayer.strokeColor = UIColor.lightGray.cgColor
        shapeLayer.fillColor = #colorLiteral(red: 0.9782002568, green: 0.9782230258, blue: 0.9782107472, alpha: 0.75)
        shapeLayer.lineWidth = 0.5
        shapeLayer.shadowOffset = CGSize(width:0, height:0)
        shapeLayer.shadowRadius = 10
        shapeLayer.shadowColor = UIColor.gray.cgColor
        shapeLayer.shadowOpacity = 0.3

        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 = 72.0
        let path = UIBezierPath()
        let centerWidth = self.frame.width / 2
        if scanTabShown {
            path.move(to: CGPoint(x: 0, y: 0))
            path.addLine(to: CGPoint(x: (centerWidth - 31 ), y: 0))
            path.addCurve(to: CGPoint(x: centerWidth, y: height - 40),
                          controlPoint1: CGPoint(x: (centerWidth - 30), y: 0), controlPoint2: CGPoint(x: centerWidth - 29, y: height - 40))
            path.addCurve(to: CGPoint(x: (centerWidth + 31 ), y: 0),
                          controlPoint1: CGPoint(x: centerWidth + 29, y: height - 40), controlPoint2: CGPoint(x: (centerWidth + 30), y: 0))
            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()
        } else {
            path.move(to: CGPoint(x: 0, y: 0))
            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
    }

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard !clipsToBounds && !isHidden && alpha > 0 else { return nil }
        for member in subviews.reversed() {
            let subPoint = member.convert(point, from: self)
            guard let result = member.hitTest(subPoint, with: event) else { continue }
            return result
        }
        return nil
    }
}

extension UITabBar {
    override open func sizeThatFits(_ size: CGSize) -> CGSize {
        var sizeThatFits = super.sizeThatFits(size)
        sizeThatFits.height = 83
        return sizeThatFits
    }
}
iKK
  • 6,394
  • 10
  • 58
  • 131