Hello all, I tried to add arc for UIBezierPath I could not able to get the exact curve,
here is my code here I have added the bezier path for the added curve from the center position.
@IBDesignable
class MyTabBar: UITabBar {
private var shapeLayer: CALayer?
private func addShape() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = createPath()
shapeLayer.strokeColor = UIColor.lightGray.cgColor
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.lineWidth = 1.0
//The below 4 lines are for shadow above the bar. you can skip them if you do not want a shadow
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
}
override func draw(_ rect: CGRect) {
self.addShape()
}
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
path.addCurve(to: CGPoint(x: centerWidth, y: height),
controlPoint1: CGPoint(x: (centerWidth - 30), y: 0), controlPoint2: CGPoint(x: centerWidth - 35, y: height))
path.addCurve(to: CGPoint(x: (centerWidth + height * 2), y: 0),
controlPoint1: CGPoint(x: centerWidth + 35, y: height), 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()
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
}
}
this is tab bar controller added plus button in center view center, and the when tap the plus button to add the curve based popup should show, I don't know how to add curve based popup.
class TabbarViewController: UITabBarController,UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.navigationController?.navigationBar.isHidden = true
setupMiddleButton()
}
// TabBarButton – Setup Middle Button
func setupMiddleButton() {
let middleBtn = UIButton(frame: CGRect(x: (self.view.bounds.width / 2)-25, y: -20, width: 50, height: 50))
middleBtn.setImage(UIImage(named: "PlusBtn"), for: .normal)
self.tabBar.addSubview(middleBtn)
middleBtn.addTarget(self, action: #selector(self.menuButtonAction), for: .touchUpInside)
self.view.layoutIfNeeded()
}
// Menu Button Touch Action
@objc func menuButtonAction(sender: UIButton) {
//show the popUp
}
}
Please share me the findings & share me your refreance Thanks
I created a general-purpose method that will generate polygons with a mixture of sharp and rounded corners of different radii. I used it to create a project with a look rather like what you are after. You can download it from Github:
https://github.com/DuncanMC/CustomTabBarController.git
Here's what it looks like:
Note that the area of the tab's view controller that extends into the tab bar controller does not get taps. If you try to tap there, it triggers the tab bar controller. You will have to do some more tinkering to get that part to work.
Ultimately you may have to create a custom UITabBar (or UITabBar-like component) and possibly a custom parent view controller that acts like a UITabBar, in order to get what you want.
The method that creates polygon paths is called buildPolygonPathFrom(points:defaultCornerRadius:)
It takes an array of PolygonPoint structs. Those are defined like this:
/// A struct describing a single vertex in a polygon. Used in building polygon paths with a mixture of rounded an sharp-edged vertexes.
public struct PolygonPoint {
let point: CGPoint
let isRounded: Bool
let customCornerRadius: CGFloat?
init(point: CGPoint, isRounded: Bool, customCornerRadius: CGFloat? = nil) {
self.point = point
self.isRounded = isRounded
self.customCornerRadius = customCornerRadius
}
init(previousPoint: PolygonPoint, isRounded: Bool) {
self.init(point: previousPoint.point, isRounded: isRounded, customCornerRadius: previousPoint.customCornerRadius)
}
}
The code to build the path for the custom tab bar looks like this:
func tabBarMaskPath() -> CGPath? {
let width = bounds.width
let height = bounds.height
guard width > 0 && height > 0 else { return nil }
let dentRadius: CGFloat = 35
let cornerRadius: CGFloat = 20
let topFlatPartWidth = (width - dentRadius * 2.0) / 2
let polygonPoints = [
PolygonPoint(point: CGPoint(x: 0, y: 0), // Point 0
isRounded: true,
customCornerRadius: cornerRadius),
PolygonPoint(point: CGPoint(x: 0, y: height), // Point 1
isRounded: false),
PolygonPoint(point: CGPoint(x: width, y: height), // Point 2
isRounded: false),
PolygonPoint(point: CGPoint(x: width, y: 0), // Point 3
isRounded: true,
customCornerRadius: cornerRadius),
PolygonPoint(point: CGPoint(x: topFlatPartWidth + dentRadius * 2, y: 0), // Point 4
isRounded: true,
customCornerRadius: cornerRadius),
PolygonPoint(point: CGPoint(x: topFlatPartWidth + dentRadius * 2, y: dentRadius + cornerRadius), // Point 5
isRounded: true,
customCornerRadius: dentRadius),
PolygonPoint(point: CGPoint(x: topFlatPartWidth , y: dentRadius + cornerRadius), // Point 6
isRounded: true,
customCornerRadius: dentRadius),
PolygonPoint(point: CGPoint(x: topFlatPartWidth , y: 0), // Point 7
isRounded: true,
customCornerRadius: cornerRadius),
]
return buildPolygonPathFrom(points: polygonPoints, defaultCornerRadius: 0)
}
I just tried it, and it is possible to subclass UITabBar. I created a subclass of UITabBar where I use a mask layer to cut a circular "notch" out of the top of the tab bar. The code is below. It looks like the screenshot below. It isn't quite what you're after, but it's a start:
(The background color for the "Page 1" view controller is set to light gray, and you can see that color showing through in the "notch" I cut out of the tab bar.)
//
// CustomTabBar.swift
// TabBarController
//
// Created by Duncan Champney on 3/31/21.
//
import UIKit
class CustomTabBar: UITabBar {
var maskLayer = CAShapeLayer()
override var frame: CGRect {
didSet {
configureMaskLayer()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
configureMaskLayer()
self.layer.mask = maskLayer
self.layer.borderWidth = 0
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configureMaskLayer()
self.layer.mask = maskLayer
}
func configureMaskLayer() {
let rect = layer.bounds
maskLayer.frame = rect
let circleBoxSize = rect.size.height * 1.25
maskLayer.fillRule = .evenOdd
let path = UIBezierPath(rect: rect)
let circleRect = CGRect(x: rect.size.width/2 - circleBoxSize / 2,
y: -circleBoxSize/2,
width: circleBoxSize,
height: circleBoxSize)
let circle = UIBezierPath.init(ovalIn: circleRect)
path.append(circle)
maskLayer.path = path.cgPath
maskLayer.fillColor = UIColor.white.cgColor // Any opaque color works and has no effect
}
}
To draw your popover view you'll need to create a filled path that shape. You'll have to construct a custom shape like that with a combination of lines and arcs. I suggest using a CGMutablePath and the method addArc(tangent1End:tangent2End:radius:transform:)
since that enables you to provide endpoints rather than angles.
Another part of the puzzle:
Here is a custom UIView subclass that masks itself in the shape you're after
//
// ShapeWithTabView.swift
// ShapeWithTab
//
// Created by Duncan Champney on 4/1/21.
//
import UIKit
class ShapeWithTabView: UIView {
var cornerRadius: CGFloat = 20
var tabRadius: CGFloat = 60
var tabExtent: CGFloat = 0
var shapeLayer = CAShapeLayer()
var maskLayer = CAShapeLayer()
func buildShapeLayerPath() -> CGPath {
let boxWidth = min(bounds.size.width - 40, 686)
let boxHeight = min(bounds.size.height - 40 - tabRadius * 2 - tabExtent, 832)
// These are the corners of the view's primary rectangle
let point1 = CGPoint(x: 0, y: boxHeight)
let point2 = CGPoint(x: 0, y: 0)
let point3 = CGPoint(x: boxWidth, y: 0)
let point4 = CGPoint(x: boxWidth, y: boxHeight)
// These are the corners of the "tab" that extends outside the view's normal bounds.
let tabPoint1 = CGPoint(x: boxWidth / 2 + tabRadius, y: boxHeight)
let tabPoint2 = CGPoint(x: boxWidth / 2 + tabRadius, y: boxHeight + tabExtent + tabRadius * 2 )
let tabPoint3 = CGPoint(x: boxWidth / 2 - tabRadius, y: boxHeight + tabExtent + tabRadius * 2)
let tabPoint4 = CGPoint(x: boxWidth / 2 - tabRadius , y: boxHeight)
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: boxHeight - cornerRadius))
path.addArc(tangent1End: point2,
tangent2End: point3,
radius: cornerRadius)
path.addArc(tangent1End: point3,
tangent2End: point4,
radius: cornerRadius)
path.addArc(tangent1End: point4,
tangent2End: point1,
radius: cornerRadius)
//
path.addArc(tangent1End: tabPoint1,
tangent2End: tabPoint2,
radius: tabRadius)
path.addArc(tangent1End: tabPoint2,
tangent2End: tabPoint3,
radius: tabRadius)
path.addArc(tangent1End: tabPoint3,
tangent2End: tabPoint4,
radius: tabRadius)
path.addArc(tangent1End: tabPoint4,
tangent2End: point1,
radius: tabRadius)
path.addArc(tangent1End: point1,
tangent2End: point2,
radius: cornerRadius)
return path
}
func doInitSetup() {
self.layer.addSublayer(shapeLayer)
self.layer.mask = maskLayer
backgroundColor = .lightGray
//Configure a shape layer to draw an outline
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.lineWidth = 2
//Configure a mask layer to mask the view to our custom shape
maskLayer.fillColor = UIColor.white.cgColor
maskLayer.strokeColor = UIColor.white.cgColor
maskLayer.lineWidth = 2
}
override init(frame: CGRect) {
super.init(frame: frame)
self.doInitSetup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.doInitSetup()
}
public func updateShapeLayerPath() {
let path = buildShapeLayerPath()
shapeLayer.path = path
maskLayer.path = path
}
override var frame: CGRect {
didSet {
print("New frame = \(frame)")
shapeLayer.frame = layer.bounds
}
}
}
Combined with the modified tab bar from above, it looks like the image below. The final task is to get the custom view sized and positioned correctly, and have it land on top of the tab bar.