Reverse layer mask for label
Asked Answered
C

2

8

How do you reverse the mask layer for a label? I have a textLabel, which I use as a mask for an imageView that contains an arbitrary image as follows:

let image = UIImage(named: "someImage")
let imageView = UIImageView(image: image!)

let textLabel = UILabel()
textLabel.frame = imageView.bounds
textLabel.text = "Some text"

imageView.layer.mask = textLabel.layer
imageView.layer.masksToBounds = true

The above makes the text in textLabel have a font colour of the imageView as in How to mask the layer of a view by the content of another view?.

How do I reverse this so as to remove the text in textLabel from the imageView?

Comte answered 21/4, 2016 at 3:15 Comment(0)
A
13

Make a subclass of UILabel:

class InvertedMaskLabel: UILabel {
    override func drawTextInRect(rect: CGRect) {
        guard let gc = UIGraphicsGetCurrentContext() else { return }
        CGContextSaveGState(gc)
        UIColor.whiteColor().setFill()
        UIRectFill(rect)
        CGContextSetBlendMode(gc, .Clear)
        super.drawTextInRect(rect)
        CGContextRestoreGState(gc)
    }
}

This subclass fills its bounds with an opaque color (white in this example, but only the alpha channel matters). Then it draws the text using the Clear blend mode, which simply sets all channels of the context back to 0, including the alpha channel.

Playground demo:

let root = UIView(frame: CGRectMake(0, 0, 400, 400))
root.backgroundColor = .blueColor()
XCPlaygroundPage.currentPage.liveView = root

let image = UIImage(named: "Kaz-256.jpg")
let imageView = UIImageView(image: image)
root.addSubview(imageView)

let label = InvertedMaskLabel()
label.text = "Label"
label.frame = imageView.bounds
label.font = .systemFontOfSize(40)
imageView.maskView = label

Result:

demo of image transparency inside the label text

Attainment answered 7/6, 2016 at 2:8 Comment(1)
can you please help me ?Quadriplegic
L
0

Since I needed to implement this recently and the syntax has changed a bit, here's a Swift 4.x Version of @RobMayoff's excellent answer. Demo/GitHub repo with Swift Playground located here.

(If you do upvote this, please also upvote his original answer as well :) )

A playground demonstrating the technique. The method drawRect inside InvertedMaskLabel has the secret sauce.

import UIKit
import PlaygroundSupport

// As per https://mcmap.net/q/1286976/-reverse-layer-mask-for-label

class InvertedMaskLabel: UILabel {

    override func drawText(in rect: CGRect) {

        guard let context = UIGraphicsGetCurrentContext() else { return }

        context.saveGState()
        UIColor.white.setFill()
        UIRectFill(rect) // fill bounds w/opaque color
        context.setBlendMode(.clear)
        super.drawText(in: rect) // draw text using clear blend mode, ie: set *all* channels to 0
        context.restoreGState()
    }
}

class TestView: UIView {

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

        backgroundColor = .green

        let image = UIImage(named: "tr")
        let imageView = UIImageView(image: image)
        imageview.frame = bounds
        addSubview(imageView)

        let label = InvertedMaskLabel()
        label.text = "Teddy"
        label.frame = imageView.bounds
        label.font = UIFont.systemFont(ofSize: 30)
        imageView.mask = label
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

let testView = TestView(frame: CGRect(x: 0, y: 0, width: 400, height: 500))
PlaygroundPage.current.liveView = testView
Launch answered 1/4, 2019 at 23:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.