CALayer с прозрачным отверстием в нем
у меня есть простой вид (левая сторона изображения), и мне нужно создать какой-то наложение (правая сторона изображения) на этот вид. Это наложение должно иметь некоторую непрозрачность, поэтому вид ниже он все еще частично виден. Самое главное, что это наложение должно иметь круглое отверстие в середине, чтобы оно не накладывалось на центр вида (см. рисунок ниже).
Я могу легко создать круг такой :
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];
но я понятия не имею, как я могу объединить эти два слоя, чтобы они создавали эффект, который я хочу. Кто? Я действительно все перепробовал... Большое спасибо за помощь!
5 ответов:
я смог решить эту проблему с предложением Джона Стейнмеца. Если кто-то заботится, вот окончательное решение:
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.икс:
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)
чтобы создать этот эффект, мне было проще всего создать весь вид, накладывающийся на экран, а затем вычесть части экрана с помощью слоев и UIBezierPaths. Для быстрой реализации:
// 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. Я надеюсь, что вы найдете его полезным!
я использовал аналогичный подход как animal_chin, но я более визуальный, поэтому я установил большую часть его в Interface Builder, используя розетки и автоматическую компоновку.
вот мое решение в 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
принято решение 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)
код Swift 2.0 совместимый
начиная с @ animal_inch ответа, я кодирую немного utility-class, надеюсь, что он оценит:
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()