Home > Mobile >  Create ellipse shape ImageView with straight edges
Create ellipse shape ImageView with straight edges

Time:02-22

Im trying to create a custom shaped ImageView that looks like this.

custom ellipse

My initial though was to round corners using the following extensions like so:

//: A UIKit based Playground for presenting user interface
  
import UIKit
import PlaygroundSupport

extension UIBezierPath {
  static func rectWithRoundCorners(
    size: CGSize,
    topLeft: CGFloat = 0,
    bottomLeft: CGFloat = 0,
    bottomRight: CGFloat = 0,
    topRight: CGFloat = 0)
    -> UIBezierPath
  {
    let path = UIBezierPath()
    path.move(to: CGPoint(x: size.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: size.height - bottomLeft))
    path.addQuadCurve(to: CGPoint(x: bottomLeft, y: size.height), controlPoint: CGPoint(x: 0, y: size.height))
    path.addLine(to: CGPoint(x: size.width - bottomRight, y: size.height))
    path.addQuadCurve(
      to: CGPoint(x: size.width, y: size.height - bottomRight),
      controlPoint: CGPoint(x: size.width, y: size.height))
    path.addLine(to: CGPoint(x: size.width, y: topRight))
    path.addQuadCurve(to: CGPoint(x: size.width - topRight, y: 0), controlPoint: CGPoint(x: size.width, y: 0))
    return path
  }
}

extension UIView {
  func roundCorners(topLeft: CGFloat = 0, bottomLeft: CGFloat = 0, bottomRight: CGFloat = 0, topRight: CGFloat = 0) {
    let path = UIBezierPath.rectWithRoundCorners(
    size: bounds.size,
    topLeft: topLeft,
    bottomLeft: bottomLeft,
    bottomRight: bottomRight,
    topRight: topRight)

    let shape = CAShapeLayer()
    shape.path = path.cgPath
    layer.mask = shape
  }
}

class MyViewController : UIViewController {
    override func loadView() {
        let view = UIView()
        view.backgroundColor = .white

        let label = UILabel()
        label.frame = CGRect(x: 150, y: 200, width: 114, height: 114)
        label.backgroundColor = .red
        label.text = "Hello World!"
        label.textColor = .black
        label.roundCorners(topLeft: 100, bottomLeft: 50, bottomRight: 0, topRight: 50)

        
        view.addSubview(label)
        self.view = view
    }
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

but I get the following result:

bad result

Is there a better approach using rounded rect and UIBezierPath? I have no experience using UIBezierPath so not sure how it will do round corners and straight edges...

CodePudding user response:

Well, your shape only has two straight edges, so there should be, at most, two addLine calls.

There might be more mathematically rigorous solutions, but I use tools like Adobe Photoshop or Illustrator to approximate what Bézier curves I need.

The overall shape (bounded by 117×103 rectangle) can be reasonably well approximated with one big cubic Bézier (shown below), one addLine for the bottom edge, one quad Bézier (or arc) for the rounded, lower-right corner, and then just close the path.

enter image description here

To get those control points, I used a pen tool in Adobe Illustrator to create a cubic bezier of the appropriate shape, and I then exported the path as a SVG, and deciphered the file to came up with the appropriate coordinates for the various control points:

let rect = CGRect(origin: .zero, size: CGSize(width: 117, height: 103))
let radius: CGFloat = 10

let path = UIBezierPath()

path.move(to: CGPoint(x: rect.maxX, y: 27.62))

path.addCurve(to: CGPoint(x: 11.77, y: rect.maxY),
              controlPoint1: CGPoint(x: 77, y: -46.21),
              controlPoint2: CGPoint(x: -36.11, y: 44.91))

path.addLine(to: CGPoint(x: rect.maxX - radius, y: rect.maxY))

path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.maxY - radius),
                  controlPoint: CGPoint(x: rect.maxX, y: rect.maxY))

path.close()

let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.blue.cgColor
shapeLayer.path = path.cgPath

someView.layer.addSublayer(shapeLayer)

Tweak the coordinates as you see fit, but hopefully this illustrates (no pun intended) how to approximate the Bézier curves in a drawing and translate that to Swift code.

This is the resulting image:

enter image description here

  • Related