ios UIView 中的两个圆角
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4847163/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Round two corners in UIView
提问by Jumhyn
A little while ago I posted a question about rounding just two corners of a view, and got a great response, but am having problems implementing it. Here is my drawRect: method:
不久前,我发布了一个关于将视图的两个角四舍五入的问题,得到了很好的回应,但在实施时遇到了问题。这是我的 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);
}
The method is being called, but doesn't seem to affect the outcome of the view. Any ideas why?
正在调用该方法,但似乎不会影响视图的结果。任何想法为什么?
回答by SachinVsSachin
CACornerMaskintroduced in iOS 11, which help to define topleft, topright, bottomleft, bottom right in view layer. Below is example to use.
CACornerMask在 iOS 11 中引入,有助于在视图层中定义左上、右上、左下、右下。下面是使用示例。
Here I try to rounded only two top corner:
在这里,我尝试只对两个顶角进行四舍五入:
myView.clipsToBounds = true
myView.layer.cornerRadius = 10
myView.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]
FYI Ref:
仅供参考:
回答by lomanf
as far as I know, if you also need to mask the subviews, you could use CALayer
masking. There are 2 ways to do this. The first one is a bit more elegant, the second one is a workaround :-) but it's also fast. Both are based on CALayer
masking. I've used both methods in a couple of projects last year then I hope you can find something useful.
据我所知,如果您还需要屏蔽子视图,则可以使用CALayer
屏蔽。有两种方法可以做到这一点。第一个更优雅,第二个是一种解决方法:-)但它也很快。两者都基于CALayer
掩码。去年我在几个项目中使用了这两种方法,希望你能找到一些有用的东西。
Solution 1
解决方案1
First of all, I created this function to generate an image mask on the fly (UIImage
) with the rounded corner I need. This function essentially needs 5 parameters: the bounds of the image and 4 corner radius (top-left, top-right, bottom-left and bottom-right).
首先,我创建了这个函数来动态生成一个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;
}
Now you just need few lines of code. I put stuff in my viewController viewDidLoad
method because it's faster but you can use it also in your custom UIView
with the layoutSubviews
method in example.
现在你只需要几行代码。我把东西放在我的 viewControllerviewDidLoad
方法中,因为它更快,但你也可以在你的自定义中使用它的例子中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];
}
Solution 2
解决方案2
This solution is a bit more "dirty". Essentially you could create a mask layer with the rounded corner you need (all corners). Then you should increase the height of the mask layer by the value of the corner radius. In this way the bottom rounded corners are hidden and you can only see the upper rounded corner. I put the code just in the viewDidLoad
method because it's faster but you can use it also in your custom UIView
with the layoutSubviews
method in example.
这个解决方案有点“脏”。本质上,您可以创建一个带有您需要的圆角(所有角)的蒙版层。然后你应该通过角半径的值增加遮罩层的高度。通过这种方式,底部圆角被隐藏,您只能看到上部圆角。我将代码放在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];
}
Hope this helps. Ciao!
希望这可以帮助。再见!
回答by P.L.
Combing through the few answers & comments, I found out that using UIBezierPath bezierPathWithRoundedRect
and CAShapeLayer
the simplest and most straight forward way. It might not be appropriate for very complex cases, but for occasional rounding of corners, it works fast and smoothly for me.
梳理了几个答案和评论,我发现了使用UIBezierPath bezierPathWithRoundedRect
和CAShapeLayer
最简单、最直接的方法。它可能不适合非常复杂的情况,但对于偶尔的圆角,它对我来说工作得又快又顺利。
I had created a simplified helper that sets the appropriate corner in the mask:
我创建了一个简化的助手,用于在蒙版中设置适当的角:
-(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;
}
To use it, simply call with the appropriate UIRectCorner enum, e.g.:
要使用它,只需使用适当的 UIRectCorner 枚举调用,例如:
[self setMaskTo:self.photoView byRoundingCorners:UIRectCornerTopLeft|UIRectCornerBottomLeft];
Please note that for me, I use it to round corners of photos in a grouped UITableViewCell, the 10.0 radius works fine for me, if need to just change the value as appropriate.
请注意,对我来说,我用它来圆化分组 UITableViewCell 中照片的角,10.0 半径对我来说很好用,如果需要适当地更改值。
EDIT:just notice a previously answered very similarly as this one (link). You can still use this answer as a added convenience function if needed.
编辑:请注意以前的回答与此非常相似(链接)。如果需要,您仍然可以将此答案用作附加的便利功能。
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
}
}
To use it, simple call maskByRoundingCorner
on any UIView
:
要使用它,只需调用maskByRoundingCorner
any UIView
:
view.maskByRoundingCorners([.topLeft, .bottomLeft])
回答by Nathan Eror
I couldn't fit this all in a comment to @lomanf's answer. So I'm adding it as an answer.
我无法在对@lomanf 的回答的评论中说明这一切。所以我将其添加为答案。
Like @lomanf said, you need to add a layer mask to prevent sublayers from drawing outside of your path's bounds. It's a lot easier to do now, though. As long as you're targeting iOS 3.2 or higher, you don't need to create an image with quartz and set it as the mask. You can simply create a CAShapeLayer
with a UIBezierPath
and use that as the mask.
就像@lomanf 所说的那样,您需要添加一个图层蒙版以防止子图层绘制到路径边界之外。不过,现在做起来容易多了。只要您的目标是 iOS 3.2 或更高版本,您就无需使用石英创建图像并将其设置为遮罩。您可以简单地CAShapeLayer
用 a创建一个UIBezierPath
并将其用作掩码。
Also, when using layer masks, make sure that the layer you're masking is not part of any layer hierarchy when you add the mask. Otherwise the behavior is undefined. If your view is already in the hierarchy, you need to remove it from its superview, mask it, then put it back where it was.
此外,在使用图层蒙版时,请确保在添加蒙版时您正在蒙版的图层不属于任何图层层次结构。否则行为是未定义的。如果您的视图已经在层次结构中,您需要将其从其超视图中删除,屏蔽它,然后将其放回原来的位置。
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];
Due to the way Core Animation rendering works, masking is a relatively slow operation. Each mask requires an extra rendering pass. So use masks sparingly.
由于 Core Animation 渲染的工作方式,遮罩是一个相对较慢的操作。每个蒙版都需要一个额外的渲染通道。因此,请谨慎使用口罩。
One of the best parts of this approach is that you no longer need to create a custom UIView
and override drawRect:
. This should make your code simpler, and maybe even faster.
这种方法最好的部分之一是您不再需要创建自定义UIView
和覆盖drawRect:
。这应该会使您的代码更简单,甚至更快。
回答by FreeAsInBeer
I've taken Nathan's example and created a category on UIView
to allow one to adhere to DRY principles. Without further ado:
我以 Nathan 的例子创建了一个类别,UIView
以允许人们遵守 DRY 原则。无需再费周折:
UIView+Roundify.h
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
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
To call:
致电:
[myView addRoundedCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight
withRadii:CGSizeMake(20.0f, 20.0f)];
回答by Josh Valdivieso
To expand a little on P.L's answer I rewrote the method like so as it wasn't rounding certain objects such as UIButton
correctly
为了稍微扩展 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;
}
And call it by
并调用它
[self setMaskTo:self.continueButton byRoundingCorners:UIRectCornerBottomLeft|UIRectCornerBottomRight withCornerRadii:CGSizeMake(3.0, 3.0)];
回答by Kevin Delord
If you want to do it in Swift you could use an extension of a UIView
. By doing so, all subclasses will be able to use the following method:
如果你想在 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
}
}
Example usage:
用法示例:
self.anImageView.roundCorner(.topRight, radius: 10)
回答by hasan
All the solutions provided achieves the goal. But, UIConstraints
can blow this up sometimes.
提供的所有解决方案都达到了目标。但是,UIConstraints
有时会炸毁它。
For example, the bottom corners needs to be rounded. If height or bottom spacing constraint are set to the UIView that needs to be rounded, the code snippets that rounds the corners needs to be moved to
viewDidLayoutSubviews
method.
例如,底角需要倒圆。如果对需要圆角的 UIView 设置了高度或底部间距约束,则需要将圆角的代码片段移至
viewDidLayoutSubviews
方法。
Highlighting:
突出显示:
UIBezierPath *maskPath = [UIBezierPath
bezierPathWithRoundedRect:roundedView.bounds byRoundingCorners:
(UIRectCornerTopRight | UIRectCornerBottomRight) cornerRadii:CGSizeMake(16.0, 16.0)];
The code snippet above will only round the top right corner if this code set in viewDidLoad
. Because roundedView.bounds
is going to change after the constraints updates the UIView
.
如果此代码设置在viewDidLoad
. 因为roundedView.bounds
在约束更新UIView
.
回答by Umang
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)];
change "AllCorners"according to your need.
根据您的需要更改“AllCorners”。
回答by Rizwan Ahmed
Extending the accepted answer, let us add backward compatibility to it. Prior to iOS 11, view.layer.maskedCorners is not available. So we can do like this
扩展已接受的答案,让我们为其添加向后兼容性。在 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
}
}
We have written maskByRoundingCorners as an UIView extension so that it improves code reuse.
我们将 maskByRoundingCorners 编写为 UIView 扩展,以提高代码重用性。
Credits to @SachinVsSachin and @P.L :) I have combined their codes to make it better.
感谢@SachinVsSachin 和@PL :) 我结合了他们的代码以使其更好。