Kenzo
Kenzo

Reputation: 640

UIView with rouded top side

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

Upvotes: 1

Views: 1182

Answers (4)

Kenzo
Kenzo

Reputation: 640

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
    }

}

Upvotes: 4

Alvin George
Alvin George

Reputation: 14296

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)

}

Upvotes: 0

Duncan C
Duncan C

Reputation: 131436

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.

Upvotes: 0

sazz
sazz

Reputation: 3291

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];

Upvotes: 0

Related Questions