【问题标题】:Rounded UIView using CALayers - only some corners - How?使用 CALayers 的圆形 UIView - 只有一些角落 - 如何?
【发布时间】:2011-01-16 20:53:51
【问题描述】:

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

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

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

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

有可能吗?怎么样?

【问题讨论】:

    标签: iphone objective-c xcode uiview calayer


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

        在 iOS 11 中,我们现在只能圆一些角

        let view = UIView()
        
        view.clipsToBounds = true
        view.layer.cornerRadius = 8
        view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
        

        【讨论】:

        • 请注意,这适用于使用自动布局的 Storyboard。发布的其他一些解决方案不适用于自动布局。
        【解决方案4】:

        为了添加到 answeraddition,我在 Swift 中创建了一个简单、可重用的 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)
            }
        }
        

        【讨论】:

          【解决方案5】:

          仅对某些角进行舍入在自动调整大小或自动布局时效果不佳。

          所以另一种选择是使用常规 cornerRadius 并隐藏您不想要的角在另一个视图下或在其父视图边界之外,确保将其设置为剪辑其内容。

          【讨论】:

            【解决方案6】:

            感谢分享。在这里我想分享一下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
            

            【讨论】:

              【解决方案7】:

              晚了五年,但我认为目前人们这样做的方式并非 100% 正确。很多人都遇到过使用 UIBezierPath + CAShapeLayer 方法会干扰 Auto-layout 的问题,尤其是在 Storyboard 上设置时。没有答案,所以我决定添加我自己的。

              有一个非常简单的方法可以绕过它:在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
                  }
              }
              

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

              【讨论】:

              • 这不是解决问题的正确方法。如果您在视图中进行自定义绘图(例如使用 Core Graphics),您应该只覆盖 drawRect(_:),否则即使是空实现也会影响性能。也不需要每次都创建一个新的蒙版层。您真正需要做的就是在视图的边界发生变化时更新遮罩层的path 属性,而这样做的地方是在layoutSubviews() 方法的覆盖范围内。
              【解决方案8】:

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

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

              【讨论】:

                【解决方案9】:

                从 iOS 3.2 开始,您可以使用 UIBezierPaths 的功能来创建开箱即用的圆角矩形(只有您指定的角是圆角的)。然后,您可以将其用作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 作为遮罩层的路径一样简单。 bezierPathWithRoundedRect:byRoundingCorners:cornerRadii: 方法的 corners 参数是一个位掩码,因此可以通过 ORing 将多个角圆角。


                编辑:添加阴影

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

                因为“imageView.layer.mask = maskLayer”应用了蒙版,所以阴影通常不会显示在蒙版之外。诀窍是使用透明视图,然后将两个子层 (CALayers) 添加到视图层: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];
                

                【讨论】:

                • 不错,这对我分组 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; `
                • @ChrisWagner:请参阅我的编辑,关于将阴影应用于圆形视图。
                • @StuDev 太棒了!我已经为阴影视图添加了一个子视图,但这要好得多!没想到要像这样添加子层。谢谢!
                • 为什么我的图像变成白色,然后当我滚动 UITableView 并重新创建单元格时 - 它可以工作!为什么?
                【解决方案10】:

                用于圆特定角的 Stuarts 示例效果很好。如果你想像左上角和右上角这样的多个角,这是怎么做的

                // 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-subviews(例如UITableViewCells 或UITableViewHeaderFooterViews)上使用此解决方案时,它导致滚动时平滑度不佳。这种使用的另一种方法是具有更好性能的this 解决方案(它为所有角落添加了cornerRadius)。为了只“显示”特定的圆角(如右上角和右下角),我在其frame.origin.x 上添加了一个带有负值的子视图,并将cornerRadius 分配给它的图层。如果有人找到更好的解决方案,我很感兴趣。
                【解决方案11】:

                有一个更简单、更快捷的答案,它可能会根据您的需要起作用,并且也适用于阴影。您可以将超层上的 maskToBounds 设置为 true,并偏移子层,使它们的 2 个角在超层边界之外,从而有效地将 2 侧的圆角切割掉。

                当然,这仅适用于您希望在同一侧仅具有 2 个圆角并且当您从一侧切掉几个像素时图层的内容看起来相同。非常适合仅在顶部四舍五入的条形图。

                【讨论】:

                  【解决方案12】:

                  我在我的代码中的很多地方都使用了这个代码,它可以 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-subviews(例如UITableViewCells 或UITableViewHeaderFooterViews)上使用此解决方案时,它导致滚动时平滑度不佳。这种使用的另一种方法是具有更好性能的this 解决方案(它为所有角落添加了cornerRadius)。为了只“显示”特定的圆角(如右上角和右下角),我在其frame.origin.x 上添加了一个带有负值的子视图,并将cornerRadius 分配给它的图层。如果有人找到更好的解决方案,我很感兴趣。
                  【解决方案13】:

                  this related question。您必须将自己的矩形绘制到带有一些圆角的CGPath,将CGPath添加到CGContext,然后使用CGContextClip进行剪辑。

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

                  【讨论】:

                    【解决方案14】:

                    我使用How do I create a round cornered UILabel on the iPhone? 的答案和How is a rounded rect view with transparency done on 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
                    {
                    }
                    

                    我以前从未使用过图形上下文,但我设法将这段代码拼凑在一起。它缺少两个角落的代码。如果你阅读了代码,你可以看到我是如何实现的(通过删除一些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];
                    }
                    

                    alt text http://nevan.net/skitch/skitched-20100224-092237.png

                    不要忘记,您需要在其中安装 QuartzCore 框架才能使其正常工作。

                    【讨论】:

                    • 对于尺寸可能发生变化的视图无法按预期工作。示例:UILabel。设置蒙版路径,然后通过设置文本更改大小将保留旧蒙版。需要子类化和重载drawRect。
                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-01-26
                    • 2016-03-30
                    • 1970-01-01
                    相关资源
                    最近更新 更多