带有透明孔的层状陶瓷

我有一个简单的视图(左侧的图片) ,我需要创建一些覆盖(右侧的图片)到这个视图。这个覆盖层应该有一些不透明度,所以下面的视图仍然是部分可见的。 最重要的是,这种覆盖应该有一个圆形孔在它的中间,所以它不覆盖的中心的看法(见下图)。

我可以很容易地创建一个这样的圆圈:

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];

但是我不知道如何将这两个图层结合起来才能产生我想要的效果。有人吗?我真的什么都试过了... 非常感谢你的帮助!

Image

60236 次浏览

我可以通过 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)

Swift 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)

我采用了与 Animal _ chin 类似的方法,但是我更注重视觉效果,所以我使用插座和自动布局将大部分 Interface Builder 设置为视觉效果。

这是我在斯威夫特的解决方案

    //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

Code Swift 2.0 compatible

从@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()

为了创建这种效果,我发现最简单的方法是在屏幕上创建一个完整的视图,然后使用图层和 UIBezierPath 减去部分屏幕。对于迅速实现:

// 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)

我测试了上面的代码,结果如下:

enter image description here

我在 CocoaPods 中添加了一个库,它抽象出了上面的大量代码,并允许您轻松地创建矩形/圆形孔的覆盖图,允许用户与覆盖图后面的视图交互。我用它为我们的一个应用程序创建了这个教程:

Tutorial using the TAOverlayView

这个库名为 TAOverlayView,是 Apache 2.0下的开放源码。我希望您会发现它很有用!

可接受的解决方案 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)