【问题标题】:Using a UILabel Sublayer to Cut Off Corners Overlaying an Image使用 UILabel 子图层切断覆盖图像的角落
【发布时间】:2016-03-02 21:32:50
【问题描述】:

我在编写用于切断 UILabel 角落的代码时遇到了问题(或者,实际上,任何您可以添加子层的 UIView 派生对象)——我必须感谢 Kurt Revis他对Use a CALayer to add a diagonal banner/badge to the corner of a UITableViewCell 的回答为我指明了这个方向。

如果角覆盖纯色,我没有问题 - 使截止角与该颜色匹配很简单。但是,如果角落覆盖了图像,您将如何让图像显示出来?

我已经搜索过任何与此问题类似的问题,但大多数答案都与表格中的单元格有关,而我在这里所做的只是在屏幕视图上放置标签。

这是我使用的代码:

-(void)returnChoppedCorners:(UIView *)viewObject
{ 
    NSLog(@"Object Width = %f", viewObject.layer.frame.size.width);
    NSLog(@"Object Height = %f", viewObject.layer.frame.size.height);

    CALayer* bannerLeftTop = [CALayer layer];

    bannerLeftTop.backgroundColor = [UIColor blackColor].CGColor;
    // or whatever color the background is

    bannerLeftTop.bounds = CGRectMake(0, 0, 25, 25);
    bannerLeftTop.anchorPoint = CGPointMake(0.5, 1.0);
    bannerLeftTop.position = CGPointMake(10, 10);
    bannerLeftTop.affineTransform = CGAffineTransformMakeRotation(-45.0 / 180.0 * M_PI);

    [viewObject.layer addSublayer:bannerLeftTop];

    CALayer* bannerRightTop = [CALayer layer];

    bannerRightTop.backgroundColor = [UIColor blackColor].CGColor;

    bannerRightTop.bounds = CGRectMake(0, 0, 25, 25);
    bannerRightTop.anchorPoint = CGPointMake(0.5, 1.0);
    bannerRightTop.position = CGPointMake(viewObject.layer.frame.size.width - 10.0, 10.0);
    bannerRightTop.affineTransform = CGAffineTransformMakeRotation(45.0 / 180.0 * M_PI);

    [viewObject.layer addSublayer:bannerRightTop];
}

我将添加类似的代码来处理 BottomLeft 和 BottomRight 角,但现在,这些角是覆盖图像的角。 bannerLeftTop 和bannerRightTop 实际上是在黑色背景下在角落上旋转的正方形。让它们清晰只会让底层的 UILabel 背景颜色出现,而不是图像。使用 z 属性也是如此。掩盖答案?哦,我应该改用底层图像吗?

我还遇到了将高度和宽度传递给此方法的问题——它们与对象的受约束高度和宽度不匹配。但我们将把它留到另一个问题上。

