ios UIView 中的两个圆角

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4847163/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 18:43:17  来源:igfitidea点击:

Round two corners in UIView

iosobjective-ciphonerounded-corners

提问by Jumhyn

A little while ago I posted a question about rounding just two corners of a view, and got a great response, but am having problems implementing it. Here is my drawRect: method:

不久前,我发布了一个关于将视图的两个角四舍五入的问题,得到了很好的回应,但在实施时遇到了问题。这是我的 drawRect: 方法:

- (void)drawRect:(CGRect)rect {
    //[super drawRect:rect]; <------Should I uncomment this?
    int radius = 5;
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextBeginPath(context);
    CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius, radius, M_PI, M_PI / 2, 1);
    CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + rect.size.height - radius, radius, M_PI / 2, 0.0f, 1);
    CGContextClosePath(context);
    CGContextClip(context);
}

The method is being called, but doesn't seem to affect the outcome of the view. Any ideas why?

正在调用该方法,但似乎不会影响视图的结果。任何想法为什么?

回答by SachinVsSachin

CACornerMaskintroduced in iOS 11, which help to define topleft, topright, bottomleft, bottom right in view layer. Below is example to use.

CACornerMask在 iOS 11 中引入,有助于在视图层中定义左上、右上、左下、右下。下面是使用示例。

Here I try to rounded only two top corner:

在这里,我尝试只对两个顶角进行四舍五入:

myView.clipsToBounds = true
myView.layer.cornerRadius = 10
myView.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]

FYI Ref:

仅供参考:

回答by lomanf


as far as I know, if you also need to mask the subviews, you could use CALayermasking. There are 2 ways to do this. The first one is a bit more elegant, the second one is a workaround :-) but it's also fast. Both are based on CALayermasking. I've used both methods in a couple of projects last year then I hope you can find something useful.


据我所知,如果您还需要屏蔽子视图,则可以使用CALayer屏蔽。有两种方法可以做到这一点。第一个更优雅,第二个是一种解决方法:-)但它也很快。两者都基于CALayer掩码。去年我在几个项目中使用了这两种方法,希望你能找到一些有用的东西。

Solution 1

解决方案1

First of all, I created this function to generate an image mask on the fly (UIImage) with the rounded corner I need. This function essentially needs 5 parameters: the bounds of the image and 4 corner radius (top-left, top-right, bottom-left and bottom-right).

首先,我创建了这个函数来动态生成一个UIImage带有我需要的圆角的图像蒙版 ( )。这个函数本质上需要 5 个参数:图像的边界和 4 个角半径(左上角、右上角、左下角和右下角)。


static inline UIImage* MTDContextCreateRoundedMask( CGRect rect, CGFloat radius_tl, CGFloat radius_tr, CGFloat radius_bl, CGFloat radius_br ) {  

    CGContextRef context;
    CGColorSpaceRef colorSpace;

    colorSpace = CGColorSpaceCreateDeviceRGB();

    // create a bitmap graphics context the size of the image
    context = CGBitmapContextCreate( NULL, rect.size.width, rect.size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast );

    // free the rgb colorspace
    CGColorSpaceRelease(colorSpace);    

    if ( context == NULL ) {
        return NULL;
    }

    // cerate mask

    CGFloat minx = CGRectGetMinX( rect ), midx = CGRectGetMidX( rect ), maxx = CGRectGetMaxX( rect );
    CGFloat miny = CGRectGetMinY( rect ), midy = CGRectGetMidY( rect ), maxy = CGRectGetMaxY( rect );

    CGContextBeginPath( context );
    CGContextSetGrayFillColor( context, 1.0, 0.0 );
    CGContextAddRect( context, rect );
    CGContextClosePath( context );
    CGContextDrawPath( context, kCGPathFill );

    CGContextSetGrayFillColor( context, 1.0, 1.0 );
    CGContextBeginPath( context );
    CGContextMoveToPoint( context, minx, midy );
    CGContextAddArcToPoint( context, minx, miny, midx, miny, radius_bl );
    CGContextAddArcToPoint( context, maxx, miny, maxx, midy, radius_br );
    CGContextAddArcToPoint( context, maxx, maxy, midx, maxy, radius_tr );
    CGContextAddArcToPoint( context, minx, maxy, minx, midy, radius_tl );
    CGContextClosePath( context );
    CGContextDrawPath( context, kCGPathFill );

    // Create CGImageRef of the main view bitmap content, and then
    // release that bitmap context
    CGImageRef bitmapContext = CGBitmapContextCreateImage( context );
    CGContextRelease( context );

    // convert the finished resized image to a UIImage 
    UIImage *theImage = [UIImage imageWithCGImage:bitmapContext];
    // image is retained by the property setting above, so we can 
    // release the original
    CGImageRelease(bitmapContext);

    // return the image
    return theImage;
}  

