带有透明孔的 CALayer
Posted
技术标签:
【中文标题】带有透明孔的 CALayer【英文标题】:CALayer with transparent hole in it 【发布时间】:2013-05-06 22:31:29 【问题描述】:我有一个简单的视图(图片的左侧),我需要为此视图创建某种叠加层(图片的右侧)。这个覆盖应该有一些不透明度,所以它下面的视图仍然是部分可见的。 最重要的是,这个覆盖层的中间应该有一个圆孔,这样它就不会覆盖视图的中心(见下图)。
我可以像这样轻松地创建一个圈子:
int radius = 20; //whatever
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0,radius,radius) cornerRadius:radius].CGPath;
circle.position = CGPointMake(CGRectGetMidX(view.frame)-radius,
CGRectGetMidY(view.frame)-radius);
circle.fillColor = [UIColor clearColor].CGColor;
还有一个像这样的“完整”矩形覆盖:
CAShapeLayer *shadow = [CAShapeLayer layer];
shadow.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height) cornerRadius:0].CGPath;
shadow.position = CGPointMake(0, 0);
shadow.fillColor = [UIColor grayColor].CGColor;
shadow.lineWidth = 0;
shadow.opacity = 0.5;
[view.layer addSublayer:shadow];
但我不知道如何将这两个图层组合起来,以便它们产生我想要的效果。任何人?我什么都试过了...非常感谢您的帮助!
【问题讨论】:
你能创建一个包含矩形和圆形的贝塞尔曲线,然后在绘制过程中使用的缠绕规则会创建一个孔(我没有尝试过)。 我不知道该怎么做:) 用矩形创建,然后moveToPoint
,然后添加圆角矩形。检查文档以了解UIBezierPath
提供的方法。
看看这个类似的问答是否有帮助:[Cut transparent hole in UIView][1] [1]:***.com/questions/9711248/…
在这里查看我的解决方案:***.com/questions/14141081/… 希望这对某人有所帮助
【参考方案1】:
我能够通过 Jon Steinmetz 的建议解决这个问题。如果有人在乎,这是最终的解决方案:
int radius = myRect.size.width;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, self.mapView.bounds.size.width, self.mapView.bounds.size.height) cornerRadius:0];
UIBezierPath *circlePath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius) cornerRadius:radius];
[path appendPath:circlePath];
[path setUsesEvenOddFillRule:YES];
CAShapeLayer *fillLayer = [CAShapeLayer layer];
fillLayer.path = path.CGPath;
fillLayer.fillRule = kCAFillRuleEvenOdd;
fillLayer.fillColor = [UIColor grayColor].CGColor;
fillLayer.opacity = 0.5;
[view.layer addSublayer:fillLayer];
斯威夫特 3.x:
let radius = myRect.size.width
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.mapView.bounds.size.width, height: self.mapView.bounds.size.height), cornerRadius: 0)
let circlePath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 2 * radius, height: 2 * radius), cornerRadius: radius)
path.append(circlePath)
path.usesEvenOddFillRule = true
let fillLayer = CAShapeLayer()
fillLayer.path = path.cgPath
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = Color.background.cgColor
fillLayer.opacity = 0.5
view.layer.addSublayer(fillLayer)
斯威夫特 4.2 和 5:
let radius: CGFloat = myRect.size.width
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height), cornerRadius: 0)
let circlePath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 2 * radius, height: 2 * radius), cornerRadius: radius)
path.append(circlePath)
path.usesEvenOddFillRule = true
let fillLayer = CAShapeLayer()
fillLayer.path = path.cgPath
fillLayer.fillRule = .evenOdd
fillLayer.fillColor = view.backgroundColor?.cgColor
fillLayer.opacity = 0.5
view.layer.addSublayer(fillLayer)
【讨论】:
为了增加灵活性,使您的视图子类为“IBDesignable”。这真的很容易!首先,将上面的代码插入我对这个问题的回答中:***.com/questions/14141081/… 作为一名 ios 开发新手,我花了几个小时试图弄清楚为什么这段代码会产生奇怪的结果。最后我发现,如果在某个时候重新计算覆盖蒙版,则必须删除添加的子层。这可以通过 view.layer.sublayers 属性实现。非常感谢您的回答! 为什么我得到的恰恰相反。透明色层,黑色半透明形状?? 如何使用此模式将透明文本添加到圆圈中,可以吗?我不知道如何 快 6 年了,仍然有帮助,但请记住,空心孔并没有真正“穿透”容纳它的层。比如说,如果将孔覆盖在按钮上。该按钮不可访问,如果您尝试像我一样制作“指导教程”,则需要该按钮。 @Nick Yap 提供的库将为您完成这项工作,通过覆盖 UIView 的 func point(inside point: CGPoint, with event: UIEvent?) -> Bool 。查看他的图书馆了解更多详情。但是,您所期望的只是“看到面具背后的东西”,这是一个有效的答案。【参考方案2】:要创建这种效果,我发现最简单的方法是创建一个覆盖屏幕的整个视图,然后使用图层和 UIBezierPaths 减去屏幕的一部分。对于 Swift 实现:
// Create a view filling the screen.
let overlay = UIView(frame: CGRectMake(0, 0,
UIScreen.mainScreen().bounds.width,
UIScreen.mainScreen().bounds.height))
// Set a semi-transparent, black background.
overlay.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.85)
// Create the initial layer from the view bounds.
let maskLayer = CAShapeLayer()
maskLayer.frame = overlay.bounds
maskLayer.fillColor = UIColor.blackColor().CGColor
// Create the frame for the circle.
let radius: CGFloat = 50.0
let rect = CGRectMake(
CGRectGetMidX(overlay.frame) - radius,
CGRectGetMidY(overlay.frame) - radius,
2 * radius,
2 * radius)
// Create the path.
let path = UIBezierPath(rect: overlay.bounds)
maskLayer.fillRule = kCAFillRuleEvenOdd
// Append the circle to the path so that it is subtracted.
path.appendPath(UIBezierPath(ovalInRect: rect))
maskLayer.path = path.CGPath
// Set the mask of the view.
overlay.layer.mask = maskLayer
// Add the view so it is visible.
self.view.addSubview(overlay)
我测试了上面的代码,结果如下:
我在 CocoaPods 中添加了一个库,该库抽象出上述大量代码,并允许您轻松创建带有矩形/圆形孔的叠加层,从而允许用户与叠加层后面的视图进行交互。我用它为我们的一个应用创建了本教程:
该库名为TAOverlayView,在 Apache 2.0 下开源。希望对您有用!
【讨论】:
另外,请不要发帖duplicate answers。相反,请考虑其他可以帮助未来用户找到所需答案的操作,如链接帖子中所述。当这些答案仅仅是一个链接和使用你的东西的建议时,它们看起来很垃圾。 @Mogsdad 我不想出现垃圾邮件,我只是在这个库上花了很多时间,我认为它对尝试做类似事情的人很有用。但是感谢您的反馈,我会更新我的答案以使用代码示例 不错的更新,尼克。我在你的角落 - 我自己已经发布了库和实用程序,我知道当我的文档已经涵盖它时,在这里放置完整的答案似乎是多余的......但是我们的想法是让答案保持独立尽可能。还有有人只发布垃圾邮件,所以我不想与他们混为一谈。我假设您的想法相同,这就是我向您指出的原因。干杯! 我使用了您创建的 pod,谢谢。但是我在覆盖层下的视图停止交互。它出什么问题了?我有一个 Scrollview,里面有 imageview。 @AmmarMujeeb 除了通过您创建的“洞”外,覆盖层会阻止交互。我对 pod 的意图是突出显示屏幕部分的叠加层,并且只允许您与突出显示的元素进行交互。【参考方案3】:接受的解决方案Swift 3.0兼容
let radius = myRect.size.width
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: self.mapView.bounds.size.width, height: self.mapView.bounds.size.height), cornerRadius: 0)
let circlePath = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 2.0*radius, height: 2.0*radius), cornerRadius: radius)
path.append(circlePath)
path.usesEvenOddFillRule = true
let fillLayer = CAShapeLayer()
fillLayer.path = path.cgPath
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = UIColor.gray.cgColor
fillLayer.opacity = 0.5
view.layer.addSublayer(fillLayer)
【讨论】:
@Fattie:你的链接失效了【参考方案4】:我采用了与 animal_chin 类似的方法,但我更直观,因此我在 Interface Builder 中使用 outlets 和自动布局进行了大部分设置。
这是我在 Swift 中的解决方案
//shadowView is a UIView of what I want to be "solid"
var outerPath = UIBezierPath(rect: shadowView.frame)
//croppingView is a subview of shadowView that is laid out in interface builder using auto layout
//croppingView is hidden.
var circlePath = UIBezierPath(ovalInRect: croppingView.frame)
outerPath.usesEvenOddFillRule = true
outerPath.appendPath(circlePath)
var maskLayer = CAShapeLayer()
maskLayer.path = outerPath.CGPath
maskLayer.fillRule = kCAFillRuleEvenOdd
maskLayer.fillColor = UIColor.whiteColor().CGColor
shadowView.layer.mask = maskLayer
【讨论】:
我喜欢这个解决方案,因为您可以在设计时和运行时轻松移动 circlePath。 这对我不起作用,虽然我将其修改为使用普通矩形而不是椭圆形,但最终的蒙版图像只是出现错误:(【参考方案5】:代码 Swift 2.0 兼容
从@animal_inch 的回答开始,我编写了一个小实用程序类,希望它会感激:
import Foundation
import UIKit
import CoreGraphics
/// Apply a circle mask on a target view. You can customize radius, color and opacity of the mask.
class CircleMaskView
private var fillLayer = CAShapeLayer()
var target: UIView?
var fillColor: UIColor = UIColor.grayColor()
didSet
self.fillLayer.fillColor = self.fillColor.CGColor
var radius: CGFloat?
didSet
self.draw()
var opacity: Float = 0.5
didSet
self.fillLayer.opacity = self.opacity
/**
Constructor
- parameter drawIn: target view
- returns: object instance
*/
init(drawIn: UIView)
self.target = drawIn
/**
Draw a circle mask on target view
*/
func draw()
guard (let target = target) else
print("target is nil")
return
var rad: CGFloat = 0
let size = target.frame.size
if let r = self.radius
rad = r
else
rad = min(size.height, size.width)
let path = UIBezierPath(roundedRect: CGRectMake(0, 0, size.width, size.height), cornerRadius: 0.0)
let circlePath = UIBezierPath(roundedRect: CGRectMake(size.width / 2.0 - rad / 2.0, 0, rad, rad), cornerRadius: rad)
path.appendPath(circlePath)
path.usesEvenOddFillRule = true
fillLayer.path = path.CGPath
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = self.fillColor.CGColor
fillLayer.opacity = self.opacity
self.target.layer.addSublayer(fillLayer)
/**
Remove circle mask
*/
func remove()
self.fillLayer.removeFromSuperlayer()
然后,在您的代码中的任何位置:
let circle = CircleMaskView(drawIn: <target_view>)
circle.opacity = 0.7
circle.draw()
【讨论】:
以上是关于带有透明孔的 CALayer的主要内容,如果未能解决你的问题,请参考以下文章
可以在 QML 中的 Map 上绘制带有孔的 MapPolygon 吗?