Crazy rounded rect UIBezierPath behavior on iOS 7. What is the deal?
Asked Answered
I

2

7

The simple UIView below draws a rounded rectangle. When I pass a corner radius of 65 or below it rounds correctly, but 66 and above and it generates a perfect circle! What is going on here? It should only show a circle when the corner radius is equal to 1/2 the frame width, but it seems that it is drawing a circle when the radius is about 1/3rd, no matter what the size of the view is. This behavior appears on iOS 7. On iOS 6 I get expected behavior.

#import "ViewController.h"
@interface MyView : UIView
@end

@implementation MyView
-(void)drawRect:(CGRect)rect {
  UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 200, 200) cornerRadius:65];
  CGContextRef c = UIGraphicsGetCurrentContext();
  CGContextAddPath(c, path.CGPath);
  [[UIColor redColor] set];
  CGContextStrokePath(c);
}
@end

@interface ViewController ()
@end

@implementation ViewController
- (void)viewDidLoad {
  [super viewDidLoad];
  MyView *v = [[MyView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
  [self.view addSubview:v];
}
@end

enter image description here enter image description here

enter image description here

Intenerate answered 24/7, 2014 at 14:7 Comment(10)
Filed rdar://17801182Intenerate
Good catch. Please keep this post updated.Grew
This bug is still present on iOS 8.Intenerate
Still present on iOS 9Interpol
Oh, here's a solution: paintcodeapp.com/news/code-for-ios-7-rounded-rectanglesInterpol
Have been fighting with this all day. That's just super weird.Allier
Still present on iOS 13.Hybris
Still present on… iOS 15. Wow.Stave
Still present on... iOS 16. Wow again.Britnibrito
Still present on... iOS 18. This thing won't be fixed. Or it's intentional.Ppi
N
3

This will probably never be fixed. Here is why.

The math to calculate if you can make a squircle is: radius * magicMultiplier * 2. If the result is longer than the side, it can't make a squircle so it makes a circle.

The magicMultiplier is required because to make it look like a squircle, the bezier curve needs to start from a longer distance than the radius. The magic multiplier provides that extra distance.

From my research and playing around with the bezier function, I believe the magic multiplier might be something around 1.0 + 8.0 / 15.0 = 1.533.

So 66*(1+8/15)*2 is 202.4 which is longer than the shortest side (200), thus it makes it a circle.

However! 65*(1+8/15)*2 is 199.33 which is smaller than 200, so it squircles it correctly.

Possible solutions

  1. Code your own bezier curve function (or get one online)
  2. Use the view's layer.cornerRadius to achieve the same thing since Apple doesn't clamp the corner radius here.
layer.cornerCurve = .continuous
layer.cornerRadius = min(radius, min(bounds.width, bounds.height)/2.0)
// You might want to clamp it yourself

Bear in mind that draw(in ctx) doesn't work with layer.maskedCorners. So you can't use SnapshotTesting with those.

Nannienanning answered 10/5, 2022 at 21:34 Comment(0)
B
0

FYI, to->circle bug happens at approx. 65%:

- "INFO --- width/height (SQUARE): 12.121212121212121"
- "INFO --- halfSize == maxRadius: 6.0606060606060606"
- "INFO --- cornerRadius: 3.967272727272727"
- "INFO --- ratioBug: 0.6546"

extension CGRect
{
    // For generic Rectangle
    // 28112022 (bug happens at 65%) (cornerRadius / maxRadius)
    // radiusFactor: [0, 1] 
    func getOptimalCornerRadius(radiusFactor: CGFloat) -> CGFloat
    {
        let minSize = self.sizeMin()
        let maxRadius = minSize / 2
        let cornerRadius = maxRadius * radiusFactor
        return cornerRadius
    }
}
Britnibrito answered 29/11, 2022 at 7:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.