Now you just need few lines of code. I put stuff in my viewController viewDidLoadmethod because it's faster but you can use it also in your custom UIViewwith the layoutSubviewsmethod in example.

现在你只需要几行代码。我把东西放在我的 viewControllerviewDidLoad方法中,因为它更快,但你也可以在你的自定义中使用它的例子中UIViewlayoutSubviews方法。



- (void)viewDidLoad {

    // Create the mask image you need calling the previous function
    UIImage *mask = MTDContextCreateRoundedMask( self.view.bounds, 50.0, 50.0, 0.0, 0.0 );
    // Create a new layer that will work as a mask
    CALayer *layerMask = [CALayer layer];
    layerMask.frame = self.view.bounds;       
    // Put the mask image as content of the layer
    layerMask.contents = (id)mask.CGImage;       
    // set the mask layer as mask of the view layer
    self.view.layer.mask = layerMask;              

    // Add a backaground color just to check if it works
    self.view.backgroundColor = [UIColor redColor];
    // Add a test view to verify the correct mask clipping
    UIView *testView = [[UIView alloc] initWithFrame:CGRectMake( 0.0, 0.0, 50.0, 50.0 )];
    testView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:testView];
    [testView release];

    [super viewDidLoad];
}

Solution 2

解决方案2

This solution is a bit more "dirty". Essentially you could create a mask layer with the rounded corner you need (all corners). Then you should increase the height of the mask layer by the value of the corner radius. In this way the bottom rounded corners are hidden and you can only see the upper rounded corner. I put the code just in the viewDidLoadmethod because it's faster but you can use it also in your custom UIViewwith the layoutSubviewsmethod in example.

这个解决方案有点“脏”。本质上,您可以创建一个带有您需要的圆角(所有角)的蒙版层。然后你应该通过角半径的值增加遮罩层的高度。通过这种方式,底部圆角被隐藏,您只能看到上部圆角。我将代码放在viewDidLoad方法中,因为它更快,但您也可以在自定义中UIView使用layoutSubviews示例中的方法。

  

- (void)viewDidLoad {

    // set the radius
    CGFloat radius = 50.0;
    // set the mask frame, and increase the height by the 
    // corner radius to hide bottom corners
    CGRect maskFrame = self.view.bounds;
    maskFrame.size.height += radius;
    // create the mask layer
    CALayer *maskLayer = [CALayer layer];
    maskLayer.cornerRadius = radius;
    maskLayer.backgroundColor = [UIColor blackColor].CGColor;
    maskLayer.frame = maskFrame;

    // set the mask
    self.view.layer.mask = maskLayer;

    // Add a backaground color just to check if it works
    self.view.backgroundColor = [UIColor redColor];
    // Add a test view to verify the correct mask clipping
    UIView *testView = [[UIView alloc] initWithFrame:CGRectMake( 0.0, 0.0, 50.0, 50.0 )];
    testView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:testView];
    [testView release];

    [super viewDidLoad];
}

Hope this helps. Ciao!

希望这可以帮助。再见!

回答by P.L.

Combing through the few answers & comments, I found out that using UIBezierPath bezierPathWithRoundedRectand CAShapeLayerthe simplest and most straight forward way. It might not be appropriate for very complex cases, but for occasional rounding of corners, it works fast and smoothly for me.

梳理了几个答案和评论,我发现了使用UIBezierPath bezierPathWithRoundedRectCAShapeLayer最简单、最直接的方法。它可能不适合非常复杂的情况,但对于偶尔的圆角,它对我来说工作得又快又顺利。

I had created a simplified helper that sets the appropriate corner in the mask:

我创建了一个简化的助手,用于在蒙版中设置适当的角:

-(void) setMaskTo:(UIView*)view byRoundingCorners:(UIRectCorner)corners
{
    UIBezierPath* rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(10.0, 10.0)];

    CAShapeLayer* shape = [[CAShapeLayer alloc] init];
    [shape setPath:rounded.CGPath];

    view.layer.mask = shape;
}

To use it, simply call with the appropriate UIRectCorner enum, e.g.:

要使用它,只需使用适当的 UIRectCorner 枚举调用,例如:

[self setMaskTo:self.photoView byRoundingCorners:UIRectCornerTopLeft|UIRectCornerBottomLeft];

Please note that for me, I use it to round corners of photos in a grouped UITableViewCell, the 10.0 radius works fine for me, if need to just change the value as appropriate.

