使用UILabel子图层切断覆盖图像的角落

时间:2015-11-29 01:27:46

标签: ios objective-c iphone uilabel calayer

我遇到了代码I的问题,用来切断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属性也是如此。屏蔽了答案吗?我应该使用基础图像吗?

我也遇到了传递给此方法的高度和宽度的问题 - 它们不匹配对象的受约束的高度和宽度。但我们会将其保存为另一个问题。

1 个答案:

答案 0 :(得分:1)

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

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

唯一的问题是(在我的测试中)UIKit不会通过约束或自动调整自动调整掩码视图的大小。如果调整了蒙版视图的大小,我们必须“手动”更新蒙版视图的框架。

我意识到你的问题被标记为,但为方便起见,我在Swift游乐场中提出了我的答案。将其转化为Objective-C并不难。我没有做任何特别“Swifty”的事情。

所以...这里是一个带角阵列的函数(指定为UIViewContentMode个案例,因为该枚举包括角落的情况),一个视图和一个“深度”,这是每个角三角应沿其方边测量多少个点:

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

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

无论如何,我要创建一个正方形的9切片可调整大小的图像。图像中间需要一个点进行拉伸,因为每个角可能需要剪裁,所以角点需要depthdepth个点。因此,图像的边长为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)

结果如下:

image with masked corners

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

如果我要调整视图大小,我需要更新遮罩视图的框架。例如,我可以在我的视图控制器中执行此操作:

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

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

更新

此处的代码用于创建带有白色背景的标签,并在背景图片上叠加它(每侧插入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)

结果:

label with clipped corners over image background