Masking UIView/UIImageView to cutout transparent t

2019-04-06 19:13发布

问题:

How can I mask an UIView or UIImageView so that a text is cutout from it?

I googled a lot and it seems that many people struggled the same. Most irritating I always tried to invert the alpha of a snapshotted view to get the result.

What I want looks like this:

回答1:

This is the custom mask label.

import UIKit
final class MaskLabel: UILabel {

// MARK: - IBInspectoable
@IBInspectable var cornerRadius: CGFloat {
    get { return self.layer.cornerRadius }
    set { self.layer.cornerRadius = newValue }
}

@IBInspectable var borderWidth: CGFloat {
    get { return self.layer.cornerRadius }
    set { self.layer.borderWidth = newValue }
}

@IBInspectable var borderColor: UIColor {
    get { return UIColor(cgColor: self.layer.borderColor ?? UIColor.clear.cgColor) }
    set { self.layer.borderColor = newValue.cgColor }
}

@IBInspectable var insetTop: CGFloat {
    get { return self.textInsets.top }
    set { self.textInsets.top = newValue }
}

@IBInspectable var insetLeft: CGFloat {
    get { return self.textInsets.left }
    set { self.textInsets.left = newValue }
}

@IBInspectable var insetBottom: CGFloat {
    get { return self.textInsets.bottom }
    set { self.textInsets.bottom = newValue }
}

@IBInspectable var insetRight: CGFloat {
    get { return self.textInsets.right }
    set { self.textInsets.right = newValue }
}



// MARK: - Value
// MARK: Public
private var textInsets = UIEdgeInsets.zero
private var originalBackgroundColor: UIColor? = nil



// MARK: - Initializer
required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)

    setLabelUI()
}

override init(frame: CGRect) {
    super.init(frame: frame)

    setLabelUI()
}

override func prepareForInterfaceBuilder() {
    setLabelUI()
}


// MARK: - Draw
override func drawText(in rect: CGRect) {
    super.drawText(in: UIEdgeInsetsInsetRect(rect, textInsets))
    guard let context = UIGraphicsGetCurrentContext() else { return }

    context.saveGState()
    context.setBlendMode(.clear)

    originalBackgroundColor?.setFill()
    UIRectFill(rect)

    super.drawText(in: rect)
    context.restoreGState()
}


// MARK: - Function
// MARK: Private
private func setLabelUI() {
    // cache (Before masking the label, the background color must be clear. So we have to cache it)
    originalBackgroundColor = backgroundColor
    backgroundColor = .clear

    layer.cornerRadius = cornerRadius
    layer.borderWidth  = borderWidth
    layer.borderColor  = borderColor.cgColor
}
}

This is the result.



回答2:

The main problem I had was my understanding. Instead of taking a colored view and trying to make a transparent hole in it, we can just layer it the other way around. So we have the colored background in the back, followed by the image in front that has the mask on it to only show the text part. And actually, that's pretty simple if you're using iOS 8+ by using the maskView property of UIView.

So it could look something like this in swift:

    let coloredBackground = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
    coloredBackground.backgroundColor = UIColor.greenColor()

    let imageView = UIImageView(frame: coloredBackground.bounds)
    imageView.image = UIImage(named: "myImage")
    coloredBackground.addSubview(imageView)

    let label = UILabel(frame: coloredBackground.bounds)
    label.text = "stackoverflow"
    coloredBackground.addSubview(label)

    imageView.maskView = label