UIView的两个角落

时间:2011-01-31 02:35:48

标签: ios objective-c iphone rounded-corners

不久之前,我发布了一个关于rounding just two corners of a view的问题,得到了很好的回复,但是在实现它时遇到了问题。这是我的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);
}

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

18 个答案:

答案 0 :(得分: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;
}  

现在你只需要几行代码。我把东西放在我的viewController viewDidLoad方法中,因为它更快,但您也可以在自定义UIView中使用它,并在示例中使用layoutSubviews方法。



- (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方法中,因为它更快,但您也可以在自定义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];
}

希望这会有所帮助。侨!

答案 1 :(得分:68)

梳理几个答案&amp;评论,我发现使用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半径对我来说很合适,如果需要只是根据需要更改值。

编辑:只是注意到之前的答案非常类似于此(link)。如果需要,您仍然可以将此答案用作附加的便利功能。

<小时/> 编辑:与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])

答案 2 :(得分:58)

iOS 11中引入了

CACornerMask,它有助于在视图层中定义topleft,topright,bottomleft,bottom right。以下是使用示例。

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

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

提前致谢。

FYI Ref:enter image description here

答案 3 :(得分:56)

对于@ 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:。这应该使您的代码更简单,甚至更快。

答案 4 :(得分: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)];

答案 5 :(得分:24)

为了扩展P.L的答案我改写了方法,因为它没有正确地舍入某些对象,例如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)];

答案 6 :(得分:11)

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

答案 7 :(得分: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”

答案 8 :(得分:2)

所提供的所有解决方案都实现了目标。但是,UIConstraints有时可能会打击它。

  

例如,底角需要圆角。如果高度或   底部间距约束设置为需要舍入的UIView,   圆角的代码片段需要移动到   viewDidLayoutSubviews方法。

<强>突出:

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

如果此代码在viewDidLoad中设置,则上面的代码段仅围绕右上角。因为roundedView.bounds会在约束更新UIView之后发生变化。

答案 9 :(得分:1)

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

  <Swiper height={height} showsButtons showsPagination={false}>
    {this.props.features.map((feature) => <SwiperSlider imageSource={feature.featureImage} />)}
  </Swiper>

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

致@SachinVsSachin和@ P.L :)我已将他们的代码组合起来以使其更好。

答案 10 :(得分: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];
}

答案 11 :(得分:1)

Bezier路径就是答案,如果你需要额外的代码,这个代码对我有用: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];

答案 12 :(得分:1)

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

我不确定这是否是你所追求的那种结果。值得注意的是,如果/当系统再次调用drawRect:再次要求重绘部分rect时,这将表现得非常奇怪。如上所述,Nevan's approach可能是更好的方法。

 // 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);    
}

答案 13 :(得分:1)

创建一个遮罩并将其设置在视图的图层

答案 14 :(得分:0)

略微hacky,但相对简单(没有子类化,屏蔽等)的方式是有两个UIViews。都是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];

不支持您希望两个对角相对的圆角变圆的情况。

答案 15 :(得分:0)

我意识到你正试图绕过UITableView的前两个角,但出于某种原因我发现最好的解决方案是使用:

self.tableView.layer.cornerRadius = 10;

以编程方式它应该围绕所有四个角落,但由于某种原因它只围绕前两个。 **请参阅下面的屏幕截图,看看我上面编写的代码的效果。

我希望这有帮助!

enter image description here

答案 16 :(得分:0)

这只有在某些事情设置正确的情况下才有效:

  1. clipsToBounds必须设置为YES
  2. opaque必须为NO
  3. backgroundColor应为“clearColor”(我对此不完全确定)
  4. contentMode必须是“UIContentModeRedraw”,因为如果不是,则不经常调用drawRect
  5. 必须在CGContextClip 之后调用
  6. [super drawRect:rect]
  7. 您的观点可能不包含任意子视图(对此不确定)
  8. 请务必至少设置一次“needsDisplay:”以触发您的直接

答案 17 :(得分:-1)

你可能需要剪辑到界限。添加行

self.clipsToBounds = YES

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