14

Historic question.

Pls note, it's now this simple:

https://stackoverflow.com/a/59993994/294884


The unusual bottom corners of an iPhoneX are Apple's new (2017) "continuous corners for iPhoneX".

It is trivial for any experienced iOS programmer to approximate the curve, but:

Does anyone know exactly how to achieve these, exactly as Apple does?

Even if it's a private call, it would be good to know.

It does seem bizarre that Apple have not explained this.

enter image description here

Please note that it's trivial to "approximate" the curve:

To repeat,

  1. it is trivial for any experienced iOS programmer to approximate the curve.

  2. The question being asked here is specifically how to do Apple actually do it?

Please do not post any more answers showing beginners how to draw a curve and approximate the iPhone curve.

Fattie
  • 27,874
  • 70
  • 431
  • 719
  • Hello @Fattie, Check this https://github.com/ZevEisenberg/ScrollSnake – Jack Mar 12 '18 at 10:24
  • 1
    The only way to know how Apple actually does it would be to disassemble the OS's drawing code, which would be tedious and time consuming, and even if you did that, it's a private implementation detail, so if you somehow wrote code that relied on its implementation being a certain way, it could change in a future update of the OS and break your code. So I have to admit that I find the purpose of this question quite difficult to understand. – Charles Srstka Mar 20 '18 at 19:35
  • 1
    @Fattie By "drawing code", I mean exactly what you're looking for—the routine(s) in the OS that draw the corners. You could disassemble those and find out exactly what Apple's doing to draw the corners—but in some release, Apple could change the method entirely to something which looks the same, but is implemented differently. So there is no functional difference between doing this and just eyeballing it as in clemens' answer below, unless I'm missing something. – Charles Srstka Mar 20 '18 at 19:49
  • @Fattie I have not disassembled the code. As I said, I don't see the benefit of doing this when you'd just end up with something identical-looking to the eyeballed version anyway. :-/ However, if you wanted to do it yourself, you're free to do so. You could also set breakpoints on the various drawing APIs and log the contents of the argument registers when they get called. – Charles Srstka Mar 20 '18 at 22:15
  • 1
    "The question being asked here is specifically how to do Apple actually do it?" The PaintCode people show you that. What they do is construct exactly the CGPath that Apple constructs. Which is easy enough to find out: just `print` it. – matt Mar 09 '23 at 18:27
  • right. I guess in almost all cases now, one would just use `.cornerCurve = .continuous`. I assume that, any changes they make in the future to exactly how they do it, would be, applied also in `.cornerCurve = .continuous`. – Fattie Mar 09 '23 at 19:26

2 Answers2

3

I wrote an experimental class which constructs a bezier path which overlaps the border of a CALayer due to @Aflah Bhari's comments. The layer has set its private property continuousCornersto YES. This is the result:

Layer and Path

The border of the layer is blue while the color of the path is red.

Here is the code. You can set radius and insets in attribute inspector of Interface Builder. I have created the image above by setting the class of the view controllers view to ArcView, its radius to 30.0 and the insets to (20.0, 20.0).

Here is the code:

ArcView.h

IB_DESIGNABLE
@interface ArcView : UIView

@property(nonatomic) IBInspectable CGFloat radius;
@property(nonatomic) IBInspectable CGSize insets;

@end

ArcView.m

#import "ArcView.h"

@interface CALayer(Private)

@property BOOL continuousCorners;

@end

@interface ArcView()

@property (strong) CALayer *borderLayer;

@end


@implementation ArcView

- (void)setRadius:(CGFloat)inRadius {
    if(_radius != inRadius) {
        _radius = inRadius;
        self.borderLayer.cornerRadius = inRadius;
        [self setNeedsDisplay];
    }
}

- (void)setInsets:(CGSize)inInsets {
    if(!CGSizeEqualToSize(_insets, inInsets)) {
        _insets = inInsets;
        [self setNeedsLayout];
        [self setNeedsDisplay];
    }
}

- (void)awakeFromNib {
    [super awakeFromNib];
    self.borderLayer = [CALayer new];
    self.borderLayer.borderColor = [[UIColor blueColor] CGColor];
    self.borderLayer.borderWidth = 0.5;
    self.borderLayer.continuousCorners = YES;
    self.borderLayer.cornerRadius = self.radius;
    [self.layer addSublayer:self.borderLayer];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    self.borderLayer.frame = CGRectInset(self.bounds, self.insets.width, self.insets.height);
}

