使用CALayers的圆形UIView-仅在某些角落-如何?


121

在我的应用程序中-有四个按钮,名称如下:

  • 左上方
  • 左下方
  • 右上
  • 底部-右

在按钮上方有一个图像视图(或UIView)。

现在,假设用户点击-顶部-左按钮。上方的图片/视图应在该特定角处四舍五入。

我在将圆角应用于UIView时遇到了一些困难。

现在,我正在使用以下代码将圆角应用于每个视图:

    // imgVUserImg is a image view on IB.
    imgVUserImg.image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"any Url Here"];
    CALayer *l = [imgVUserImg layer];
    [l setMasksToBounds:YES];
    [l setCornerRadius:5.0];  
    [l setBorderWidth:2.0];
    [l setBorderColor:[[UIColor darkGrayColor] CGColor]];

上面的代码将圆度应用于提供的View的每个角。相反,我只是想将圆度应用于选定的角,例如-顶部/顶部+左侧/底部+右侧等。

可能吗?怎么样?

Answers:


44

我在“ 如何在iPhone上创建圆角UILabel?”中使用了答案以及如何在iPhone上完成具有透明度的圆角矩形视图中的代码编写此代码。

然后我意识到我回答了错误的问题(给了一个四舍五入的UILabel而不是UIImage),所以我用下面的代码对其进行了更改:

http://discussions.apple.com/thread.jspa?threadID=1683876

使用“视图”模板制作一个iPhone项目。在视图控制器中,添加以下内容:

- (void)viewDidLoad
{
    CGRect rect = CGRectMake(10, 10, 200, 100);
    MyView *myView = [[MyView alloc] initWithFrame:rect];
    [self.view addSubview:myView];
    [super viewDidLoad];
}

MyView只是一个UIImageView子类:

@interface MyView : UIImageView
{
}

我以前从未使用过图形上下文,但是设法将这段代码整合在一起。它缺少两个角落的代码。如果您阅读该代码,则可以看到我的实现方式(通过删除一些CGContextAddArc调用,并删除代码中的一些半径值。所有拐角处的代码都在那儿,因此将其用作起点并删除不需要角的零件。请注意,如果需要,也可以制作带有2个或3个圆角的矩形。

该代码并不完美,但是我敢肯定您可以对其进行整理。

static void addRoundedRectToPath(CGContextRef context, CGRect rect, float radius, int roundedCornerPosition)
{

    // all corners rounded
    //  CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius);
    //  CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius);
    //  CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius, 
    //                  radius, M_PI / 4, M_PI / 2, 1);
    //  CGContextAddLineToPoint(context, rect.origin.x + rect.size.width - radius, 
    //                          rect.origin.y + rect.size.height);
    //  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 + radius);
    //  CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + radius, 
    //                  radius, 0.0f, -M_PI / 2, 1);
    //  CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y);
    //  CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius, 
    //                  -M_PI / 2, M_PI, 1);

    // top left
    if (roundedCornerPosition == 1) {
        CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius);
        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius);
        CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius, 
                        radius, M_PI / 4, M_PI / 2, 1);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, 
                                rect.origin.y + rect.size.height);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y);
    }   

    // bottom left
    if (roundedCornerPosition == 2) {
        CGContextMoveToPoint(context, rect.origin.x, rect.origin.y);
        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, 
                                rect.origin.y + rect.size.height);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
        CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y);
        CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius, 
                        -M_PI / 2, M_PI, 1);
    }

    // add the other corners here


    CGContextClosePath(context);
    CGContextRestoreGState(context);
}


-(UIImage *)setImage
{
    UIImage *img = [UIImage imageNamed:@"my_image.png"];
    int w = img.size.width;
    int h = img.size.height;

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);

    CGContextBeginPath(context);
    CGRect rect = CGRectMake(0, 0, w, h);


    addRoundedRectToPath(context, rect, 50, 1);
    CGContextClosePath(context);
    CGContextClip(context);

    CGContextDrawImage(context, rect, img.CGImage);

    CGImageRef imageMasked = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    [img release];

    return [UIImage imageWithCGImage:imageMasked];
}

