3

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

Nisse Engström
  • 4,738
  • 23
  • 27
  • 42
Diksha
  • 439
  • 7
  • 22
  • Refer this link https://www.raywenderlich.com/34003/core-graphics-tutorial-curves-and-layers – Nirav D Jun 14 '16 at 07:12
  • I think this link is useful to you http://stackoverflow.com/questions/12904767/drawing-a-bezier-curve-between-a-set-of-given-points – Birendra Jun 14 '16 at 07:32
  • is their any restriction on using Image as curve ? @Diksha – Rahul Jun 14 '16 at 08:32
  • @RahulMishra i have to use UIView rather than image – Diksha Jun 14 '16 at 08:42
  • @Diksha You can Use a `UIView` and Put an `UIImageView` inside it..and Place a image having curve. I have done same functionality – Rahul Jun 14 '16 at 08:44
  • @RahulMishra You have used Image on imageview or you have done through beizerpath – Diksha Jun 14 '16 at 08:44
  • @Diksha I have taken a white Image having same curve and Applied to ImageView and then Positioned the UIImage VIew accordingly... – Rahul Jun 14 '16 at 09:29
  • @Diksha Do you have this white Curve Image ? – Rahul Jun 14 '16 at 09:30
  • @RahulMishra no i have to execute the task with beizerpath – Diksha Jun 14 '16 at 09:30
  • @Diksha I haven't used Bezire Curve. I will try if I get this curve then will post my answer – Rahul Jun 14 '16 at 09:34
  • @Diksha Even If you make A curve then How can You acheive same UI as shown in Image.... Bezire curve will only add a curve. How will you cut the map part under the curve. – Rahul Jun 14 '16 at 09:45
  • I will put my view over the mapview thus it will look like the map under the curve – Diksha Jun 14 '16 at 09:47
  • @Diksha If you can manage to get a Image have curve then it will takes minutes to make this UI.... Just Make A UIVIew as a container of MapView and CurveImageView....Make MapView frame equal to Parent View and at the top put your ImageView.. – Rahul Jun 14 '16 at 12:29
  • @RahulMishra No rahul,actually i have to do it without image.I know it would be easy with image – Diksha Jun 14 '16 at 12:54
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/114639/discussion-between-rahulmishra-and-diksha). – Rahul Jun 14 '16 at 13:00

1 Answers1

8

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

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks rob..you saved me again..i want to learn this..actually dont know how to deal with UIBeizers,i want to learn this,so can you help me from where can i start...thanks...:) – Diksha Jul 12 '16 at 10:03
  • @Elan - For the basics of bezier paths, see Apple documentation (https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/BezierPaths/BezierPaths.html#//apple_ref/doc/uid/TP40010156-CH11-SW1) or google "uibezierpath tutorial". For the maths to achieve the desired path, this is drawing on my old high-school trigonometry from the mists of time. :) Bottom line, just start playing around with building your own `UIBezierPath` in either custom `UIView` subclass (like above) or `CAShapeLayer` and I'm sure you'll pick it up quickly. It's not as hard as it looks. – Rob Jul 12 '16 at 10:12