在UIView中的两个角


82

不久前,我发布了一个关于仅将视图的两个角变圆的问题,并得到了很好的答复,但是在实现它时遇到了问题。这是我的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);
}

该方法正在被调用,但似乎并不影响视图的结果。有什么想法吗?

Answers:


103

iOS 11中引入的CACornerMask可帮助在视图层中定义左上,右上,左下,右下。以下是使用示例。

在这里,我尝试仅舍入两个上角:

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

仅供参考:


这看起来像是现在惯用的方式。接得好!
Jumhyn

2
注意,视图的宽度/高度仍至少应为角半径的两倍,否则会出现奇怪的伪像。例如,仅将底角修圆,半径为20且高度仅为30会导致单元格明显变窄(一个像素或两个像素)。
Graham Perks

请注意,如果您的视图更改其框架,则这是正确的方法(可能由于UITextView的扩展,在我的情况下-stackoverflow.com/questions/50926215/…)。如果设置了UIBezierPath,则在框架更改时不会自动更新。
thomers

注意,它仅在iOS 11及更高版本中可用if #available(iOS 11.0, *) { }
Tri Ngo Minh,

2
好答案。原来不需要设置clipsToBoundstrue。您可以掩盖角落,当添加阴影图层clipsToBoundsfalse。为我派上了用场。
Au Ris

72


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

解决方案1

首先,我创建了此函数以动态生成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;
}  

现在,您只需要几行代码。我把东西在我的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];
}

解决方案2

这个解决方案有点“肮脏”。本质上,您可以使用所需的圆角(所有角)创建遮罩层。然后,应通过角半径的值来增加遮罩层的高度。这样,底部的圆角被隐藏了,您只能看到上面的圆角。我把代码放在viewDidLoad方法,因为它的速度更快,但你可以在自定义也用它UIViewlayoutSubviews实例方法。

  

- (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];
}

希望这可以帮助。再见!


终于工作了!非常感谢!一个相对于另一个的优势是什么?
Jumhyn 2011年

无论如何,甚至CAShapeLayer解决方案也是完美的。您可以使用UIBezierPath(需要3.2+)或仅使用CGPath方法(CGPathMoveToPoint,CGPathAddQuadCurveToPoint等)
绘制CGPath

1
从纵向旋转到横向旋转后,如何使蒙版自动调整大小?
chourobin

不要忘记添加layer.masksToBounds = YES
Dmitry

如何使它仅绕右上角和右下角?
PLOW

69

通过一些答案和评论,我发现使用UIBezierPath bezierPathWithRoundedRectCAShapeLayer最简单,最直接的方法。它可能不适用于非常复杂的情况,但是对于偶尔的圆角处理,它对我来说快速而流畅。

我创建了一个简化的助手,可以在遮罩中设置适当的角:

-(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;
}

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

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

请注意,对我来说,我用它来对成组的UITableViewCell中的照片进行圆角处理,如果只需要适当地更改值,则10.0半径对我来说很好用。

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


编辑:与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
    }
}

要使用它,只需maskByRoundingCorner在任意位置调用UIView

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

在仍然添加边框的同时有可能吗?
Unome

2
我不得不将函数调用放到DispatchQueue.main.async {}中,因为只有一个角变圆了……
Maksim Kniazev '18年

1
@MaksimKniazev是的,必须从主(UI)线程中调用。此规则适用于任何触摸UI的操作,必须从主线程调用它们,否则可能会发生一些奇怪的事情。
PL

1
似乎只有一个角落。尝试过@MaksimKniazev解决方案也不起作用。
Gustavo_fringe

我有与@Gustavo_fringe相同的问题,在该问题中,右上角没有被裁剪/遮盖到xib视图的内部(调整大小),还不确定如何修复它。
Cyber​​Mew

57

我无法对@lomanf的答案发表评论。因此,我将其添加为答案。

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

另外,在使用图层蒙版时,添加蒙版时,请确保要蒙版的图层不属于任何图层层次结构。否则,行为是不确定的。如果视图已经在层次结构中,则需要将其从其父视图中删除,对其进行遮罩,然后再将其放回原处。

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];

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

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


看起来很有希望,但是视图却没有出现。有什么想法吗?
Jumhyn 2011年

10
您正在使用maskLayer.bounds来调整UIBezierPath的大小,但是边界的值为CGRectZero。您需要添加第二行代码(立即在CAShapeLayer初始化之后),例如maskLayer.frame = self.view.bounds;。
lomanf 2011年

1
你是对的。我从另一个已经存在形状层的示例中提取了该代码。对于那个很抱歉。
内森·埃罗

它完美!如上所述,添加maskLayer.frame = self.view.bounds即可。否则,视图不会显示。
丹尼尔·里贝罗

+1效果很好。实际上,我认为我比lomanf的#2解决方案更喜欢这种方法。这种方法可以轻松地将弧添加到相对的角(例如:右上+左下),并且肯定比lomanf的第一个解决方案要短。内森的好代码!
FreeAsInBeer 2013年

30

我以内森(Nathan)的示例为例,并创建了一个类别,UIView以使其遵守DRY原则。无需再费周折:

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

#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

致电:

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

2
不要忘记在.m文件中导入<QuartzCore / QuatzCore.h>,并将QuartzCore.framework添加到项目中
DEzra 2013年