- (void)drawRect:(CGRect)rect {
    CGFloat theRadius = self.radius;
    CGFloat theOffset = 1.2 * theRadius;
    CGRect theRect = CGRectInset(self.bounds, self.insets.width, self.insets.height);
    UIBezierPath *thePath = [UIBezierPath new];
    CGPoint thePoint;

    [thePath moveToPoint:CGPointMake(CGRectGetMinX(theRect) + theOffset, CGRectGetMinY(theRect))];
    [thePath addLineToPoint:CGPointMake(CGRectGetMaxX(theRect) - theOffset, CGRectGetMinY(theRect))];
    thePoint = CGPointMake(CGRectGetMaxX(theRect), CGRectGetMinY(theRect));
    [thePath addQuadCurveToPoint:CGPointMake(CGRectGetMaxX(theRect), CGRectGetMinY(theRect) + theOffset) controlPoint:thePoint];
    [thePath addLineToPoint:CGPointMake(CGRectGetMaxX(theRect), CGRectGetMaxY(theRect) - theOffset)];
    thePoint = CGPointMake(CGRectGetMaxX(theRect), CGRectGetMaxY(theRect));
    [thePath addQuadCurveToPoint:CGPointMake(CGRectGetMaxX(theRect) - theOffset, CGRectGetMaxY(theRect)) controlPoint:thePoint];
    [thePath addLineToPoint:CGPointMake(CGRectGetMinX(theRect) + theOffset, CGRectGetMaxY(theRect))];
    thePoint = CGPointMake(CGRectGetMinX(theRect), CGRectGetMaxY(theRect));
    [thePath addQuadCurveToPoint:CGPointMake(CGRectGetMinX(theRect), CGRectGetMaxY(theRect) - theOffset) controlPoint:thePoint];
    [thePath addLineToPoint:CGPointMake(CGRectGetMinX(theRect), CGRectGetMinY(theRect) + theOffset)];
    thePoint = CGPointMake(CGRectGetMinX(theRect), CGRectGetMinY(theRect));
    [thePath addQuadCurveToPoint:CGPointMake(CGRectGetMinX(theRect) + theOffset, CGRectGetMinY(theRect)) controlPoint:thePoint];
    thePath.lineWidth = 0.5;
    [[UIColor redColor] set];
    [thePath stroke];
}

@end

I hope this helps you with your problem. I've found the factor of 1.2 for theOffset through experiments. You might modify this value if necessary. The value I have chosen for the radius is not optimal and can certainly be improved. But since it depends on the exact distance from the rim, I didn't invest much time for it.

clemens
  • 16,716
  • 11
  • 50
  • 65
  • 1
    Please see my comments on the question. I was hoping there would be a common pattern to how the control points are placed. The Apple® way of placing control points ;) – Aflah Bhari Mar 18 '18 at 20:49
  • @Fattie Fair point. I was just raising a possibility (which is why I didn't create an answer). My hope was that if you matched the corners of the border and did the same for other UI elements e.g. icons (assuming they have the same type of continuous curve) with the same placement (scaled up or down proportionately) of control points, then it could be possible that apple are using that exact method. Purely speculative stuff on my part, I'm afraid. Edit: I'm deleting my comments on the question because I feel like they are adding too much noise. – Aflah Bhari Mar 20 '18 at 20:16
  • @Fattie When I said "icons" I was referring to the image in https://apple.stackexchange.com/questions/313713/what-is-the-definitive-iphone-x-corner-radius/313715#313715 I assumed the diagram was different to apples "new rounded corners" that you mentioned and _continuousRoundedRectBezierPath. So icons used in springboard that (once again, I'm assuming and I'm probably completely wrong) have the new curvature. Anyway, I'm sorry that I can't be of more help, none of this is really helpful to you. Lets hope apple makes a _continuousCurveRect or something. – Aflah Bhari Mar 20 '18 at 22:51
  • @Fattie do you think it's worth updating the question to state that? Especially that the _continuousCorners property in CALayer (which comes up in the link of the last point) isn't the same thing as the iPhoneX continuous curve shape. – Aflah Bhari Mar 21 '18 at 00:17
  • 1
    The PaintCode people have written an Objective-C category that draws these curves, clearly written by reverse-engineering the resulting CGPath. But I think actually this answer gives a rather better match to what Apple is doing. – matt Mar 01 '23 at 22:27
3

As of iOS 13, there's an API available for this:

https://developer.apple.com/documentation/quartzcore/calayercornercurve

See CALayerCornerCurve.continuous

netdigger
  • 3,659
  • 3
  • 26
  • 49