简体   繁体   English

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

[英]Using a UILabel Sublayer to Cut Off Corners Overlaying an Image

I've encountered a problem with code I'd written to cut off the corners of a UILabel (or, indeed, any UIView-derived object to which you can add sublayers) -- I do have to thank Kurt Revis for his answer to Use a CALayer to add a diagonal banner/badge to the corner of a UITableViewCell that pointed me in this direction. 我在编写代码时遇到了问题,这些代码是为了切掉UILabel(或者实际上可以添加子层的任何UIView派生的对象)的角落而写的-我确实要感谢Kurt Revis的回答使用CALayer将对角横幅/徽章添加到向我指出该方向的UITableViewCell的角上。

I don't have a problem if the corner overlays a solid color -- it's simple enough to make the cut-off corner match that color. 如果拐角处覆盖纯色,我没有问题,它很简单,可以使截止角与该颜色匹配。 But if the corner overlays an image, how would you let the image show through? 但是,如果角落覆盖图像,您将如何让图像显示通过?

I've searched SO for anything similar to this problem, but most of those answers have to do with cells in tables and all I'm doing here is putting a label on a screen's view. 我已经在SO中搜索了与此问题类似的东西,但是大多数答案与表中的单元格有关,而我在这里所做的只是在屏幕视图上放置标签。

Here's the code I use: 这是我使用的代码:

-(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];
}

I'll be adding similar code to do the BottomLeft and BottomRight corners, but, right now, those are corners that overlay an image. 我将添加类似的代码来制作BottomLeft和BottomRight角,但是现在,这些角是覆盖图像的角。 The bannerLeftTop and bannerRightTop are actually squares that are rotated over the corner against a black background. bannerLeftTop和bannerRightTop实际上是在黑色背景下在角上旋转的正方形。 Making them clear only lets the underlying UILabel background color appear, not the image. 清除它们只会使基础UILabel背景色显示,而不会显示图像。 Same for using the z property. 与使用z属性相同。 Is masking the answer? 掩盖答案吗? Oo should I be working with the underlying image instead? 噢,我应该改为使用基础图像吗?

I'm also encountering a problem with the Height and Width being passed to this method -- they don't match the constrained Height and Width of the object. 我还遇到了将Height和Width传递给此方法的问题-它们与对象的受约束的Height和Width不匹配。 But we'll save that for another question. 但是,我们将其保存为另一个问题。

What you need to do, instead of drawing an opaque corner triangle over the label, is mask the label so its corners aren't drawn onto the screen. 您不需要遮盖标签,而是在标签上绘制不透明的角三角形,而不是在屏幕上绘制其角,这是您需要做的。

Since iOS 8.0, UIView has a maskView property, so we don't actually need to drop to the Core Animation level to do this. 从iOS 8.0开始, UIView具有maskView属性,因此我们实际上不需要下降到Core Animation级别即可。 We can draw an image to use as a mask, with the appropriate corners clipped. 我们可以绘制图像用作遮罩,并剪裁适当的角。 Then we'll create an image view to hold the mask image, and set it as the maskView of the label (or whatever). 然后,我们将创建一个图像视图来保存蒙版图像,并将其设置为标签(或其他内容)的maskView

The only problem is that (in my testing) UIKit won't resize the mask view automatically, either with constraints or autoresizing. 唯一的问题是(在我的测试中)UIKit不会通过约束或自动调整大小来自动调整蒙版视图的大小。 We have to update the mask view's frame “manually” if the masked view is resized. 如果调整了蒙版视图的大小,我们必须“手动”更新蒙版视图的框架。

I realize your question is tagged , but I developed my answer in a Swift playground for convenience. 我意识到您的问题被标记为 ,但是为了方便起见,我在Swift游乐场开发了答案。 It shouldn't be hard to translate this to Objective-C. 将其转换为Objective-C应该不难。 I didn't do anything particularly “Swifty”. 我没有做任何特别的事情“迅速”。

So... here's a function that takes an array of corners (specified as UIViewContentMode cases, because that enum includes cases for the corners), a view, and a “depth”, which is how many points each corner triangle should measure along its square sides: 所以...这是一个函数,它接受一个角数组(指定为UIViewContentMode例,因为该枚举包括角的例),一个视图和一个“深度”,即每个角三角形应沿其角测量多少个点方形边:

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

