首页 文章

UIView的两个角落

提问于
浏览
71

不久之前,我发布了一个关于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 回答

  • 1

    iOS 11中引入了CACornerMask,它有助于在视图层中定义topleft,topright,bottomleft,bottom right . 以下是使用示例 .

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

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

    提前致谢 .

    仅供参考文献:
    enter image description here

  • 1

    据我所知,如果你还需要掩盖子视图,你可以使用 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

    这个解决方案多一点"dirty" . 基本上你可以用你需要的圆角创建一个遮罩层(所有角落) . 然后,您应该通过角半径的值增加遮罩层的高度 . 通过这种方式,底部圆角被隐藏,您只能看到上圆角 . 我把代码放在 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];
    }
    

    希望这可以帮助 . 再见!

  • 56

    通过几个答案和评论,我发现使用 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半径适合我,如果需要只是根据需要更改值 .

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


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

    要使用它,只需在 UIView 上调用 maskByRoundingCorner

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

    在评论@ lomanf的答案时,我无法满足这一切 . 所以我将其添加为答案 .

    就像@lomanf说的那样,你需要添加一个图层蒙版来防止子图层在你的路径之外绘制's bounds. It'现在要容易得多了 . 只要您需要使用石英创建图像并将其设置为蒙版 . 您可以使用 UIBezierPath 创建 CAShapeLayer 并将其用作蒙版 .

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

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

  • 2

    我是've taken 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)];
    
  • 1

    要在P.L 's answer I rewrote the method like so as it wasn' t上稍微扩展某些对象,例如 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)];
    
  • 67

    如果你想在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)
    
  • 23
    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" .

  • 1

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

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

    Highlighting:

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

    如果此代码在 viewDidLoad 中设置,则上面的代码段将仅围绕右上角 . 因为 roundedView.bounds 将在约束更新 UIView 后更改 .

  • 2

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

  • 1

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

    我'm not sure if this is the sort of result you'之后 . 值得注意的是,如果/当系统再次调用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);    
    }
    
  • -1

    Bezier路径是anwer,如果你需要额外的代码,这个为我工作: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];
    
  • 0

    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

    扩展接受的答案,让我们添加向后兼容性 . 在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和@ P.L :)我已经将他们的代码组合起来以使其更好 .

  • 72

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

    • clipsToBounds必须设置为YES

    • 不透明必须是NO

    • backgroundColor应为"clearColor"(我不完全确定)

    • contentMode必须是"UIContentModeRedraw",因为如果没有,则不经常调用drawRect
      必须在CGContextClip之后调用

    • [super drawRect:rect]

    • 您的视图可能不包含任意子视图(不确定)

    • 务必至少设置一次"needsDisplay:"以触发您的画笔

  • 30

    一个稍微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];
    

    不支持您希望两个对角相对的圆角圆滑的情况 .

  • 0

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

    self.tableView.layer.cornerRadius = 10;
    

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

    我希望这有帮助!

    enter image description here

  • 49

    你可能需要剪辑到边界 . 添加行

    self.clipsToBounds = YES
    

    在代码中的某个地方设置该属性 .

相关问题