替代文字http://nevan.net/skitch/skitched-20100224-092237.png

不要忘记,您将需要在其中获得QuartzCore框架才能起作用。


尺寸可能会更改的视图无法正常工作。示例:UILabel。设置遮罩路径,然后通过将其设置为文本来更改其大小,将保留旧的遮罩。将需要子类化并重载drawRect。
user3099609

358

从iOS 3.2开始,您可以使用的功能UIBezierPath创建开箱即用的圆角矩形(其中仅对您指定的角进行圆角处理)。然后,您可以将其用作的路径CAShapeLayer,并将其用作视图图层的蒙版:

// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds 
                                               byRoundingCorners:UIRectCornerTopLeft
                                                     cornerRadii:CGSizeMake(10.0, 10.0)];

// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imageView.bounds;
maskLayer.path = maskPath.CGPath;

// Set the newly created shape layer as the mask for the image view's layer
imageView.layer.mask = maskLayer;

就是这样-不会在Core Graphics中手动定义形状,也不会在Photoshop中创建遮罩图像。该层甚至不需要失效。应用圆角或更改为新角非常简单,只需定义一个新角UIBezierPath并将其CGPath用作遮罩层的路径即可。该方法的corners参数bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:是位掩码,因此可以通过对它们进行“或”运算来对多个角进行圆角处理。


编辑:添加阴影

如果您要为此添加阴影,则需要做更多的工作。

由于“ imageView.layer.mask = maskLayer”应用了遮罩,因此通常不会在其外部显示阴影。诀窍是使用透明视图,然后CALayer在视图层中添加两个子层:shadowLayerroundedLayer。两者都需要利用UIBezierPath。该图像将作为的内容添加roundedLayer

// Create a transparent view
UIView *theView = [[UIView alloc] initWithFrame:theFrame];
[theView setBackgroundColor:[UIColor clearColor]];

// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:theView.bounds 
                                               byRoundingCorners:UIRectCornerTopLeft
                                                     cornerRadii:CGSizeMake(10.0f, 10.0f)];

// Create the shadow layer
CAShapeLayer *shadowLayer = [CAShapeLayer layer];
[shadowLayer setFrame:theView.bounds];
[shadowLayer setMasksToBounds:NO];
[shadowLayer setShadowPath:maskPath.CGPath];
// ...
// Set the shadowColor, shadowOffset, shadowOpacity & shadowRadius as required
// ...

// Create the rounded layer, and mask it using the rounded mask layer
CALayer *roundedLayer = [CALayer layer];
[roundedLayer setFrame:theView.bounds];
[roundedLayer setContents:(id)theImage.CGImage];

CAShapeLayer *maskLayer = [CAShapeLayer layer];
[maskLayer setFrame:theView.bounds];
[maskLayer setPath:maskPath.CGPath];

roundedLayer.mask = maskLayer;

// Add these two layers as sublayers to the view
[theView.layer addSublayer:shadowLayer];
[theView.layer addSublayer:roundedLayer];

1
不错,这对分组UITableView单元格的selectedBackgroundViews
很有帮助

无论如何,在圆角之后向该视图添加阴影?尝试以下操作没有成功。`self.layer.masksToBounds = NO; self.layer.shadowColor = [UIColor blackColor] .CGColor; self.layer.shadowRadius = 3; self.layer.shadowOffset = CGSizeMake(-3,3); self.layer.shadowOpacity = 0.8; self.layer.shouldRasterize = YES; `
克里斯·瓦格纳

1
@ChrisWagner:关于将阴影应用于圆形视图,请参见我的编辑。
Stuart

@StuDev太好了!我已经有了用于阴影视图的子视图,但这要好得多!没想到要添加这样的子图层。谢谢!
克里斯·瓦格纳

为什么我的图像变成白色,然后滚动UITableView并重新创建单元格-它起作用了!为什么?
费尔南多·雷东多

18

