Omer Tekbiyik
Omer Tekbiyik

Reputation: 4744

How to draw smooth curve according to given height

I want to drive a smooth curve that curve's 1/3 is outward 2/3 is inward.This is what I want to achieve.

enter image description here

So far , I tried this with adding 1 curve like :

  var height: CGFloat = UIScreen.main.bounds.height / 14
  let centerWidth = self.frame.width / 2 
  let screenView = UIScreen.main.bounds
  let width = (screenView.width - (2*screenView.width/40)) / 10

     path.move(to: CGPoint(x: 0, y: 0)) // start top left
     path.addLine(to: CGPoint(x: (centerWidth - width * 2.5), y: 0)) // the beginning of the trough
           // first curve down
     path.addCurve(to: CGPoint(x: centerWidth   , y: height),
                     controlPoint1: CGPoint(x: (centerWidth - width * 1.33), y: 0), controlPoint2: CGPoint(x: centerWidth - width * 1.33  , y: height))

But when I try this outward and inward curve's have same height. So I changed it and tried adding 2 curve like :

     path.move(to: CGPoint(x: 0, y: 0)) // start top left
    path.addLine(to: CGPoint(x: (centerWidth - width * 2.5), y: 0)) // the beginning of the trough
        // first curve down
    path.addCurve(to: CGPoint(x: centerWidth - width * 1.33 , y: height/3),
                  controlPoint1: CGPoint(x: (centerWidth - width * 1.33), y: 0), controlPoint2: CGPoint(x: centerWidth - width * 1.33 , y: height/3))

    path.addCurve(to: CGPoint(x: centerWidth, y: height),
                                    controlPoint1: CGPoint(x: centerWidth - width * 1.33 , y: height/3), controlPoint2: CGPoint(x: centerWidth - width , y: height  ))

It similar to what I want but junction point doesnt look the curve is a single piece enter image description here

I have tried so many things but cant draw what I want exacly.So How can I draw a curve that endpoint height's 1/3 gonna be outward and 2/3 inward that looks like a one piece.

I'm open to all kinds of ideas to draw.Regards

NOTE

I'm trying to customize tabbar's center button which height value is UIScreen.main.bounds.height and width is `

let screenView = UIScreen.main.bounds
let width = (screenView.width - (2*screenView.width/40))`

Upvotes: 0

Views: 281

Answers (1)

Rob
Rob

Reputation: 437432

As I look at your designer’s rendering, if you are breaking this into two separate Bézier curves, the key observation is that the top ⅓ curve is symmetrical (with respect to itself) as is the bottom ⅔ curve. And for symmetrical Bézier curves, a quad Bézier is easier to deal with.

And to make the inflection point seamless, you’ll want to ensure that two control points (one for the quad Bézier above the inflection point, the other for the quad Bézier below the inflection point) are collinear with the inflection point itself. E.g.:

enter image description here

It takes a little trigonometry to figure out the placement of these control points, e.g.

let center = CGPoint(x: view.bounds.midX, y: 200)
let radius: CGFloat = 140
let curveOffsetBottom: CGFloat = 30
let curveOffsetInflection: CGFloat = 50

greenCircularShapeLayer.path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true).cgPath

let h = radius + curveOffsetBottom + 50

let curveBottom = CGPoint(x: center.x,
                          y: center.y + radius + curveOffsetBottom)

let y0 = radius + curveOffsetBottom - h * 2 / 3
let t0 = asin(y0 / (radius + curveOffsetInflection))
let x0 = y0 / tan(t0)

let inflectionPoint = CGPoint(x: center.x - x0, y: center.y + y0)

let t1 = atan((curveBottom.x - inflectionPoint.x) / (curveBottom.y - inflectionPoint.y))

let t2 = 2 * (.pi / 2 - t1)

let x2 = (curveBottom.y - inflectionPoint.y) / tan(t2)
let x1 = x2 / 2

let cp1 = CGPoint(x: inflectionPoint.x - x1, y: curveBottom.y - h)
let cp2 = CGPoint(x: inflectionPoint.x + x2, y: curveBottom.y)

let curveTop = CGPoint(x: cp1.x - cp1.distance(to: inflectionPoint), y: curveBottom.y - h)

let path = UIBezierPath()
path.move(to: curveTop)
path.addQuadCurve(to: inflectionPoint, controlPoint: cp1)
path.addQuadCurve(to: curveBottom, controlPoint: cp2)

path.addLine(to: CGPoint(x: view.bounds.maxX, y: path.currentPoint.y))
path.addLine(to: CGPoint(x: view.bounds.maxX, y: view.bounds.maxY))
path.addLine(to: CGPoint(x: view.bounds.minX, y: view.bounds.maxY))
path.addLine(to: CGPoint(x: view.bounds.minX, y: curveTop.y))
path.close()

blackCurveShapeLayer.path = path.cgPath

Where

extension CGPoint {
    func distance(to point: CGPoint) -> CGFloat {
        hypot(point.x - x, point.y - y)
    }
}

Upvotes: 1

Related Questions