How to round off one corner of a resizable UIView

2020-06-18 05:16发布

I'm using this code to round off one corner of my UIView:

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:
    self.view.bounds byRoundingCorners:(UIRectCornerTopLeft) cornerRadii:
    CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.view.bounds;
maskLayer.path = maskPath.CGPath;
self.view.layer.mask = maskLayer;
self.view.layer.masksToBounds = NO;

This code works, as long as I don't ever resize the view. If I make the view larger, the new area does not appear because it's outside the bounds of the mask layer (this mask layer does not automatically resize itself with the view). I could just make the mask as large as it will ever need to be, but it could be full-screen on the iPad so I'm worried about performance with a mask that big (I'll have more than one of these in my UI). Also, a super-sized mask wouldn't work for the situation where I need the upper right corner (alone) to be rounded off.

Is there a simpler, easier way to achieve this?

Update: here is what I'm trying to achieve: http://i.imgur.com/W2AfRBd.png (the rounded corner I want is circled here in green).

I have achieved a working version of this, using a subclass of UINavigationController and overriding viewDidLayoutSubviews like so:

- (void)viewDidLayoutSubviews {
    CGRect rect = self.view.bounds;
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect 
        byRoundingCorners:UIRectCornerTopLeft cornerRadii:CGSizeMake(8.0, 8.0)];
    self.maskLayer = [CAShapeLayer layer];
    self.maskLayer.frame = rect;
    self.maskLayer.path = maskPath.CGPath;
    self.view.layer.mask = self.maskLayer;
}

I then instantiate my UINavigationController subclass with my view controller, and then I offset the frame of the nav controller's view by 20px (y) to expose the status bar and leave a 44-px high navigation bar, as shown in the picture.

The code is working, except that it doesn't handle rotation very well at all. When the app rotates, viewDidLayoutSubviews gets called before the rotation and my code creates a mask that fits the view after rotation; this creates an undesirable blockiness to the rotation, where bits that should be hidden are exposed during the rotation. Also, whereas the app's rotation is perfectly smooth without this mask, with the mask being created the rotation becomes noticeably jerky and slow.

The iPad app Evomail also has rounded corners like this, and their app suffers from the same problem.

标签: ios calayer
9条回答
Ridiculous、
2楼-- · 2020-06-18 05:41

Try this. Hope this will helps you.

UIView* parent = [[UIView alloc] initWithFrame:CGRectMake(10,10,100,100)];
parent.clipsToBounds = YES;

UIView* child = [[UIView alloc] new];
child.clipsToBounds = YES;
child.layer.cornerRadius = 3.0f;
child.backgroundColor = [UIColor redColor];
child.frame = CGRectOffset(parent.bounds, +4, -4);


[parent addSubView:child];
查看更多
放我归山
3楼-- · 2020-06-18 05:42

I know this is a pretty hacky way of doing it but couldn't you just add a png over the top of the corner?

Ugly I know, but it won't affect performance, rotation will be fine if its a subview and users won't notice.

查看更多
Emotional °昔
4楼-- · 2020-06-18 05:44

I thought about this again and I think there is a simpler solution. I updated my sample to showcase both solutions.

The new solution is to simply create a container view that has 4 rounded corners (via CALayer cornerRadius). You can size that view so only the corner you're interested in is visible on screen. This solution doesn't work well if you need 3 corners rounded, or two opposite (on the diagonal) corners rounded. I think it works in most other cases, including the one you've described in your question and screenshot.

Here's the repo for the sample: https://github.com/TomSwift/testRoundedCorner

查看更多
beautiful°
5楼-- · 2020-06-18 05:48

I'm a fan of doing what @Martin suggests. As long as there isn't animated content behind the rounded-corner then you can pull this off - even with a bitmap image displayed behind the frontmost view needing the rounded corner.

I created a sample project to mimic your screenshot. The magic happens in a UIView subclass called TSRoundedCornerView. You can place this view anywhere you want - above the view you want to show a rounded corner on, set a property to say what corner to round (adjust the radius by adjusting the size of the view), and setting a property that is the "background view" that you want to be visible in the corner.

Here's the repo for the sample: https://github.com/TomSwift/testRoundedCorner

And here's the drawing magic for the TSRoundedCornerView. Basically we create an inverted clip path with our rounded corner, then draw the background.

- (void) drawRect:(CGRect)rect
{
    CGContextRef gc = UIGraphicsGetCurrentContext();

    CGContextSaveGState(gc);
    {
        // create an inverted clip path
        // (thanks rob mayoff: http://stackoverflow.com/questions/9042725/drawrect-how-do-i-do-an-inverted-clip)
        UIBezierPath* bp = [UIBezierPath bezierPathWithRoundedRect: self.bounds
                                                 byRoundingCorners: self.corner // e.g. UIRectCornerTopLeft
                                                       cornerRadii: self.bounds.size];
        CGContextAddPath(gc, bp.CGPath);
        CGContextAddRect(gc, CGRectInfinite);
        CGContextEOClip(gc);

        // self.backgroundView is the view we want to show peering out behind the rounded corner
        // this works well enough if there's only one layer to render and not a view hierarchy!
        [self.backgroundView.layer renderInContext: gc];

//$ the iOS7 way of rendering the contents of a view.  It works, but only if the UIImageView has already painted...  I think.
//$ if you try this, be sure to setNeedsDisplay on this view from your view controller's viewDidAppear: method.
//        CGRect r = self.backgroundView.bounds;
//        r.origin = [self.backgroundView convertPoint: CGPointZero toView: self];
//        [self.backgroundView drawViewHierarchyInRect: r
//                                  afterScreenUpdates: YES];
    }
    CGContextRestoreGState(gc);
}
查看更多
三岁会撩人
6楼-- · 2020-06-18 05:51

You could subclass the view you are using and override "layoutSubviews"method. This one gets called everytime your view dimensions change.

Even if "self.view"(referenced in your code) is your viewcontroller's view, you can still set this view to a custom class in your storyboard. Here's the modified code for the subclass:

- (void)layoutSubviews {
    [super layoutSubviews];

    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:
                          self.bounds byRoundingCorners:(UIRectCornerTopLeft) cornerRadii:
                          CGSizeMake(10.0, 10.0)];
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    maskLayer.frame = self.bounds;
    maskLayer.path = maskPath.CGPath;
    self.layer.mask = maskLayer;
    self.layer.masksToBounds = NO;
}
查看更多
爷的心禁止访问
7楼-- · 2020-06-18 05:55

Two ideas:

  • Resize the mask when the view is resized. You don't get automatic resizing of sublayers the way you get automatic resizing of subviews, but you still get an event, so you can do manual resizing of sublayers.

  • Or... If this a view whose drawing and display you are in charge of, make the rounding of the corner a part of how you draw the view in the first place (by clipping). That is in fact the most efficient approach.

查看更多
登录 后发表回答