我在代码中的许多地方都使用过此代码,它可以100%正确地工作。您可以通过更改一个属性“ byRoundingCorners:UIRectCornerBottomLeft”来更改任何编码器

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:UIRectCornerBottomLeft cornerRadii:CGSizeMake(10.0, 10.0)];

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

在子UITableView视图(例如UITableViewCells或UITableViewHeaderFooterViews)上使用此解决方案时,滚动时导致平滑度变差 。这种使用的另一种方法是具有更好性能的解决方案(它在所有角上都增加了一个cornerRadius)。为了仅显示特定的圆角(如右上角和右下角),我在其上添加了一个带有负值的子视图,frame.origin.x并将cornerRadius分配给了该图层。如果有人找到更好的解决方案,我很感兴趣。
anneblue 2014年

11

在iOS 11中,我们现在只能将某些角落

let view = UIView()

view.clipsToBounds = true
view.layer.cornerRadius = 8
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]

7

带有Swift 3+语法的CALayer扩展

extension CALayer {

    func round(roundedRect rect: CGRect, byRoundingCorners corners: UIRectCorner, cornerRadii: CGSize) -> Void {
        let bp = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: cornerRadii)
        let sl = CAShapeLayer()
        sl.frame = self.bounds
        sl.path = bp.cgPath
        self.mask = sl
    }
}

可以像这样使用:

let layer: CALayer = yourView.layer
layer.round(roundedRect: yourView.bounds, byRoundingCorners: [.bottomLeft, .topLeft], cornerRadii: CGSize(width: 5, height: 5))

5

斯图尔特示例中的特定角圆角效果很好。如果您想在左上角和右上角等多个角处转弯,这就是方法

// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageview
                                               byRoundingCorners:UIRectCornerTopLeft|UIRectCornerTopRight
                                                     cornerRadii:CGSizeMake(10.0, 10.0)];

// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imageview.bounds;
maskLayer.path = maskPath.CGPath;

// Set the newly created shape layer as the mask for the image view's layer
imageview.layer.mask = maskLayer; 

在子UITableView视图(例如UITableViewCells或UITableViewHeaderFooterViews)上使用此解决方案时,滚动时导致平滑度变差 。这种使用的另一种方法是具有更好性能的解决方案(它在所有角上都增加了一个cornerRadius)。为了仅显示特定的圆角(如右上角和右下角),我在其上添加了一个带有负值的子视图,frame.origin.x并将cornerRadius分配给了该图层。如果有人找到更好的解决方案,我很感兴趣。
anneblue 2014年

5

感谢分享。在这里,我想分享swift 2.0上的解决方案,以供对此问题进一步参考。(以符合UIRectCorner的协议)

let mp = UIBezierPath(roundedRect: cell.bounds, byRoundingCorners: [.bottomLeft, .TopLeft], cornerRadii: CGSize(width: 10, height: 10))
let ml = CAShapeLayer()
ml.frame = self.bounds
ml.path = mp.CGPath
self.layer.mask = ml

4

有一个更简单,更快速的答案可能会根据您的需要而起作用,也可以与阴影配合使用。您可以将超级图层上的maskToBounds设置为true,并偏移子图层,使它们的两个角位于超级图层边界之外,从而有效地将两侧的圆角切掉。

当然,这仅在您只想在同一侧上有两个圆角并且从一侧切下几个像素时该图层的内容看起来相同时才起作用。非常适合将条形图仅在顶部四舍五入。


3

请参阅此相关问题。您必须将自己的矩形绘制为CGPath带有一些圆角的,将其添加CGPath到中CGContext,然后使用裁剪到该矩形CGContextClip

您还可以将带有alpha值的圆角矩形绘制到图像上,然后使用该图像创建一个新图层,将该图层设置为图层的mask属性(请参阅Apple的文档)。


2

迟到了半年,但我认为人们目前的做法并非100%正确。许多人遇到的问题是,使用UIBezierPath + CAShapeLayer方法会干扰自动布局,尤其是在情节提要上设置自动布局时。没有答案可以解决,所以我决定添加自己的答案。

有一个非常简单的方法可以避免此问题:在drawRect(rect: CGRect)函数中绘制圆角。