【问题讨论】:

    标签: ios objective-c iphone uilabel calayer


    【解决方案1】:

    您需要做的不是在标签上绘制一个不透明的角三角形,而是掩盖标签,使其角不会被绘制到屏幕上。

    从 iOS 8.0 开始,UIView 具有 maskView 属性,因此我们实际上不需要降到核心动画级别来执行此操作。我们可以绘制一个图像用作蒙版,剪掉适当的角。然后我们将创建一个图像视图来保存遮罩图像,并将其设置为标签的maskView(或其他)。

    唯一的问题是(在我的测试中)UIKit 不会自动调整遮罩视图的大小,无论是使用约束还是自动调整大小。如果调整蒙版视图的大小,我们必须“手动”更新蒙版视图的框架。

    我知道您的问题已标记为 ,但为了方便起见,我在 Swift 操场上开发了我的答案。将其转换为 Objective-C 应该不难。我没有做任何特别“Swifty”的事情。

    所以...这里有一个函数,它接受一个角数组(指定为UIViewContentMode case,因为该枚举包括角的情况)、一个视图和一个“深度”,即每个角的点数三角形应沿其正方形边测量:

    func maskCorners(corners: [UIViewContentMode], ofView view: UIView, toDepth depth: CGFloat) {
    

    在 Objective-C 中,对于corners 参数,您可以使用位掩码(例如(1 << UIViewContentModeTopLeft) | (1 << UIViewContentModeBottomRight)),或者您可以使用NSNumbers 中的NSArray(例如@[ @(UIViewContentModeTopLeft), @(UIViewContentModeBottomRight) ])。

    无论如何,我要创建一个正方形、9 片可调整大小的图像。图像中间需要一个点来拉伸,因为每个角都可能需要被剪裁,所以这些角需要是depth x depth 点。因此图像的边长为1 + 2 * depth 点:

        let s = 1 + 2 * depth
    

    现在我要创建一个路径来勾勒蒙版的轮廓,并剪掉角。

        let path = UIBezierPath()
    

    所以,如果左上角被剪掉,我需要路径来避开正方形的左上角(在 0、0 处)。否则,路径包括正方形的左上角。

        if corners.contains(.TopLeft) {
            path.moveToPoint(CGPoint(x: 0, y: 0 + depth))
            path.addLineToPoint(CGPoint(x: 0 + depth, y: 0))
        } else {
            path.moveToPoint(CGPoint(x: 0, y: 0))
        }
    

    依次对每个角落做同样的事情,绕过广场:

        if corners.contains(.TopRight) {
            path.addLineToPoint(CGPoint(x: s - depth, y: 0))
            path.addLineToPoint(CGPoint(x: s, y: 0 + depth))
        } else {
            path.addLineToPoint(CGPoint(x: s, y: 0))
        }
    
        if corners.contains(.BottomRight) {
            path.addLineToPoint(CGPoint(x: s, y: s - depth))
            path.addLineToPoint(CGPoint(x: s - depth, y: s))
        } else {
            path.addLineToPoint(CGPoint(x: s, y: s))
        }
    
        if corners.contains(.BottomLeft) {
            path.addLineToPoint(CGPoint(x: 0 + depth, y: s))
            path.addLineToPoint(CGPoint(x: 0, y: s - depth))
        } else {
            path.addLineToPoint(CGPoint(x: 0, y: s))
        }
    

    最后,关闭路径以便我填充它:

        path.closePath()
    

    现在我需要创建蒙版图像。我将使用仅限 alpha 的位图来执行此操作:

        let colorSpace = CGColorSpaceCreateDeviceGray()
        let scale = UIScreen.mainScreen().scale
        let gc = CGBitmapContextCreate(nil, Int(s * scale), Int(s * scale), 8, 0, colorSpace, CGImageAlphaInfo.Only.rawValue)!
    

    我需要调整上下文的坐标系以匹配 UIKit:

        CGContextScaleCTM(gc, scale, -scale)
        CGContextTranslateCTM(gc, 0, -s)
    

    现在我可以在上下文中填充路径。这里使用白色是任意的;任何 alpha 为 1.0 的颜色都可以使用:

        CGContextSetFillColorWithColor(gc, UIColor.whiteColor().CGColor)
        CGContextAddPath(gc, path.CGPath)
        CGContextFillPath(gc)
    

    接下来我从位图创建一个UIImage

        let image = UIImage(CGImage: CGBitmapContextCreateImage(gc)!, scale: scale, orientation: .Up)
    

    如果这是在 Objective-C 中,此时您希望使用 CGContextRelease(gc) 释放位图上下文,但 Swift 会为我处理。

    无论如何,我将不可调整大小的图像转换为 9 切片可调整大小的图像:

        let maskImage = image.resizableImageWithCapInsets(UIEdgeInsets(top: depth, left: depth, bottom: depth, right: depth))
    

    最后,我设置了遮罩视图。我可能已经有一个遮罩视图,因为您可能已经使用不同的设置裁剪了视图,所以如果它是图像视图,我将重用现有的遮罩视图:

        let maskView = view.maskView as? UIImageView ?? UIImageView()
        maskView.image = maskImage
    

    最后,如果我必须创建遮罩视图,我需要将其设置为view.maskView 并设置它的框架:

        if view.maskView != maskView {
            view.maskView = maskView
            maskView.frame = view.bounds
        }
    }
    

    好的,我该如何使用这个功能?为了演示,我将制作一个紫色背景视图,并在其上放置一个图像:

    let view = UIImageView(image: UIImage(named: "Kaz-256.jpg"))
    view.autoresizingMask = [ .FlexibleWidth, .FlexibleHeight ]
    let backgroundView = UIView(frame: view.frame)
    backgroundView.backgroundColor = UIColor.purpleColor()
    backgroundView.addSubview(view)
    XCPlaygroundPage.currentPage.liveView = backgroundView
    

    然后我将遮盖图像视图的一些角落。大概你会这样做,比如viewDidLoad

    maskCorners([.TopLeft, .BottomRight], ofView: view, toDepth: 50)
    

    结果如下:

    您可以通过剪裁的角落看到紫色背景。

    如果我要调整视图大小,我需要更新遮罩视图的框架。例如,我可能会在我的视图控制器中这样做:

        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
            self.cornerClippedView.maskView?.frame = self.cornerClippedView.bounds
        }
    

    Here's a gist of all the code,因此您可以将其复制并粘贴到 Playground 中进行试用。您必须提供自己可爱的测试图片。

    更新

    下面的代码创建了一个带有白色背景的标签,并将它(每边插入 20 个点)覆盖在背景图像上:

    let backgroundView = UIImageView(image: UIImage(named: "Kaz-256.jpg"))
    let label = UILabel(frame: backgroundView.bounds.insetBy(dx: 20, dy: 20))
    label.backgroundColor = UIColor.whiteColor()
    label.font = UIFont.systemFontOfSize(50)
    label.text = "This is the label"
    label.lineBreakMode = .ByWordWrapping
    label.numberOfLines = 0
    label.textAlignment = .Center
    label.autoresizingMask = [ .FlexibleWidth, .FlexibleHeight ]
    backgroundView.addSubview(label)
    XCPlaygroundPage.currentPage.liveView = backgroundView
    
    maskCorners([.TopLeft, .BottomRight], ofView: label, toDepth: 50)
    

    结果:

    【讨论】:

    • 如果我总是想剪掉所有四个角,我根本不需要corners 参数,对吧?我可以在没有所有 if corners.contains 代码的情况下创建贝塞尔路径。
    • 我将 Swift 转换为 Objective-C 并试了一下,是的,你可以看到剪裁角下的 UIView 的背景颜色。但我已经能够做到这一点。问题是我有一个 UIImage 作为 UIView 的子视图,我希望它通过剪角显示。可以这样想——使用迷人的 Kaz 图片作为背景,并尝试在其顶部放置一个带有剪角的标签。
    • 好的,我已经修改了答案。
    • 谢谢,我在 Objective-C 中完成了所有工作。我已将您的答案标记为正确。我花了一段时间才弄清楚的最大的 Swiftie 是 CGPoint 的区别——Obj-C 使用 (CGPoint){0, 0 + depth} 而不是 Swift 的 (CGPoint(x: 0, y: 0 + depth))。我还必须额外发布一个在 CGBitmapContextCreateImage 内部创建的看不见的额外 CGImageRef,Analyze 将其指示为潜在泄漏——我必须将该命令分成两部分以获得一个实际的 CGImageRef,然后我可以发布。再次感谢。
    • 哦,在 Objective-C 中我会说CGPointMake(0, 0+depth)。我在评论中提到需要CGContextRelease,但很容易错过。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-02
    • 2017-01-23
    • 2010-11-03
    • 2021-05-31
    • 2016-08-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多