What I'm essentially trying to do is have a text label 'cut' a text-shaped hole through the view. I've tried using self.mask = uiLabel
but those refused to position the text correctly so I'm approaching this through Core Graphics.
Here's the code that isn't working (in the draw(_ rect: CGRect)
):
let context = (UIGraphicsGetCurrentContext())!
// Set mask background color
context.setFillColor(UIColor.black.cgColor)
context.fill(rect)
context.saveGState()
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attributes = [
NSParagraphStyleAttributeName: paragraphStyle,
NSFontAttributeName: UIFont.systemFont(ofSize: 16, weight: UIFontWeightMedium),
NSForegroundColorAttributeName: UIColor.white
]
let string = NSString(string: "LOGIN")
// This wouldn't vertically align so we calculate the string size and create a new rect in which it is vertically aligned
let size = string.size(attributes: attributes)
let position = CGRect(
x: rect.origin.x,
y: rect.origin.y + (rect.size.height - size.height) / 2,
width: rect.size.width,
height: size.height
)
context.translateBy(x: 0, y: rect.size.height)
context.scaleBy(x: 1, y: -1)
string.draw(
in: position,
withAttributes: attributes
)
let mask = (context.makeImage())!
context.restoreGState()
// Redraw with created mask
context.clear(rect)
context.saveGState()
// !!!! Below line is the problem
context.clip(to: rect, mask: mask)
context.restoreGState()
Essentially I've successfully created the code to create a CGImage
(the mask
variable) which is the mask I want to apply to the whole image.
The marked line when replaced with context.draw(mask, in: rect)
(to view the mask) correctly displays. The mask shows (correctly) as:
:
However once I try to apply this mask (using the context.clip(to: rect, mask: mask)
) nothing happens!. Actual result:
Desired result is:
but for some reason the mask is not being correctly applied.
This code seems like it should work as I've read the docs over and over again. I've additionally tried to create the mask in a separate CGContext which didn't work. Also when I tried to convert the CGImage (mask
) to CGColorSpaceCreateDeviceGray()
using .copy(colorSpace:)
, it returned nil
. I've been at this for two days so any help is appreciated
If you want the label to have fully translucent text, you can use blend modes instead of masks.
public class KnockoutLabel: UILabel {
public override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
context.setBlendMode(.clear)
self.drawText(in: rect)
}
}
Make sure to set isOpaque
to false
though; by default the view assumes it is opaque, since you use an opaque background color.
I'm not actually sure that you will enjoy the code below. It was made by using the PaintCode. Text will be always at the center of rounded rectangle. Hope it will help you.
Code, put it inside draw(_ rect: CGRect):
//// General Declarations
let context = UIGraphicsGetCurrentContext()
//// Color Declarations - just to make a gradient same as your button have
let gradientColor = UIColor(red: 0.220, green: 0.220, blue: 0.223, alpha: 1.000)
let gradientColor2 = UIColor(red: 0.718, green: 0.666, blue: 0.681, alpha: 1.000)
let gradientColor3 = UIColor(red: 0.401, green: 0.365, blue: 0.365, alpha: 1.000)
//// Gradient Declarations
let gradient = CGGradient(colorsSpace: nil, colors: [gradientColor.cgColor, gradientColor3.cgColor, gradientColor2.cgColor] as CFArray, locations: [0, 0.55, 1])!
//// Subframes
let group: CGRect = CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: rect.height)
//// Group
context.saveGState()
context.beginTransparencyLayer(auxiliaryInfo: nil)
//// Rectangle Drawing
let rectanglePath = UIBezierPath(roundedRect: group, cornerRadius: 5)
context.saveGState()
rectanglePath.addClip()
let rectangleRotatedPath = UIBezierPath()
rectangleRotatedPath.append(rectanglePath)
var rectangleTransform = CGAffineTransform(rotationAngle: -99 * -CGFloat.pi/180)
rectangleRotatedPath.apply(rectangleTransform)
let rectangleBounds = rectangleRotatedPath.cgPath.boundingBoxOfPath
rectangleTransform = rectangleTransform.inverted()
context.drawLinearGradient(gradient,
start: CGPoint(x: rectangleBounds.minX, y: rectangleBounds.midY).applying(rectangleTransform),
end: CGPoint(x: rectangleBounds.maxX, y: rectangleBounds.midY).applying(rectangleTransform),
options: [])
context.restoreGState()
//// Text Drawing
context.saveGState()
context.setBlendMode(.destinationOut)
let textRect = group
let textTextContent = "LOGIN"
let textStyle = NSMutableParagraphStyle()
textStyle.alignment = .center
let textFontAttributes = [
NSFontAttributeName: UIFont.systemFont(ofSize: 16, weight: UIFontWeightBold),
NSForegroundColorAttributeName: UIColor.black,
NSParagraphStyleAttributeName: textStyle,
]
let textTextHeight: CGFloat = textTextContent.boundingRect(with: CGSize(width: textRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: textFontAttributes, context: nil).height
context.saveGState()
context.clip(to: textRect)
textTextContent.draw(in: CGRect(x: textRect.minX, y: textRect.minY + (textRect.height - textTextHeight) / 2, width: textRect.width, height: textTextHeight), withAttributes: textFontAttributes)
context.restoreGState()
context.restoreGState()
context.endTransparencyLayer()
context.restoreGState()
Results:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With