例如,如果我想要UIView的上圆角,则可以对UIView进行子类化,然后在适当的地方使用该子类。

import UIKit

class TopRoundedView: UIView {

    override func drawRect(rect: CGRect) {
        super.drawRect(rect)

        var maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: UIRectCorner.TopLeft | UIRectCorner.TopRight, cornerRadii: CGSizeMake(5.0, 5.0))

        var maskLayer = CAShapeLayer()
        maskLayer.frame = self.bounds
        maskLayer.path = maskPath.CGPath

        self.layer.mask = maskLayer
    }
}

这是解决问题的最佳方法,完全不需要花费任何时间来适应。


1
这不是解决问题的正确方法。仅drawRect(_:)当在视图中进行自定义绘图(例如使用Core Graphics)时才应覆盖,否则,即使是空的实现也会影响性能。每次都不需要创建新的遮罩层。您实际需要做的就是path在视图范围更改时更新遮罩层的属性,并且该操作的位置在layoutSubviews()方法的覆盖范围之内。
斯图尔特

2

自动调整大小或自动布局时,仅圆角化效果不好。

因此,另一种选择是使用常规cornerRadius并在其他视图下或其超级视图范围之外隐藏不需要的角,以确保将其设置为剪切其内容。


1

为了增加对答案另外,我创建了一个简单的,可重复使用UIView的斯威夫特。根据您的用例,您可能需要进行修改(避免在每个布局上创建对象等),但是我想使其尽可能简单。UIImageView如果您不喜欢子类化,则扩展名可让您更轻松地将其应用于其他视图(例如)。

extension UIView {

    func roundCorners(_ roundedCorners: UIRectCorner, toRadius radius: CGFloat) {
        roundCorners(roundedCorners, toRadii: CGSize(width: radius, height: radius))
    }

    func roundCorners(_ roundedCorners: UIRectCorner, toRadii cornerRadii: CGSize) {
        let maskBezierPath = UIBezierPath(
            roundedRect: bounds,
            byRoundingCorners: roundedCorners,
            cornerRadii: cornerRadii)
        let maskShapeLayer = CAShapeLayer()
        maskShapeLayer.frame = bounds
        maskShapeLayer.path = maskBezierPath.cgPath
        layer.mask = maskShapeLayer
    }
}

class RoundedCornerView: UIView {

    var roundedCorners: UIRectCorner = UIRectCorner.allCorners
    var roundedCornerRadii: CGSize = CGSize(width: 10.0, height: 10.0)

    override func layoutSubviews() {
        super.layoutSubviews()
        roundCorners(roundedCorners, toRadii: roundedCornerRadii)
    }
}

这是将其应用于的方法UIViewController

class MyViewController: UIViewController {

    private var _view: RoundedCornerView {
        return view as! RoundedCornerView
    }

    override func loadView() {
        view = RoundedCornerView()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        _view.roundedCorners = [.topLeft, .topRight]
        _view.roundedCornerRadii = CGSize(width: 10.0, height: 10.0)
    }
}

0

总结Stuart的答案,您可以使用圆角方法,如下所示:

@implementation UIView (RoundCorners)

- (void)applyRoundCorners:(UIRectCorner)corners radius:(CGFloat)radius {
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)];

    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = self.bounds;
    maskLayer.path = maskPath.CGPath;

    self.layer.mask = maskLayer;
}

@end

因此,要应用圆角,只需执行以下操作:

[self.imageView applyRoundCorners:UIRectCornerTopRight|UIRectCornerTopLeft radius:10];

0

我建议定义一个图层的蒙版。遮罩本身应该是CAShapeLayer具有专用路径的对象。您可以使用下一个UIView扩展(Swift 4.2):

extension UIView {
    func round(corners: UIRectCorner, with radius: CGFloat) {
        let maskLayer = CAShapeLayer()
        maskLayer.frame = bounds
        maskLayer.path = UIBezierPath(
            roundedRect: bounds,
            byRoundingCorners: corners,
            cornerRadii: CGSize(width: radius, height: radius)
        ).cgPath
        layer.mask = maskLayer
   }
}
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.