In Objective-C, for the corners argument, you could use a bitmask (eg (1 << UIViewContentModeTopLeft) | (1 << UIViewContentModeBottomRight) ), or you could use an NSArray of NSNumber s (eg @[ @(UIViewContentModeTopLeft), @(UIViewContentModeBottomRight) ] ). 在Objective-C中,对于corners参数,可以使用位掩码(例如(1 << UIViewContentModeTopLeft) | (1 << UIViewContentModeBottomRight) ),也可以使用NSNumberNSArray (例如@[ @(UIViewContentModeTopLeft), @(UIViewContentModeBottomRight) ] )。

Anyway, I'm going to create a square, 9-slice resizable image. 无论如何,我将创建一个方形的9切片可调整大小的图像。 The image will need one point in the middle for stretching, and since each corner might need to be clipped, the corners need to be depth by depth points. 图像在中间需要一个点进行拉伸,并且由于可能需要修剪每个角,因此需要按depth点对角进行depth Thus the image will have sides of length 1 + 2 * depth points: 因此,图像的边长为1 + 2 * depth点:

    let s = 1 + 2 * depth

Now I'm going to create a path that outlines the mask, with the corners clipped. 现在,我将创建一条轮廓线,该轮廓线会勾勒出蒙版的轮廓。

    let path = UIBezierPath()

So, if the top left corner is clipped, I need the path to avoid the top left point of the square (which is at 0, 0). 因此,如果裁剪了左上角,则需要避免该正方形的左上角点(位于0、0)的路径。 Otherwise, the path includes the top left point of the square. 否则,路径包括正方形的左上角。

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

Do the same for each corner in turn, going around the square: 依次绕过正方形,对每个角进行相同的操作:

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

Finally, close the path so I can fill it: 最后,关闭路径,以便我可以填充它:

    path.closePath()

Now I need to create the mask image. 现在,我需要创建蒙版图像。 I'll do this using an alpha-only bitmap: 我将使用纯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)!

I need to adjust the coordinate system of the context to match UIKit: 我需要调整上下文的坐标系以匹配UIKit:

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

Now I can fill the path in the context. 现在,我可以在上下文中填充路径。 The use of white here is arbitrary; 这里使用白色是任意的; any color with an alpha of 1.0 would work: alpha值为1.0的任何颜色都可以使用:

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

Next I create a UIImage from the bitmap: 接下来,我从位图创建一个UIImage

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

If this were in Objective-C, you'd want to release the bitmap context at this point, with CGContextRelease(gc) , but Swift takes care of it for me. 如果这是在Objective-C中使用的,则此时您要使用CGContextRelease(gc)释放位图上下文,但是Swift会替我处理它。

Anyway, I convert the non-resizable image to a 9-slice resizable image: 无论如何,我将不可调整大小的图像转换为9切片可调整大小的图像:

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

Finally, I set up the mask view. 最后,我设置了遮罩视图。 I might already have a mask view, because you might have clipped the view with different settings already, so I'll reuse an existing mask view if it is an image view: 我可能已经有了遮罩视图,因为您可能已经用不同的设置裁剪了该视图,所以如果它是图像视图,我将重用现有的遮罩视图:

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

Finally, if I had to create the mask view, I need to set it as view.maskView and set its frame: 最后,如果必须创建蒙版视图,则需要将其设置为view.maskView并设置其框架:

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

OK, how do I use this function? OK,如何使用此功能? To demonstrate, I'll make a purple background view, and put an image on top of it: 为了演示,我将制作紫色背景视图,并将图像放在其顶部:

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

Then I'll mask some corners of the image view. 然后,我将遮盖图像视图的某些角落。 Presumably you would do this in, say, viewDidLoad : 大概您可以在viewDidLoad执行此操作:

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

Here's the result: 结果如下:

具有蒙版角的图像

You can see the purple background showing through the clipped corners. 您会看到紫色的背景通过剪裁的角显示。

If I were to resize the view, I'd need to update the mask view's frame. 如果要调整视图的大小,则需要更新蒙版视图的框架。 For example, I might do this in my view controller: 例如,我可以在视图控制器中执行此操作:

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

Here's a gist of all the code , so you can copy and paste it into a playground to try out. 这是所有代码的要点 ,因此您可以将其复制并粘贴到操场上以进行尝试。 You'll have to supply your own adorable test image. 您必须提供自己的可爱测试图像。

UPDATE 更新

Here's code to create a label with a white background, and overlay it (inset by 20 points on each side) on the background image: 以下代码创建带有白色背景的标签,并将其覆盖(在每侧插入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)

Result: 结果:

在图像背景上带有弯角的标签

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM