1

I create this post to answer to the question on hold posting at Create a UIView with rounded top edge

The question is how to create a rounded UIView like this ?

enter image description here

... and NOT like this (2 rounded corners) enter image description here

Answer : https://stackoverflow.com/a/28075863/1952147

Community
  • 1
  • 1
Kenzo
  • 640
  • 6
  • 17

4 Answers4

4

The solution is combined an oval and a rectangle with bezierPath.

You can add height offset if needed to customize more the curve.

@implementation UIView (RoundedCorners)    
-(void)setRoundedRectWithOvalWidthOffset:(CGFloat)offset {
        CGRect bounds = self.bounds;

        CGRect rectBounds = CGRectMake(bounds.origin.x,
                                       bounds.origin.y + bounds.size.height/2,
                                       bounds.size.width,
                                       bounds.size.height/2);
        UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:rectBounds];
        //[rect addClip];

        CGRect ovalBounds = CGRectMake(bounds.origin.x - offset/2,
                                       bounds.origin.y,
                                       bounds.size.width + offset,
                                       bounds.size.height);
        UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect:ovalBounds];
        //[oval addClip];

        [rectPath appendPath:ovalPath];

        // Create the shape layer and set its path
        CAShapeLayer *maskLayer = [CAShapeLayer layer];
        maskLayer.frame = bounds;
        maskLayer.path = rectPath.CGPath;

        // Set the newly created shape layer as the mask for the view's layer
        self.layer.mask = maskLayer;
    }
@end

Code for swift 3.0

//MARK: - UIView Extension
extension UIView {
    func setTopCurve(){
        let offset = CGFloat(self.frame.size.height/4)
        let bounds = self.bounds
        let rectBounds = CGRect(x: bounds.origin.x, y: bounds.origin.y + bounds.size.height/2  , width:  bounds.size.width, height: bounds.size.height / 2)
        let rectPath = UIBezierPath(rect: rectBounds)
        let ovalBounds = CGRect(x: bounds.origin.x - offset / 2, y: bounds.origin.y, width: bounds.size.width + offset, height: bounds.size.height)
        let ovalPath = UIBezierPath(ovalIn: ovalBounds)
    rectPath.append(ovalPath)

        let maskLayer = CAShapeLayer.init()
        maskLayer.frame = bounds
        maskLayer.path = rectPath.cgPath
        self.layer.mask = maskLayer
    }

}
Patel Jigar
  • 2,141
  • 1
  • 23
  • 30
Kenzo
  • 640
  • 6
  • 17
0

It's actually much simpler than that. There is a method in UIBezierPath, bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:, that takes a bit mask that lets you control which corners are rounded and which are not. It will create a path for you that only rounds 2 of 4 corners if that's what you want.

I thought there was a corresponding CGPath function, but I don't see one in the docs. (UIBezierPath uses CGPath under the covers, so most of the time UIBezierPath methods have corresponding CGPath functions. I guess the UIBezierPath is built from primitives like lines and arcs.

If what you need is a CGPath you can always use the UIBezierPath method and then get a CGPath from the resulting UIBezierPath.

It's also possible to build a rounded rectangle yourself by combining the lines for the sides with arcs for the corners, but figuring out how to do it is a bit of a head-scratcher.

EDIT: My solution works, but the result makes the top into a semicircle who's radius is half the rectangle's width, not the flattened oval in the OPs question.

It looks like this:

enter image description here

If the requirement is that the top be a flattened oval then your solution, or a custom path built with a mixture of lines and cubic Bezier curves would be the way to go.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
0

What about this?

    // Create the view
UIView *theView = [[UIView alloc] initWithFrame:CGRectMake(50, 220, 200, 200)];
[theView setBackgroundColor:[UIColor orangeColor]];

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:theView.bounds byRoundingCorners:UIRectCornerTopRight|UIRectCornerTopLeft cornerRadii:CGSizeMake(10.0, 10.0)];

CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = theView.bounds;
maskLayer.path = maskPath.CGPath;
theView.layer.mask = maskLayer;


[self.view addSubview:theView];
sazz
  • 3,193
  • 3
  • 23
  • 33
0

Using Extension:

extension UIView {

func addTopRoundedCornerToView(targetView:UIView?, desiredCurve:CGFloat?)
{
    let offset:CGFloat =  targetView!.frame.width/desiredCurve!
    let bounds: CGRect = targetView!.bounds

    let rectBounds: CGRect = CGRectMake(bounds.origin.x, bounds.origin.y+bounds.size.height / 2, bounds.size.width, bounds.size.height / 2)

    let rectPath: UIBezierPath = UIBezierPath(rect: rectBounds)
    let ovalBounds: CGRect = CGRectMake(bounds.origin.x - offset / 2, bounds.origin.y, bounds.size.width + offset, bounds.size.height)
    let ovalPath: UIBezierPath = UIBezierPath(ovalInRect: ovalBounds)
    rectPath.appendPath(ovalPath)

    // Create the shape layer and set its path
    let maskLayer: CAShapeLayer = CAShapeLayer()
    maskLayer.frame = bounds
    maskLayer.path = rectPath.CGPath

    // Set the newly created shape layer as the mask for the view's layer
    targetView!.layer.mask = maskLayer
}
}

Usage:

override func viewWillAppear(animated: Bool) {
    self.navigationController?.navigationBarHidden =  true
    self.view.addTopRoundedCornerToView(self.view, desiredCurve: 0.6)

}
Alvin George
  • 14,148
  • 92
  • 64