Search code examples
iosobjective-ccore-graphicsuibezierpath

How to Draw a Bezier Curve in UIView


I need to make a curve in a UIView as displayed in the image below. I have to use UIBezierPath. Kindly help me out with this.

I also want to know how to flip the curve from the horizontal axis, so that I have the arc at the top and the base at the bottom.

Example of bezier path


Solution

  • To draw a solid filled in arc within a particular CGSize, you can define a UIBezierPath like so:

    - (UIBezierPath * _Nullable)pathOfArcWithinSize:(CGSize)size {
        if (size.width == 0 || size.height <= 0) return nil;
    
        CGFloat theta = M_PI - atan2(size.width / 2.0, size.height) * 2.0;
        CGFloat radius = self.bounds.size.height / (1.0 - cos(theta));
    
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(0, 0)];
        [path addArcWithCenter:CGPointMake(size.width / 2.0, -radius + size.height) radius:radius startAngle:M_PI_2 + theta endAngle:M_PI_2 - theta clockwise:false];
        [path closePath];
    
        return path;
    }
    

    That's just using a little trigonometry to calculate the angle and radius for the arc given the height and width of the view.

    Once you have that, you can either construct a CAShapeLayer using that path and then add that as a sublayer of a UIView or you can implement your own drawRect method that calls fill on that path. (Or, given that you've tagged this with , you could also do a custom drawRect with CoreGraphics calls, but I'm not sure why you'd do that.)

    For example, you could define a CurvedView class that uses CAShapeLayer:

    //  CurvedView.h
    
    #import <UIKit/UIKit.h>
    
    IB_DESIGNABLE
    @interface CurvedView : UIView
    
    @property (nonatomic, strong) IBInspectable UIColor *fillColor;
    
    @end
    

    And

    //  CurvedView.m
    
    #import "CurvedView.h"
    
    @interface CurvedView ()
    @property (nonatomic, weak) CAShapeLayer *curvedLayer;
    @end
    
    @implementation CurvedView
    
    - (instancetype)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            [self configureView];
        }
        return self;
    }
    
    - (instancetype _Nullable)initWithCoder:(NSCoder *)coder {
        self = [super initWithCoder:coder];
        if (self) {
            [self configureView];
        }
        return self;
    }
    
    - (void)configureView {
        self.fillColor = [UIColor whiteColor];
    
        CAShapeLayer *layer = [CAShapeLayer layer];
        layer.fillColor = self.fillColor.CGColor;
        layer.strokeColor = [UIColor clearColor].CGColor;
        layer.lineWidth = 0;
        [self.layer addSublayer:layer];
        self.curvedLayer = layer;
    }
    
    - (void)setFillColor:(UIColor *)fillColor {
        _fillColor = fillColor;
    
        self.curvedLayer.fillColor = fillColor.CGColor;
    }
    
    - (void)layoutSubviews {
        [super layoutSubviews];
    
        self.curvedLayer.path = [self pathOfArcWithinSize:self.bounds.size].CGPath;
    }
    
    - (UIBezierPath * _Nullable)pathOfArcWithinSize:(CGSize)size {
        if (size.width == 0 || size.height <= 0) return nil;
    
        CGFloat theta = M_PI - atan2(size.width / 2.0, size.height) * 2.0;
        CGFloat radius = self.bounds.size.height / (1.0 - cos(theta));
    
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(0, 0)];
        [path addArcWithCenter:CGPointMake(size.width / 2.0, -radius + size.height) radius:radius startAngle:M_PI_2 + theta endAngle:M_PI_2 - theta clockwise:false];
        [path closePath];
    
        return path;
    }
    
    @end
    

    That yields:

    enter image description here

    Or, if you'd rather use the drawRect approach rather than using CAShapeLayer:

    //  CurvedView.m
    
    #import "CurvedView.h"
    
    @implementation CurvedView
    
    - (instancetype)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            [self configureView];
        }
        return self;
    }
    
    - (instancetype _Nullable)initWithCoder:(NSCoder *)coder {
        self = [super initWithCoder:coder];
        if (self) {
            [self configureView];
        }
        return self;
    }
    
    - (void)configureView {
        self.fillColor = [UIColor whiteColor];
    }
    
    - (void)setFillColor:(UIColor *)fillColor {
        _fillColor = fillColor;
    
        [self setNeedsDisplay];
    }
    
    - (void)drawRect:(CGRect)rect {
        UIBezierPath *path = [self pathOfArcWithinSize:self.bounds.size];
        [self.fillColor setFill];
        [path fill];
    }
    
    - (UIBezierPath * _Nullable)pathOfArcWithinSize:(CGSize)size {
        if (size.width == 0 || size.height <= 0) return nil;
    
        CGFloat theta = M_PI - atan2(size.width / 2.0, size.height) * 2.0;
        CGFloat radius = self.bounds.size.height / (1.0 - cos(theta));
    
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(0, 0)];
        [path addArcWithCenter:CGPointMake(size.width / 2.0, -radius + size.height) radius:radius startAngle:M_PI_2 + theta endAngle:M_PI_2 - theta clockwise:false];
        [path closePath];
    
        return path;
    }
    
    @end
    

    If you want the arc to occupy the bottom of the view, the path would look like:

    - (UIBezierPath * _Nullable)pathOfArcWithinSize:(CGSize)size {
        if (size.width == 0 || size.height <= 0) return nil;
    
        CGFloat theta = M_PI - atan2(size.width / 2.0, size.height) * 2.0;
        CGFloat radius = self.bounds.size.height / (1.0 - cos(theta));
    
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(0, size.height)];
        [path addArcWithCenter:CGPointMake(size.width / 2.0, radius) radius:radius startAngle:M_PI_2 * 3.0 + theta endAngle:M_PI_2 * 3.0 - theta clockwise:false];
        [path closePath];
    
        return path;
    }
    

    Essentially, that's the same theta and radius, but start in lower left corner, set the center to be size.width / 2.0, radius, and arc from M_PI_2 * 3.0 ± theta:

    enter image description here