ios 带有透明孔的 CALayer
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16512761/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
CALayer with transparent hole in it
提问by animal_chin
I have a simple view (left side of the picture) and i need to create some kind of overlay (right side of the picture) to this view. This overlay should have some opacity, so the view bellow it is still partly visible. Most importantly this overlay should have a circular hole in the middle of it so it doesn't overlay the center of the view (see picture bellow).
我有一个简单的视图(图片的左侧),我需要为此视图创建某种叠加(图片的右侧)。这个叠加层应该有一些不透明度,所以它下面的视图仍然是部分可见的。最重要的是,这个覆盖层的中间应该有一个圆孔,这样它就不会覆盖视图的中心(见下图)。
I can easily create a circle like this :
我可以轻松地创建一个这样的圆圈:
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;
And a "full" rectangular overlay like this :
还有一个像这样的“完整”矩形覆盖:
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];
But I have no idea how can I combine these two layers so they create effect I want. Anyone? I've tried really everything... Thanks a lot for help!
但我不知道如何将这两层结合起来,从而创造出我想要的效果。任何人?我真的什么都试过了……非常感谢您的帮助!
回答by animal_chin
I was able to solve this with Jon Steinmetz suggestion. If any one cares, here's the final solution:
我能够通过 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];
Swift 3.x:
斯威夫特 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)
Swift 4.2 & 5:
斯威夫特 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)
回答by Nick Yap
To create this effect, I found it easiest to create an entire view overlaying the screen, then subtracting portions of the screen using layers and UIBezierPaths. For a Swift implementation:
为了创建这种效果,我发现最简单的方法是创建一个覆盖屏幕的整个视图,然后使用图层和 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)
I tested the code above, and here is the result:
我测试了上面的代码,结果如下:
I added a library to CocoaPods that abstracts away a lot of the above code and allows you to easily create overlays with rectangular/circular holes, allowing the user to interact with views behind the overlay. I used it to create this tutorial for one of our apps:
我在 CocoaPods 中添加了一个库,它抽象出很多上述代码,并允许您轻松创建带有矩形/圆形孔的叠加层,允许用户与叠加层后面的视图进行交互。我用它为我们的一个应用程序创建了本教程:
The library is called TAOverlayView, and is open source under Apache 2.0. I hope you find it useful!
该库名为TAOverlayView,在 Apache 2.0 下是开源的。希望对你有帮助!
回答by Randy
Accepted solution Swift 3.0compatible
接受的解决方案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)
回答by skwashua
I took a similar approach as animal_chin, but I'm more visual, so I set most of it up in Interface Builder using outlets and auto layout.
我采用了与animal_chin 类似的方法,但我更直观,所以我使用outlet 和自动布局在Interface Builder 中设置了大部分。
Here is my solution in Swift
这是我在 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
回答by Luca Davanzo
Code Swift 2.0 compatible
代码 Swift 2.0 兼容
Starting from @animal_inch answer, I code a little utility-class, hope it will appreciate:
从@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()
}
}
Then, wherever in your code:
然后,在您的代码中的任何位置:
let circle = CircleMaskView(drawIn: <target_view>)
circle.opacity = 0.7
circle.draw()