I want to make curved bottom bar it is not looking perfect round by below code
struct customShape: Shape {
var xAxis : CGFloat
var animatableData: CGFloat{
get { return xAxis}
set { xAxis = newValue}
}
func path(in rect: CGRect) -> Path {
return Path { path in
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
path.addLine(to: CGPoint(x: 0, y: rect.height))
let center = xAxis
path.move(to: CGPoint(x: center - 43, y: 0))
let to1 = CGPoint(x: center, y: 50)
let center1 = CGPoint(x: center - 30, y: 0)
let center2 = CGPoint(x: center - 46, y: 42)
let to2 = CGPoint(x: center + 52, y: 0)
let center3 = CGPoint(x: center + 57, y: 47)
let center4 = CGPoint(x: center + 35, y: 0)
path.addCurve(to: to1, control1: center1, control2: center2)
path.addCurve(to: to2, control1: center3, control2: center4)
}
}
}
You are trying to construct a circle with Bezier handles. And it seems you are not calculating the values but guessing and trying. This will be at least very hard if not impossible to solve. Try to add an arc instead of a curve.
Possible implementation:
struct customShape: Shape {
var xAxis : CGFloat
var radius: CGFloat = 40
var animatableData: CGFloat{
get { return xAxis}
set { xAxis = newValue}
}
func path(in rect: CGRect) -> Path {
return Path { path in
path.move(to: CGPoint(x: 0, y: 0))
// add a line to the starting point of the circle
path.addLine(to: CGPoint(x: xAxis - radius, y: 0))
// add the arc with the centerpoint of (xAxis,0) and 180°
path.addArc(center: .init(x: xAxis, y: 0), radius: radius, startAngle: .init(degrees: 180), endAngle: .init(degrees: 0), clockwise: true)
// complete the rectangle
path.addLine(to: .init(x: rect.size.width, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
path.addLine(to: CGPoint(x: 0, y: rect.height))
path.closeSubpath()
}
}
}
Related
I want to generate a curve in the middle of a line programatically, however the edge of the curve is not rounded using my current solution.
func createPath() -> CGPath {
let height: CGFloat = 86.0
let path = UIBezierPath()
let centerWidth = self.frame.width / 2
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: (centerWidth - (height/2)), y: 0))
path.addArc(withCenter: CGPoint(x: centerWidth, y: 0), radius: 80 / 2, startAngle: 180 * CGFloat(PI)/180, endAngle: 0 * CGFloat(PI)/180, clockwise: false)
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.lineCapStyle = .round
path.stroke()
path.close()
self.path = path
return path.cgPath
}
This is the Arc I have created but I want the curves to be rounded:
But I want it to be like this:
This is the code that you need
func createPath() -> CGPath {
let padding: CGFloat = 5.0
let centerButtonHeight: CGFloat = 53.0
let f = CGFloat(centerButtonHeight / 2.0) + padding
let h = frame.height
let w = frame.width
let halfW = frame.width/2.0
let r = CGFloat(18)
let path = UIBezierPath()
path.move(to: .zero)
path.addLine(to: CGPoint(x: halfW-f-(r/2.0), y: 0))
path.addQuadCurve(to: CGPoint(x: halfW-f, y: (r/2.0)), controlPoint: CGPoint(x: halfW-f, y: 0))
path.addArc(withCenter: CGPoint(x: halfW, y: (r/2.0)), radius: f, startAngle: .pi, endAngle: 0, clockwise: false)
path.addQuadCurve(to: CGPoint(x: halfW+f+(r/2.0), y: 0), controlPoint: CGPoint(x: halfW+f, y: 0))
path.addLine(to: CGPoint(x: w, y: 0))
path.addLine(to: CGPoint(x: w, y: h))
path.addLine(to: CGPoint(x: 0.0, y: h))
path.close()
return path.cgPath
}
Result:
You can customize the size of the button...
For a great explanation of how it is work, I recommend you to read this explanation, and you can draw any shape which do you want: https://ayusinghi96.medium.com/draw-custom-shapes-and-views-with-uiberzierpath-ios-1737f5cb975
You can just draw more arc like you already does. Or may be you can use path.addCurve, if you dont want oval
func createPath() -> CGPath {
let bigRadius: CGFloat = 40.0
let path = UIBezierPath()
let centerWidth = self.frame.width / 2
path.move(to: CGPoint(x: 0, y: 0))
let radius: CGFloat = 4 //change it if you want
let leftArcOriginX = centerWidth - bigRadius - radius
let leftArcOriginY: CGFloat = 0
path.addLine(to: CGPoint(x: leftArcOriginX, y: leftArcOriginY))
// add left little arc, change angle if you want, if you dont want oval, may be you can use path.addCurve(to: , controlPoint1: , controlPoint2: )
path.addArc(withCenter: CGPoint(x: leftArcOriginX, y: leftArcOriginY + radius), radius: radius, startAngle: CGFloat(270.0 * Double.pi/180.0), endAngle: 0, clockwise: true)
// add big arc
path.addArc(withCenter: CGPoint(x: centerWidth, y: radius), radius: bigRadius, startAngle: CGFloat(180.0 * Double.pi/180.0), endAngle: CGFloat(0 * Double.pi/180.0), clockwise: false)
// add right litte arc
path.addArc(withCenter: CGPoint(x: centerWidth + bigRadius + radius, y: radius), radius: radius, startAngle: CGFloat(180.0 * Double.pi/180.0), endAngle: CGFloat(270.0 * Double.pi/180.0), clockwise: true)
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.lineCapStyle = .round
path.stroke()
path.close()
return path.cgPath
}
I added curves on the right and left sides. When you look at the stroke function, everything is normal, but when the stroke function is removed, the curve on the right works normally, but the curve on the left does not work. is this a bug?
ContentView
struct ContentView: View {
#State var viewState: CGSize = .zero
var body: some View {
ZStack {
Color.orange
.clipShape(FooBezierPath(rightOffset: viewState).stroke())//remove stroke
.edgesIgnoringSafeArea(.all)
.overlay(
HStack {
Image(systemName: "chevron.right")
.font(.largeTitle)
.offset(y: 115)
.edgesIgnoringSafeArea(.all)
Spacer()
Image(systemName: "chevron.left")
.font(.largeTitle)
.contentShape(Rectangle())
.gesture(DragGesture().onChanged({ (value) in
withAnimation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0)) {
self.viewState = value.translation
}
}).onEnded({ (value) in
withAnimation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0)) {
self.viewState = .zero
}
}))
.edgesIgnoringSafeArea(.all)
.offset(y: 70)
}
,alignment: .topTrailing
)
.padding(.horizontal)
}
}
}
BezierPath
struct FooBezierPath: Shape {
var rightOffset: CGSize
func path(in rect: CGRect) -> Path {
return Path { path in
let width = rect.width + rightOffset.width
let height = rect.height
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
path.addLine(to: CGPoint(x: 0, y: rect.height))
path.addLine(to: CGPoint(x: 0, y: 0))
//MARK: - Left Curve
path.move(to: CGPoint(x: 0, y: 80))
path.addCurve(to: CGPoint(x: 0, y: 180), control1: CGPoint(x: 50, y: 130), control2: CGPoint(x: 50, y: 130))
//MARK: - Right Curve
path.move(to: CGPoint(x: width, y: 80))
path.addCurve(to: CGPoint(x: width, y: 180), control1: CGPoint(x: width - 50, y: 130), control2: CGPoint(x: width - 50, y: 130))
}
}
}
It Helps to Draw the entire shape without moving points, otherwise the clip malfunctions for some reason. Try this:
struct FooBezierPath: Shape {
var rightOffset: CGSize
func path(in rect: CGRect) -> Path {
return Path { path in
let width = rect.width + rightOffset.width
let height = rect.height
// Draw left Edge of Shape
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: 80))
path.addCurve(to: CGPoint(x: 0, y: 180), control1: CGPoint(x: 50, y: 130), control2: CGPoint(x: 50, y: 130))
path.addLine(to: CGPoint(x: 0, y: height))
// Draw Bottom Edge of Shape
path.addLine(to: CGPoint(x: width, y: height))
// Draw Right Edge of Shape
path.addLine(to: CGPoint(x: width, y: 180))
path.addCurve(to: CGPoint(x: width, y: 80), control1: CGPoint(x: width - 50, y: 130), control2: CGPoint(x: width - 50, y: 130))
path.addLine(to: CGPoint(x: width, y: 0))
// Draw Top Edge of Shape
path.addLine(to: CGPoint(x:0, y: 0))
}
}
}
Tried and tested the code, the results are:
Without Stroke
With Stroke
I'm trying to use my own UITabBar instance inside a UITabBarController. Using Storyboard I know that you can add your Custom Class to your TabbarController using the following:
How can I achieve the same thing programmatically?
Here's my custom class:
I've tried modifying and overriding the tabBar variable inside UITabBarController but it seems that it's a get only variable and can not be modified. Is there an easy solution to fix this issue?
Much appreciated!
class CustomTabBar: 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
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
// 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
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let buttonRadius: CGFloat = 35
return abs(self.center.x - point.x) > buttonRadius || abs(point.y) > buttonRadius
}
func createPathCircle() -> CGPath {
let radius: CGFloat = 37.0
let path = UIBezierPath()
let centerWidth = self.frame.width / 2
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: (centerWidth - radius * 2), y: 0))
path.addArc(withCenter: CGPoint(x: centerWidth, y: 0), radius: radius, startAngle: CGFloat(180).degreesToRadians, endAngle: CGFloat(0).degreesToRadians, clockwise: false)
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
}
}
And here's my view controller:
final class TabbarViewController: UITabBarController {
// MARK: - Properties
private lazy var tabBarItemControllers: [UIViewController] = {
let editorController = ...
let settingsController = ...
return [editorController, settingsController]
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureTabbar()
setViewControllers(tabBarItemControllers, animated: true)
}
}
How do i design a UIView whose all 4 corners have different corner radius. So, UIView with its top right corner radius of 20, top left corner radius of 30, bottom left corner radius of 10 and bottom right corner radius of 20
I want to add different cornerRadius to Viw Container shown in image.
using UIBEZIER method cuts off the share comment and like portion.
viw Container is pinned to cell
You can use this UIView extension. It will create and apply a mask layer according to your radius values.
extension UIView {
func applyRadiusMaskFor(topLeft: CGFloat = 0, bottomLeft: CGFloat = 0, bottomRight: CGFloat = 0, topRight: CGFloat = 0) {
let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.width - topRight, y: 0))
path.addLine(to: CGPoint(x: topLeft, y: 0))
path.addQuadCurve(to: CGPoint(x: 0, y: topLeft), controlPoint: .zero)
path.addLine(to: CGPoint(x: 0, y: bounds.height - bottomLeft))
path.addQuadCurve(to: CGPoint(x: bottomLeft, y: bounds.height), controlPoint: CGPoint(x: 0, y: bounds.height))
path.addLine(to: CGPoint(x: bounds.width - bottomRight, y: bounds.height))
path.addQuadCurve(to: CGPoint(x: bounds.width, y: bounds.height - bottomRight), controlPoint: CGPoint(x: bounds.width, y: bounds.height))
path.addLine(to: CGPoint(x: bounds.width, y: topRight))
path.addQuadCurve(to: CGPoint(x: bounds.width - topRight, y: 0), controlPoint: CGPoint(x: bounds.width, y: 0))
let shape = CAShapeLayer()
shape.path = path.cgPath
layer.mask = shape
}
}
Example usage:
let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
view.backgroundColor = .red
view.applyRadiusMaskFor(topLeft: 80, bottomLeft: 40, bottomRight: 30, topRight: 60)
Result: Radius applied image
I want to set different corner radius for a view in Swift -3 , I am able to set the radius for the each corner to the same value like the one mentioned in the following post ,how to set cornerRadius for only top-left and top-right corner of a UIView?
Is there a way I can set the corner radius in the following format ?
Radius top-left: 18
Radius top-right: 18
Radius bottom-right: 3
Radius bottom-left: 18
Do you want to add unique corner value for each corner?
Do you want to add a border after that?
I've got a solution will look like this:
First, add a UIBezierPath extension I made:
extension UIBezierPath {
convenience init(shouldRoundRect rect: CGRect, topLeftRadius: CGSize = .zero, topRightRadius: CGSize = .zero, bottomLeftRadius: CGSize = .zero, bottomRightRadius: CGSize = .zero){
self.init()
let path = CGMutablePath()
let topLeft = rect.origin
let topRight = CGPoint(x: rect.maxX, y: rect.minY)
let bottomRight = CGPoint(x: rect.maxX, y: rect.maxY)
let bottomLeft = CGPoint(x: rect.minX, y: rect.maxY)
if topLeftRadius != .zero{
path.move(to: CGPoint(x: topLeft.x+topLeftRadius.width, y: topLeft.y))
} else {
path.move(to: CGPoint(x: topLeft.x, y: topLeft.y))
}
if topRightRadius != .zero{
path.addLine(to: CGPoint(x: topRight.x-topRightRadius.width, y: topRight.y))
path.addCurve(to: CGPoint(x: topRight.x, y: topRight.y+topRightRadius.height), control1: CGPoint(x: topRight.x, y: topRight.y), control2:CGPoint(x: topRight.x, y: topRight.y+topRightRadius.height))
} else {
path.addLine(to: CGPoint(x: topRight.x, y: topRight.y))
}
if bottomRightRadius != .zero{
path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y-bottomRightRadius.height))
path.addCurve(to: CGPoint(x: bottomRight.x-bottomRightRadius.width, y: bottomRight.y), control1: CGPoint(x: bottomRight.x, y: bottomRight.y), control2: CGPoint(x: bottomRight.x-bottomRightRadius.width, y: bottomRight.y))
} else {
path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y))
}
if bottomLeftRadius != .zero{
path.addLine(to: CGPoint(x: bottomLeft.x+bottomLeftRadius.width, y: bottomLeft.y))
path.addCurve(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius.height), control1: CGPoint(x: bottomLeft.x, y: bottomLeft.y), control2: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius.height))
} else {
path.addLine(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y))
}
if topLeftRadius != .zero{
path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y+topLeftRadius.height))
path.addCurve(to: CGPoint(x: topLeft.x+topLeftRadius.width, y: topLeft.y) , control1: CGPoint(x: topLeft.x, y: topLeft.y) , control2: CGPoint(x: topLeft.x+topLeftRadius.width, y: topLeft.y))
} else {
path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y))
}
path.closeSubpath()
cgPath = path
}
}
Then, add this UIView extension:
extension UIView{
func roundCorners(topLeft: CGFloat = 0, topRight: CGFloat = 0, bottomLeft: CGFloat = 0, bottomRight: CGFloat = 0) {//(topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) {
let topLeftRadius = CGSize(width: topLeft, height: topLeft)
let topRightRadius = CGSize(width: topRight, height: topRight)
let bottomLeftRadius = CGSize(width: bottomLeft, height: bottomLeft)
let bottomRightRadius = CGSize(width: bottomRight, height: bottomRight)
let maskPath = UIBezierPath(shouldRoundRect: bounds, topLeftRadius: topLeftRadius, topRightRadius: topRightRadius, bottomLeftRadius: bottomLeftRadius, bottomRightRadius: bottomRightRadius)
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
layer.mask = shape
}
}
Finally, call method
myView.roundCorners(topLeft: 10, topRight: 20, bottomLeft: 30, bottomRight: 40)
And add border. Apparently, layer.borderRadius won't work properly, so create a border using CAShapeLayer and previously created path.
let borderLayer = CAShapeLayer()
borderLayer.path = (myView.layer.mask! as! CAShapeLayer).path! // Reuse the Bezier path
borderLayer.strokeColor = UIColor.red.cgColor
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.lineWidth = 5
borderLayer.frame = myView.bounds
myView.layer.addSublayer(borderLayer)
Voila!
You could set the default layer.cornerRadius to the smallest value and then set the layer mask's border to the bigger value.
let demoView = UIView(frame: CGRect(x: 100, y: 200, width: 100, height: 100))
demoView.backgroundColor = UIColor.red
demoView.layer.cornerRadius = 3.0
let maskPath = UIBezierPath(roundedRect: demoView.bounds,
byRoundingCorners: [.topLeft, .topRight, .bottomLeft],
cornerRadii: CGSize(width: 18.0, height: 0.0))
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath.cgPath
demoView.layer.mask = maskLayer
view.addSubview(demoView)
A slightly improved and simplified answer based on #Kirill Dobryakov's. Curves can leave very small but noticeable irregularities, when you look at it and you know it's not ideally round (try e.g. view side 40 and radius 20). I have no idea how it's even possible, but anyway, the most reliable way is to use arcs which make ideal round corners, and also an #IBDesigneable component for you:
extension UIBezierPath {
convenience init(shouldRoundRect rect: CGRect, topLeftRadius: CGFloat, topRightRadius: CGFloat, bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat){
self.init()
let path = CGMutablePath()
let topLeft = rect.origin
let topRight = CGPoint(x: rect.maxX, y: rect.minY)
let bottomRight = CGPoint(x: rect.maxX, y: rect.maxY)
let bottomLeft = CGPoint(x: rect.minX, y: rect.maxY)
if topLeftRadius != 0 {
path.move(to: CGPoint(x: topLeft.x + topLeftRadius, y: topLeft.y))
} else {
path.move(to: topLeft)
}
if topRightRadius != 0 {
path.addLine(to: CGPoint(x: topRight.x - topRightRadius, y: topRight.y))
path.addArc(tangent1End: topRight, tangent2End: CGPoint(x: topRight.x, y: topRight.y + topRightRadius), radius: topRightRadius)
}
else {
path.addLine(to: topRight)
}
if bottomRightRadius != 0 {
path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y - bottomRightRadius))
path.addArc(tangent1End: bottomRight, tangent2End: CGPoint(x: bottomRight.x - bottomRightRadius, y: bottomRight.y), radius: bottomRightRadius)
}
else {
path.addLine(to: bottomRight)
}
if bottomLeftRadius != 0 {
path.addLine(to: CGPoint(x: bottomLeft.x + bottomLeftRadius, y: bottomLeft.y))
path.addArc(tangent1End: bottomLeft, tangent2End: CGPoint(x: bottomLeft.x, y: bottomLeft.y - bottomLeftRadius), radius: bottomLeftRadius)
}
else {
path.addLine(to: bottomLeft)
}
if topLeftRadius != 0 {
path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y + topLeftRadius))
path.addArc(tangent1End: topLeft, tangent2End: CGPoint(x: topLeft.x + topLeftRadius, y: topLeft.y), radius: topLeftRadius)
}
else {
path.addLine(to: topLeft)
}
path.closeSubpath()
cgPath = path
}
}
#IBDesignable
open class VariableCornerRadiusView: UIView {
private func applyRadiusMaskFor() {
let path = UIBezierPath(shouldRoundRect: bounds, topLeftRadius: topLeftRadius, topRightRadius: topRightRadius, bottomLeftRadius: bottomLeftRadius, bottomRightRadius: bottomRightRadius)
let shape = CAShapeLayer()
shape.path = path.cgPath
layer.mask = shape
}
#IBInspectable
open var topLeftRadius: CGFloat = 0 {
didSet { setNeedsLayout() }
}
#IBInspectable
open var topRightRadius: CGFloat = 0 {
didSet { setNeedsLayout() }
}
#IBInspectable
open var bottomLeftRadius: CGFloat = 0 {
didSet { setNeedsLayout() }
}
#IBInspectable
open var bottomRightRadius: CGFloat = 0 {
didSet { setNeedsLayout() }
}
override open func layoutSubviews() {
super.layoutSubviews()
applyRadiusMaskFor()
}
}
best way to do this after iOS 11, it looks more smooth in that way.
func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
clipsToBounds = true
layer.cornerRadius = radius
layer.maskedCorners = CACornerMask(rawValue: corners.rawValue)
}
for original answer: https://stackoverflow.com/a/50289822/4206186