请注意,对我来说,我用它来圆化分组 UITableViewCell 中照片的角,10.0 半径对我来说很好用,如果需要适当地更改值。

EDIT:just notice a previously answered very similarly as this one (link). You can still use this answer as a added convenience function if needed.

编辑:请注意以前的回答与此非常相似(链接)。如果需要,您仍然可以将此答案用作附加的便利功能。



EDIT:编辑:与 Swift 3 中的 UIView 扩展相同的代码

extension UIView {
    func maskByRoundingCorners(_ masks:UIRectCorner, withRadii radii:CGSize = CGSize(width: 10, height: 10)) {
        let rounded = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: masks, cornerRadii: radii)

        let shape = CAShapeLayer()
        shape.path = rounded.cgPath

        self.layer.mask = shape
    }
}

To use it, simple call maskByRoundingCorneron any UIView:

要使用它,只需调用maskByRoundingCornerany UIView

view.maskByRoundingCorners([.topLeft, .bottomLeft])

回答by Nathan Eror

I couldn't fit this all in a comment to @lomanf's answer. So I'm adding it as an answer.

我无法在对@lomanf 的回答的评论中说明这一切。所以我将其添加为答案。

Like @lomanf said, you need to add a layer mask to prevent sublayers from drawing outside of your path's bounds. It's a lot easier to do now, though. As long as you're targeting iOS 3.2 or higher, you don't need to create an image with quartz and set it as the mask. You can simply create a CAShapeLayerwith a UIBezierPathand use that as the mask.

就像@lomanf 所说的那样,您需要添加一个图层蒙版以防止子图层绘制到路径边界之外。不过,现在做起来容易多了。只要您的目标是 iOS 3.2 或更高版本,您就无需使用石英创建图像并将其设置为遮罩。您可以简单地CAShapeLayer用 a创建一个UIBezierPath并将其用作掩码。

Also, when using layer masks, make sure that the layer you're masking is not part of any layer hierarchy when you add the mask. Otherwise the behavior is undefined. If your view is already in the hierarchy, you need to remove it from its superview, mask it, then put it back where it was.

此外,在使用图层蒙版时,请确保在添加蒙版时您正在蒙版的图层不属于任何图层层次结构。否则行为是未定义的。如果您的视图已经在层次结构中,您需要将其从其超视图中删除,屏蔽它,然后将其放回原来的位置。

CAShapeLayer *maskLayer = [CAShapeLayer layer];
UIBezierPath *roundedPath = 
  [UIBezierPath bezierPathWithRoundedRect:maskLayer.bounds
                        byRoundingCorners:UIRectCornerTopLeft |
                                          UIRectCornerBottomRight
                              cornerRadii:CGSizeMake(16.f, 16.f)];    
maskLayer.fillColor = [[UIColor whiteColor] CGColor];
maskLayer.backgroundColor = [[UIColor clearColor] CGColor];
maskLayer.path = [roundedPath CGPath];

//Don't add masks to layers already in the hierarchy!
UIView *superview = [self.view superview];
[self.view removeFromSuperview];
self.view.layer.mask = maskLayer;
[superview addSubview:self.view];

Due to the way Core Animation rendering works, masking is a relatively slow operation. Each mask requires an extra rendering pass. So use masks sparingly.

由于 Core Animation 渲染的工作方式,遮罩是一个相对较慢的操作。每个蒙版都需要一个额外的渲染通道。因此,请谨慎使用口罩。

One of the best parts of this approach is that you no longer need to create a custom UIViewand override drawRect:. This should make your code simpler, and maybe even faster.

这种方法最好的部分之一是您不再需要创建自定义UIView和覆盖drawRect:。这应该会使您的代码更简单,甚至更快。

回答by FreeAsInBeer

I've taken Nathan's example and created a category on UIViewto allow one to adhere to DRY principles. Without further ado:

我以 Nathan 的例子创建了一个类别,UIView以允许人们遵守 DRY 原则。无需再费周折:

UIView+Roundify.h

UIView+Roundify.h

#import <UIKit/UIKit.h>

@interface UIView (Roundify)

-(void)addRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii;
-(CALayer*)maskForRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii;

@end

UIView+Roundify.m

UIView+Roundify.m

#import "UIView+Roundify.h"

@implementation UIView (Roundify)

-(void)addRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii {
    CALayer *tMaskLayer = [self maskForRoundedCorners:corners withRadii:radii];
    self.layer.mask = tMaskLayer;
}

-(CALayer*)maskForRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii {
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = self.bounds;

    UIBezierPath *roundedPath = [UIBezierPath bezierPathWithRoundedRect:
        maskLayer.bounds byRoundingCorners:corners cornerRadii:radii];
    maskLayer.fillColor = [[UIColor whiteColor] CGColor];
    maskLayer.backgroundColor = [[UIColor clearColor] CGColor];
    maskLayer.path = [roundedPath CGPath];

    return maskLayer;
}

