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

标签 ios objective-c iphone uilabel calayer

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

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

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

这是我使用的代码:

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

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

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

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

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

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

无论如何,我要创建一个正方形的 9 切片可调整大小的图像。图像需要在中间一个点进行拉伸(stretch),并且由于可能需要裁剪每个角,因此角需要为 depth通过 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))

最后,我设置了 mask View 。我可能已经有一个 mask View ,因为您可能已经使用不同的设置裁剪了 View ,所以如果它是 ImageView ,我将重用现有的 mask View :
    let maskView = view.maskView as? UIImageView ?? UIImageView()
    maskView.image = maskImage

最后,如果我必须创建 mask View ,我需要将其设置为 view.maskView并设置它的框架:
    if view.maskView != maskView {
        view.maskView = maskView
        maskView.frame = view.bounds
    }
}

好的,我该如何使用这个功能?为了演示,我将制作一个紫色背景 View ,并在其上放置一个图像:
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

然后我将掩盖 ImageView 的一些角落。假设您会在 viewDidLoad 中执行此操作:
maskCorners([.TopLeft, .BottomRight], ofView: view, toDepth: 50)

结果如下:

image with masked corners

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

如果我要调整 View 大小,我需要更新 mask View 的框架。例如,我可能会在我的 View Controller 中这样做:
    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)

结果:

label with clipped corners over image background

关于ios - 使用 UILabel 子图层切断覆盖图像的角落,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33978424/

相关文章:

c++ - 为什么 __PRETTY_FUNCTION__ 被称为 __PRETTY_FUNCTION__?

ios - UICollectionView header 仅在第一个单元格上调用一次

objective-c - 如何打开文档目录中的Html页面?

objective-c - 添加带有核心数据的关系对象时出现 "property cannot be found in forward class object"错误

iphone - 在格式化价格时获取 NSNumberFormatter 以拼出货币

ios - Objective-C 属性和指向 Swift 的指针

javascript - 仅检测视口(viewport) jQuery 中的元素

iphone - UINavigationBar 上的 Pixelheight 并带有提示

ios - RACSignal : Why use rac_textSignal a "defer" to return a RACSignal to self?

ios - Swift 对类进行子类化的问题