圆角 UIView 使用 CALayers-只有一些角-如何?

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

  • 左上角
  • 左下角
  • 右上角
  • 右下角

按钮上方有一个图像视图(或 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]];

上面的代码将圆度应用到提供的视图的每个角上。相反,我只是想应用圆度到选定的角落,如-top/top + left/bottom + right 等。

有可能吗,怎么可能?

66941 次浏览

这个相关的问题。您必须绘制自己的矩形到 CGPath一些圆角,添加到您的 CGContextCGPath,然后剪辑到它使用 CGContextClip

您也可以绘制圆角直线与阿尔法值的图像,然后使用该图像创建一个新的层,您设置为您的层的 mask属性(请参阅苹果的文档)。

我使用了 How do I create a round cornered UILabel on the iPhone?的答案和 如何在 iphone 上实现透明的圆角正视图?的代码来编写这段代码。

然后我意识到我回答了错误的问题(给了一个圆形的 UILabel 而不是 UIImage) ,所以我用这段代码来改变它:

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

使用 View 模板创建一个 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
{
}

I'd never used graphics contexts before, but I managed to hobble together this code. It's missing the code for two of the corners. If you read the code, you can see how I implemented this (by deleting some of the CGContextAddArc calls, and deleting some of the radius values in the code. The code for all corners is there, so use that as a starting point and delete the parts that create corners you don't need. Note that you can make rectangles with 2 or 3 rounded corners too if you want.

代码并不完美,但我相信你可以稍微整理一下。

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 框架,这样才能工作。

Starting in iOS 3.2, you can use the functionality of UIBezierPaths to create an out-of-the-box rounded rect (where only corners you specify are rounded). You can then use this as the path of a CAShapeLayer, and use this as a mask for your view's layer:

// 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作为掩码层的路径一样简单。bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:方法的 corners参数是一个位掩码,因此可以通过将它们放在一起来舍入多个角点。


编辑: 添加阴影

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

因为“ 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];

我已经在我的代码中的许多地方使用了这个代码,它的工作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];

有一个更容易和更快的答案,可能工作取决于您的需要,也与阴影工作。您可以将叠加层上的 maskToBound 设置为 true,然后偏移子层,使其中的两个角落位于叠加层边界之外,从而有效地将两侧的圆角切除。

of course this only works when you want to have only 2 rounded corners on the same side and the content of the layer looks the same when you cut off a few pixels from one side. works great for having bar charts rounded only on the top side.

Stuarts example for rounding specific corners works great. If you want to round multiple corners like top left and right this is how to do it

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

只舍入一些角不会发挥自动调整大小或自动布局不错。

因此,另一种选择是使用常规的 cornerRadius,并隐藏您不希望在另一个视图下或在其超视图界限之外的角落,以确保它被设置为剪辑其内容。

总结斯图尔特的答案,你可以采用如下的圆角法:

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

虽然晚了五年,但我认为现在人们做这件事的方式并不是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
}
}

这是解决问题的最好方法,而且根本不需要花任何时间去适应。

Thanks for sharing. Here I'd like to share the solution on swift 2.0 for further reference on this issue. (to conform the UIRectCorner's protocol)

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

CALayer extension with Swift 3 + syntax

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

为了添加到 回答加法中,我在 Swift 中创建了一个简单的、可重用的 UIView。根据您的用例,您可能希望进行修改(避免在每个布局上创建对象等) ,但我希望尽可能简单。扩展允许您将此应用于其他视图的(例如。如果你不喜欢子类化,那么更容易。

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

In iOS 11, we can now round some corners only

let view = UIView()


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

我建议定义一个层次的面具。掩码本身应该是一个具有专用路径的 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
}
}