+1很棒的东西,不过有一个问题,将视图移除/读取为其超级视图的目的是什么?
eladleb

@eladleb:这里的基本代码来自Nathan,我只是对其进行了一些优化。从我完成的研究中,我找不到在应用图层蒙版之前删除视图的正当理由。因此,我相信省略该代码是安全的。
FreeAsInBeer 2013年

@FreeAsInBeer-我同意,然后我将其删除进行投票,这实际上会更改视图的z顺序,并可能产生更多问题
。.– eladleb

解决了在TableView中添加圆角的问题:我添加了页眉,圆角的圆角并添加了带有圆角的页脚。谢谢。
2015年

24

为了扩大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;
}

并称之为

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

12

如果您想在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
    }
}    

用法示例:

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

3

扩展接受的答案,让我们向后添加兼容性。在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
    }
}

我们已经将maskByRoundingCorners编写为UIView扩展,以便改善代码重用。

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


2
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)];

根据您的需要更改“ AllCorners”


所以您必须在UIView中执行此操作还是可以在UIViewController中完成此操作?完成后,如何使用UIBezierPath?谢谢。
StoneBreaker 2012年

2

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

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

突出显示:

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

如果此代码设置为,则上面的代码段将仅在右上角处变圆 viewDidLoad。因为roundedView.bounds约束更新后将会改变UIView



1

从代码开始,您可能会使用下面的代码片段。

我不确定这是否是您追求的结果。同样值得一提的是,如果/当系统调用drawRect:时,再次要求仅重绘rect的一部分,这将表现得很奇怪。上面提到的Nevan的方法可能是更好的方法。

 // make sure the view's background is set to [UIColor clearColor]
- (void)drawRect:(CGRect)rect
{
    CGFloat radius = 10.0;
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextTranslateCTM(context, rect.size.width/2, rect.size.height/2);
    CGContextRotateCTM(context, M_PI); // rotate so image appears right way up
    CGContextTranslateCTM(context, -rect.size.width/2, -rect.size.height/2);

    CGContextBeginPath(context);

    CGContextMoveToPoint(context, rect.origin.x, rect.origin.y);
    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);
    CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
    CGContextClip(context); 

    // now do your drawing, e.g. draw an image
    CGImageRef anImage = [[UIImage imageNamed:@"image.jpg"] CGImage];
    CGContextDrawImage(context, rect, anImage);    
}

我制定了一个带有圆角的视图,但是我无法使其修剪任何子视图。如果我在视图的边缘放置一个带有直角的视图,则它仍然会覆盖其视图的圆角。知道为什么会这样吗?
Jumhyn 2011年

因为剪切-drawRect:只会影响您正在绘制的视图。完成绘制后,其子视图将独立绘制。
乔纳森·格林斯潘

1

一个稍微有点怪异但相对简单(没有子类化,遮罩等)的方法是拥有两个UIView。两者都有clipToBounds = YES。在子视图上设置圆角,然后将其放置在父视图内,以便裁切想要的直角。

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];

不支持您要对角线的两个对角圆角的情况。


1

贝塞尔曲线路径是答案,如果您需要其他代码,则该代码对我有用:https ://stackoverflow.com/a/13163693/936957

UIBezierPath *maskPath;
maskPath = [UIBezierPath bezierPathWithRoundedRect:_backgroundImageView.bounds 
                                 byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight) 
                                       cornerRadii:CGSizeMake(3.0, 3.0)];

CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
_backgroundImageView.layer.mask = maskLayer;
[maskLayer release];

1

UIBezierPath解决方案。

- (void) drawRect:(CGRect)rect {

    [super drawRect:rect];

    //Create shape which we will draw. 
    CGRect rectangle = CGRectMake(2, 
                                  2, 
                                  rect.size.width - 4,
                                  rect.size.height - 4);

    //Create BezierPath with round corners
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rectangle 
                                                   byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight 
                                                         cornerRadii:CGSizeMake(10.0, 10.0)];

    //Set path width
    [maskPath setLineWidth:2];

    //Set color
    [[UIColor redColor] setStroke];

    //Draw BezierPath to see it
    [maskPath stroke];
}

0

仅当某些设置正确时,这才起作用:

  1. clipsToBounds必须设置为YES
  2. 不透明必须为否
  3. backgroundColor应该是“ clearColor”(对此我不太确定)
  4. contentMode必须为“ UIContentModeRedraw”,因为drawRect经常不调用
  5. 在CGContextClip之后必须调用[super drawRect:rect]
  6. 您的视图可能不包含任意子视图(对此不确定)
  7. 确保至少设置一次“ needsDisplay:”以触发drawrect

0

我意识到您正在尝试将UITableView的顶部两个角弄圆,但是由于某种原因,我发现最好的解决方案是使用:

self.tableView.layer.cornerRadius = 10;

以编程方式应将所有四个角都圆满,但由于某种原因,它只能将前两个角四舍五入。**请查看下面的屏幕截图,以查看我上面编写的代码的效果。

我希望这有帮助!

在此处输入图片说明


1
如果不是圆角的全部,UITableView那么在其他地方必须有代码使UITableView底部没有圆角,或者甚至覆盖底部的aUIView或。UILabelUITableView
Josh Valdivieso

-1

您可能必须限制边界。添加行

self.clipsToBounds = YES

在代码中的某处设置该属性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.