@end

To call:

致电:

[myView addRoundedCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight
                withRadii:CGSizeMake(20.0f, 20.0f)];

回答by Josh Valdivieso

To expand a little on P.L's answer I rewrote the method like so as it wasn't rounding certain objects such as UIButtoncorrectly

为了稍微扩展 PL 的答案,我重写了该方法,因为它没有UIButton正确地舍入某些对象

- (void)setMaskTo:(id)sender byRoundingCorners:(UIRectCorner)corners withCornerRadii:(CGSize)radii
{
    // UIButton requires this
    [sender layer].cornerRadius = 0.0;

    UIBezierPath *shapePath = [UIBezierPath bezierPathWithRoundedRect:[sender bounds]
                                                byRoundingCorners:corners
                                                cornerRadii:radii];

    CAShapeLayer *newCornerLayer = [CAShapeLayer layer];
    newCornerLayer.frame = [sender bounds];
    newCornerLayer.path = shapePath.CGPath;
    [sender layer].mask = newCornerLayer;
}

And call it by

并调用它

[self setMaskTo:self.continueButton byRoundingCorners:UIRectCornerBottomLeft|UIRectCornerBottomRight withCornerRadii:CGSizeMake(3.0, 3.0)];

回答by Kevin Delord

If you want to do it in Swift you could use an extension of a UIView. By doing so, all subclasses will be able to use the following method:

如果你想在 Swift 中做到这一点,你可以使用UIView. 通过这样做,所有子类都将能够使用以下方法:

import QuartzCore

extension UIView {
    func roundCorner(corners: UIRectCorner, radius: CGFloat) {
        let maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let maskLayer = CAShapeLayer()
        maskLayer.frame = bounds
        maskLayer.path = maskPath.CGPath
        layer.mask = maskLayer
    }
}    

Example usage:

用法示例:

self.anImageView.roundCorner(.topRight, radius: 10)

回答by hasan

All the solutions provided achieves the goal. But, UIConstraintscan blow this up sometimes.

提供的所有解决方案都达到了目标。但是,UIConstraints有时会炸毁它。

For example, the bottom corners needs to be rounded. If height or bottom spacing constraint are set to the UIView that needs to be rounded, the code snippets that rounds the corners needs to be moved to viewDidLayoutSubviewsmethod.

例如,底角需要倒圆。如果对需要圆角的 UIView 设置了高度或底部间距约束,则需要将圆角的代码片段移至 viewDidLayoutSubviews方法。

Highlighting:

突出显示:

UIBezierPath *maskPath = [UIBezierPath
    bezierPathWithRoundedRect:roundedView.bounds byRoundingCorners:
    (UIRectCornerTopRight | UIRectCornerBottomRight) cornerRadii:CGSizeMake(16.0, 16.0)];

The code snippet above will only round the top right corner if this code set in viewDidLoad. Because roundedView.boundsis going to change after the constraints updates the UIView.

如果此代码设置在viewDidLoad. 因为roundedView.bounds在约束更新UIView.

回答by Umang

UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(5, 5, self.bounds.size.width-10, self.bounds.size.height-10)
                                               byRoundingCorners:UIRectCornerAllCorners
                                                     cornerRadii:CGSizeMake(12.0, 12.0)];

change "AllCorners"according to your need.

根据您的需要更改“AllCorners”

回答by Rizwan Ahmed

Extending the accepted answer, let us add backward compatibility to it. Prior to iOS 11, view.layer.maskedCorners is not available. So we can do like this

扩展已接受的答案,让我们为其添加向后兼容性。在 iOS 11 之前,view.layer.maskedCorners 不可用。所以我们可以这样做

if #available(iOS 11.0, *) {
    myView.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]
} else {
    myView.maskByRoundingCorners([.topLeft, .topRight])
}

extension UIView{
    func maskByRoundingCorners(_ masks:UIRectCorner, withRadii radii:CGSize = CGSize(width: 10, height: 10)) {
        let rounded = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: masks, cornerRadii: radii)

        let shape = CAShapeLayer()
        shape.path = rounded.cgPath

        self.layer.mask = shape
    }
}

We have written maskByRoundingCorners as an UIView extension so that it improves code reuse.

我们将 maskByRoundingCorners 编写为 UIView 扩展,以提高代码重用性。

Credits to @SachinVsSachin and @P.L :) I have combined their codes to make it better.

感谢@SachinVsSachin 和@PL :) 我结合了他们的代码以使其更好。