做一个透视层
Make a see-through layer
我正在尝试在我的应用程序上实现擦除功能。对于图像,我可以在使用 UIGraphicsBeginImageContextWithOptions
绘制图像时将弯曲模式设置为清晰。这是我使用的代码
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0)
guard let context = UIGraphicsGetCurrentContext() else { return }
image.draw(in: self.bounds)
context.setLineCap(.round)
context.setLineWidth(5)
context.setStrokeColor(UIColor.white.cgColor)
context.setBlendMode(.clear)
context.beginPath()
context.move(to: lastStroke.touchPoint)
context.addLine(to: currentStroke.touchPoint)
context.strokePath()
let updatedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
它适用于图像。但我想在 UILabel
.
上实现它
实现它的一种方法是将 UILabel
转换为 UIImage
,这将增加 UILabel
修改的难度(例如更改字体、间距)。这也将使它恢复。
能做个CALayer
/CAShapeLayer
或UIView
可以透视吗?
Here is an example I have seen on an app.
P.S:我不是在寻找确切的代码,任何实现想法都将是可观的。
All views have a "Backing layer", including UILabel
views.
As such, you can install a mask layer on the view's layer. You can make that mask layer a CAShapeLayer, or a regular layer with a CGImage as it's contents.
Don't mess with blend modes. Just add a mask layer to any layer that you want to be able to erase/un-erase, including a UILabel's layer
The mask layer will show/hide the contents of the layer it masks. (Opaque pixels in the mask reveal the layer underneath, but clear pixels hide it.)
If you want to be able to do freehand erasing/revealing of things like labels, I would suggest using a layer with an image in it as your mask. Draw opaque pixels into the masks image to "un-erase" the masked image, and draw with clear pixels to erase the masked image. Because you're just changing the mask, the masked image is left untouched.
@IBOutlet label: UILabel
var labelMask = CALayer()
...
func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// Set up the mask to be the same size as the label view
labelMask.frame = label.bounds
label.layer.mask = labelMask
labelMask.contents = // install a UIImage's CGImage as the contents
// The rest of your viewDidAppear code goes here...
}
Edit:
I posted a demo app on Github it that illustrates how to mask ANY view using a CALayer. This demo app uses a CGImage to create a raster mask. You can also use a CAShapeLayer as a vector mask.
Here is the readme from that project:
MaskableImageView
This project demonstrates how to use a CALayer
to mask a UIView
.
It defines a custom subclass of UIImageView, MaskableView
.
The MaskableView
class has a 属性 maskLayer
that contains a CALayer.
MaskableView
defines a didSet
method on its bounds
属性 so that when the view's bounds change, it resizes the mask layer to match the size of the image view.
The MaskableView
has a method installSampleMask
which builds an image the same size as the image view, mostly filled with opaque black, but with a small rectangle in the center filled with black at an alpha of 0.7. The translucent center rectangle causes the image view to become partly transparent and show the view underneath.
The demo app installs a couple of subviews into the MaskableView
, a sample image of Scampers, one of my dogs, and a UILabel. It also installs an image of a checkerboard under the MaskableView
so that you can see the translucent parts more easily.
The MaskableView
has properties circleRadius
, maskDrawingAlpha
, and drawingAction
that it uses to let the user erase/un-erase the image by tapping on the view to update the mask.
The MaskableView
attaches a UIPanGestureRecognizer
and a UITapGestureRecognizer
to itself, with an action of gestureRecognizerUpdate
. The gestureRecognizerUpdate
method takes the tap/drag location from the gesture recognizer and uses it to draw a circle onto the image mask that either decreases the image mask's alpha (to partly erase pixels) or increase the image mask's alpha (to make those pixels more opaque.)
The MaskableView
's mask drawing is crude, and only meant for demonstration purposes. It draws a series of discrete circles intstead of rendering a path into the mask based on the user's drag gesture. A better solution would be to connect the points from the gesture recognizer and use them to render a smoothed curve into the mask.
The app's screen looks like this:
我正在尝试在我的应用程序上实现擦除功能。对于图像,我可以在使用 UIGraphicsBeginImageContextWithOptions
绘制图像时将弯曲模式设置为清晰。这是我使用的代码
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0)
guard let context = UIGraphicsGetCurrentContext() else { return }
image.draw(in: self.bounds)
context.setLineCap(.round)
context.setLineWidth(5)
context.setStrokeColor(UIColor.white.cgColor)
context.setBlendMode(.clear)
context.beginPath()
context.move(to: lastStroke.touchPoint)
context.addLine(to: currentStroke.touchPoint)
context.strokePath()
let updatedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
它适用于图像。但我想在 UILabel
.
实现它的一种方法是将 UILabel
转换为 UIImage
,这将增加 UILabel
修改的难度(例如更改字体、间距)。这也将使它恢复。
能做个CALayer
/CAShapeLayer
或UIView
可以透视吗?
Here is an example I have seen on an app.
P.S:我不是在寻找确切的代码,任何实现想法都将是可观的。
All views have a "Backing layer", including UILabel
views.
As such, you can install a mask layer on the view's layer. You can make that mask layer a CAShapeLayer, or a regular layer with a CGImage as it's contents.
Don't mess with blend modes. Just add a mask layer to any layer that you want to be able to erase/un-erase, including a UILabel's layer
The mask layer will show/hide the contents of the layer it masks. (Opaque pixels in the mask reveal the layer underneath, but clear pixels hide it.)
If you want to be able to do freehand erasing/revealing of things like labels, I would suggest using a layer with an image in it as your mask. Draw opaque pixels into the masks image to "un-erase" the masked image, and draw with clear pixels to erase the masked image. Because you're just changing the mask, the masked image is left untouched.
@IBOutlet label: UILabel
var labelMask = CALayer()
...
func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// Set up the mask to be the same size as the label view
labelMask.frame = label.bounds
label.layer.mask = labelMask
labelMask.contents = // install a UIImage's CGImage as the contents
// The rest of your viewDidAppear code goes here...
}
Edit:
I posted a demo app on Github it that illustrates how to mask ANY view using a CALayer. This demo app uses a CGImage to create a raster mask. You can also use a CAShapeLayer as a vector mask.
Here is the readme from that project:
MaskableImageView
This project demonstrates how to use a CALayer
to mask a UIView
.
It defines a custom subclass of UIImageView, MaskableView
.
The MaskableView
class has a 属性 maskLayer
that contains a CALayer.
MaskableView
defines a didSet
method on its bounds
属性 so that when the view's bounds change, it resizes the mask layer to match the size of the image view.
The MaskableView
has a method installSampleMask
which builds an image the same size as the image view, mostly filled with opaque black, but with a small rectangle in the center filled with black at an alpha of 0.7. The translucent center rectangle causes the image view to become partly transparent and show the view underneath.
The demo app installs a couple of subviews into the MaskableView
, a sample image of Scampers, one of my dogs, and a UILabel. It also installs an image of a checkerboard under the MaskableView
so that you can see the translucent parts more easily.
The MaskableView
has properties circleRadius
, maskDrawingAlpha
, and drawingAction
that it uses to let the user erase/un-erase the image by tapping on the view to update the mask.
The MaskableView
attaches a UIPanGestureRecognizer
and a UITapGestureRecognizer
to itself, with an action of gestureRecognizerUpdate
. The gestureRecognizerUpdate
method takes the tap/drag location from the gesture recognizer and uses it to draw a circle onto the image mask that either decreases the image mask's alpha (to partly erase pixels) or increase the image mask's alpha (to make those pixels more opaque.)
The MaskableView
's mask drawing is crude, and only meant for demonstration purposes. It draws a series of discrete circles intstead of rendering a path into the mask based on the user's drag gesture. A better solution would be to connect the points from the gesture recognizer and use them to render a smoothed curve into the mask.
The app's screen looks like this: