Вырезать прозрачное отверстие в UIView
Вы хотите создать представление, в котором внутри него будет прозрачный кадр, чтобы можно было видеть через этот прозрачный кадр вид, но области за пределами этого не будут отображаться. Таким образом, по существу окно в представлении.
В надежде сделать что-то вроде этого:
CGRect hole = CGRectMake(100, 100, 250, 250);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextFillRect(context, rect);
CGContextAddRect(context, hole);
CGContextClip(context);
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
CGContextFillRect(context, rect);
но прозрачность не отменяет черный цвет, поэтому весь фон черный. Любые идеи в этом направлении?
Ответы
Ответ 1
Это моя реализация (поскольку мне нужен был вид с прозрачными частями):
Файл заголовка (.h):
// Subclasses UIview to draw transparent rects inside the view
#import <UIKit/UIKit.h>
@interface PartialTransparentView : UIView {
NSArray *rectsArray;
UIColor *backgroundColor;
}
- (id)initWithFrame:(CGRect)frame backgroundColor:(UIColor*)color andTransparentRects:(NSArray*)rects;
@end
Файл реализации (.m):
#import "PartialTransparentView.h"
#import <QuartzCore/QuartzCore.h>
@implementation PartialTransparentView
- (id)initWithFrame:(CGRect)frame backgroundColor:(UIColor*)color andTransparentRects:(NSArray*)rects
{
backgroundColor = color;
rectsArray = rects;
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.opaque = NO;
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
[backgroundColor setFill];
UIRectFill(rect);
// clear the background in the given rectangles
for (NSValue *holeRectValue in rectsArray) {
CGRect holeRect = [holeRectValue CGRectValue];
CGRect holeRectIntersection = CGRectIntersection( holeRect, rect );
[[UIColor clearColor] setFill];
UIRectFill(holeRectIntersection);
}
}
@end
Теперь, чтобы добавить представление с частичной прозрачностью, вам нужно импортировать подклассу UIView PartialTransparentView, а затем использовать ее следующим образом:
NSArray *transparentRects = [[NSArray alloc] initWithObjects:[NSValue valueWithCGRect:CGRectMake(0, 50, 100, 20)],[NSValue valueWithCGRect:CGRectMake(0, 150, 10, 20)], nil];
PartialTransparentView *transparentView = [[PartialTransparentView alloc] initWithFrame:CGRectMake(0,0,200,400) backgroundColor:[UIColor colorWithWhite:1 alpha:0.75] andTransparentRects:rects];
[self.view addSubview:backgroundView];
Это создаст представление с 2 прозрачными прямоугольниками.
Конечно, вы можете добавить столько исправлений, сколько пожелаете, или просто использовать их.
Вышеупомянутый код обрабатывает только прямоугольники, поэтому, если вы хотите использовать круги, вам придется его модифицировать.
Ответ 2
Lefteris Answer абсолютно прав, однако он создает прозрачные Rect. Для прозрачного слоя CIRCULAR измените draw rect как
- (void)drawRect:(CGRect)rect {
[backgroundColor setFill];
UIRectFill(rect);
for (NSValue *holeRectValue in rectsArray) {
CGRect holeRect = [holeRectValue CGRectValue];
CGRect holeRectIntersection = CGRectIntersection( holeRect, rect );
CGContextRef context = UIGraphicsGetCurrentContext();
if( CGRectIntersectsRect( holeRectIntersection, rect ) )
{
CGContextAddEllipseInRect(context, holeRectIntersection);
CGContextClip(context);
CGContextClearRect(context, holeRectIntersection);
CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
CGContextFillRect( context, holeRectIntersection);
}
}
}
Ответ 3
Я использовал UIBezierPath
для обработки вырезания прозрачного отверстия.
Следующий код переходит в подкласс UIView
, который вы хотите нарисовать прозрачным отверстием:
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
// Clear any existing drawing on this view
// Remove this if the hole never changes on redraws of the UIView
CGContextClearRect(context, self.bounds);
// Create a path around the entire view
UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:self.bounds];
// Your transparent window. This is for reference, but set this either as a property of the class or some other way
CGRect transparentFrame;
// Add the transparent window
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:transparentFrame cornerRadius:5.0f];
[clipPath appendPath:path];
// NOTE: If you want to add more holes, simply create another UIBezierPath and call [clipPath appendPath:anotherPath];
// This sets the algorithm used to determine what gets filled and what doesn't
clipPath.usesEvenOddFillRule = YES;
// Add the clipping to the graphics context
[clipPath addClip];
// set your color
UIColor *tintColor = [UIColor blackColor];
// (optional) set transparency alpha
CGContextSetAlpha(context, 0.7f);
// tell the color to be a fill color
[tintColor setFill];
// fill the path
[clipPath fill];
}
Ответ 4
Другое решение:
Большой прямоangularьник - это весь вид (желтого цвета), а маленький - прозрачный прямоangularьник.
Непрозрачность цвета настраивается.
let pathBigRect = UIBezierPath(rect: bigRect)
let pathSmallRect = UIBezierPath(rect: smallRect)
pathBigRect.appendPath(pathSmallRect)
pathBigRect.usesEvenOddFillRule = true
let fillLayer = CAShapeLayer()
fillLayer.path = pathBigRect.CGPath
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = UIColor.yellowColor().CGColor
//fillLayer.opacity = 0.4
view.layer.addSublayer(fillLayer)
![enter image description here]()
Ответ 5
@mosib ответ был очень полезен для меня, пока я не хотел нарисовать больше, чем один круглый вырез на мой взгляд. После битвы немного, я обновил свой drawRect вот так (код в быстром... жаль плохое редактирование):
override func drawRect(rect: CGRect)
{
backgroundColor.setFill()
UIRectFill(rect)
let layer = CAShapeLayer()
let path = CGPathCreateMutable()
for aRect in self.rects
{
let holeEnclosingRect = aRect
CGPathAddEllipseInRect(path, nil, holeEnclosingRect) // use CGPathAddRect() for rectangular hole
/*
// Draws only one circular hole
let holeRectIntersection = CGRectIntersection(holeRect, rect)
let context = UIGraphicsGetCurrentContext()
if( CGRectIntersectsRect(holeRectIntersection, rect))
{
CGContextBeginPath(context);
CGContextAddEllipseInRect(context, holeRectIntersection)
//CGContextDrawPath(context, kCGPathFillStroke)
CGContextClip(context)
//CGContextClearRect(context, holeRectIntersection)
CGContextSetFillColorWithColor(context, UIColor.clearColor().CGColor)
CGContextFillRect(context, holeRectIntersection)
CGContextClearRect(context, holeRectIntersection)
}*/
}
CGPathAddRect(path, nil, self.bounds)
layer.path = path
layer.fillRule = kCAFillRuleEvenOdd
self.layer.mask = layer
}
Ответ 6
Это сделает обрезку:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor( context, [UIColor blueColor].CGColor );
CGContextFillRect( context, rect );
CGRect holeRectIntersection = CGRectIntersection( CGRectMake(50, 50, 50, 50), rect );
if( CGRectIntersectsRect( holeRectIntersection, rect ) )
{
CGContextAddEllipseInRect(context, holeRectIntersection);
CGContextClip(context);
CGContextClearRect(context, holeRectIntersection);
CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
CGContextFillRect( context, holeRectIntersection);
}
Ответ 7
Эта реализация поддерживает Rectangles and Circles, написанную быстрым:
PartialTransparentMaskView
class PartialTransparentMaskView: UIView{
var transparentRects: Array<CGRect>?
var transparentCircles: Array<CGRect>?
weak var targetView: UIView?
init(frame: CGRect, backgroundColor: UIColor?, transparentRects: Array<CGRect>?, transparentCircles: Array<CGRect>?, targetView: UIView?) {
super.init(frame: frame)
if((backgroundColor) != nil){
self.backgroundColor = backgroundColor
}
self.transparentRects = transparentRects
self.transparentCircles = transparentCircles
self.targetView = targetView
self.opaque = false
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(rect: CGRect) {
backgroundColor?.setFill()
UIRectFill(rect)
// clear the background in the given rectangles
if let rects = transparentRects {
for aRect in rects {
var holeRectIntersection = CGRectIntersection( aRect, rect )
UIColor.clearColor().setFill();
UIRectFill(holeRectIntersection);
}
}
if let circles = transparentCircles {
for aRect in circles {
var holeRectIntersection = aRect
let context = UIGraphicsGetCurrentContext();
if( CGRectIntersectsRect( holeRectIntersection, rect ) )
{
CGContextAddEllipseInRect(context, holeRectIntersection);
CGContextClip(context);
CGContextClearRect(context, holeRectIntersection);
CGContextSetFillColorWithColor( context, UIColor.clearColor().CGColor)
CGContextFillRect( context, holeRectIntersection);
}
}
}
}
}
Ответ 8
Вот моя общая быстрая реализация.
- Для статических представлений добавьте кортежи в массив holeViews как (theView, isRound)
- Если вы хотите динамически назначать представления, как мне было нужно, установите генератор на что-то, скажем,
{someViewArray.map{($0,false)}} // array of views, not round
- Используйте радиус угла обзора вместо флага isRound, если вы хотите, isRound просто проще создавать круги.
- Обратите внимание, что isRound действительно isEllipseThatWillBeRoundIfTheViewIsSquare
- Большинству кода не нужны общедоступные/внутренние.
Надеюсь, что это поможет кому-то, благодаря другим вкладчикам.
public class HolyView : UIView {
public var holeViews = [(UIView,Bool)]()
public var holeViewsGenerator:(()->[(UIView,Bool)])?
internal var _backgroundColor : UIColor?
public override var backgroundColor : UIColor? {
get {return _backgroundColor}
set {_backgroundColor = newValue}
}
public override func drawRect(rect: CGRect) {
if (backgroundColor == nil) {return}
let ctxt = UIGraphicsGetCurrentContext()
backgroundColor?.setFill()
UIRectFill(rect)
UIColor.whiteColor().setFill()
UIRectClip(rect)
let views = (holeViewsGenerator == nil ? holeViews : holeViewsGenerator!())
for (view,isRound) in views {
let r = convertRect(view.bounds, fromView: view)
if (CGRectIntersectsRect(rect, r)) {
let radius = view.layer.cornerRadius
if (isRound || radius > 0) {
CGContextSetBlendMode(ctxt, kCGBlendModeDestinationOut);
UIBezierPath(roundedRect: r,
byRoundingCorners: .AllCorners,
cornerRadii: (isRound ? CGSizeMake(r.size.width/2, r.size.height/2) : CGSizeMake(radius,radius))
).fillWithBlendMode(kCGBlendModeDestinationOut, alpha: 1)
}
else {
UIRectFillUsingBlendMode(r, kCGBlendModeDestinationOut)
}
}
}
}
}
Ответ 9
Если вы хотите что-то быстрое и эффективное, я добавил библиотеку (TAOverlayView) в CocoaPods, которая позволяет создавать наложения с прямоугольной/круглые отверстия, позволяющие пользователю взаимодействовать с видами за оверлей. Я использовал его для создания этого учебника для одного из наших приложений:
![Tutorial using the TAOverlayView]()
Вы можете изменить фон, установив backgroundColor
наложения с чем-то вроде UIColor(red: 0, green: 0, blue: 0, alpha: 0.85)
, в зависимости от вашего цвета и непрозрачности.
Ответ 10
Реализация ответа @Lefteris на Swift 4:
import UIKit
class PartialTransparentView: UIView {
var rectsArray: [CGRect]?
convenience init(rectsArray: [CGRect]) {
self.init()
self.rectsArray = rectsArray
backgroundColor = UIColor.black.withAlphaComponent(0.6)
isOpaque = false
}
override func draw(_ rect: CGRect) {
backgroundColor?.setFill()
UIRectFill(rect)
guard let rectsArray = rectsArray else {
return
}
for holeRect in rectsArray {
let holeRectIntersection = rect.intersection(holeRect)
UIColor.clear.setFill()
UIRectFill(holeRectIntersection)
}
}
}
Ответ 11
Ну, я должен буду ответить как пропущенный комментарий и заполнил форму ответа:)
Я действительно хотел бы, чтобы Карстен предоставил больше информации о лучшем способе делать то, что он предлагает.
Вы можете использовать
+ (UIColor *)colorWithPatternImage:(UIImage *)image
чтобы создать фоновое "цветное" изображение любой сложности. Изображение может быть создано либо программно, если вы знакомы с классами рисования или статически, если предварительно установлены фреймы окон.
Ответ 12
Закончено "Подделка"
windowFrame является свойством
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
CGContextFillRect(context, rect);
CGRect rootFrame = [[Navigation rootController] view].frame;
CGSize deviceSize = CGSizeMake(rootFrame.size.width, rootFrame.size.height);
CGRect topRect = CGRectMake(0, 0, deviceSize.width, windowFrame.origin.y);
CGRect leftRect = CGRectMake(0, topRect.size.height, windowFrame.origin.x, windowFrame.size.height);
CGRect rightRect = CGRectMake(windowFrame.size.width+windowFrame.origin.x, topRect.size.height, deviceSize.width-windowFrame.size.width+windowFrame.origin.x, windowFrame.size.height);
CGRect bottomRect = CGRectMake(0, windowFrame.origin.y+windowFrame.size.height, deviceSize.width, deviceSize.height-windowFrame.origin.y+windowFrame.size.height);
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextFillRect(context, topRect);
CGContextFillRect(context, leftRect);
CGContextFillRect(context, rightRect);
CGContextFillRect(context, bottomRect);
Ответ 13
в этом коде создайте больше круга
- (void)drawRect:(CGRect)rect {
// Drawing code
UIColor *bgcolor=[UIColor colorWithRed:0.85 green:0.85 blue:0.85 alpha:1.0f];//Grey
[bgcolor setFill];
UIRectFill(rect);
if(!self.initialLoad){//If the view has been loaded from next time we will try to clear area where required..
// clear the background in the given rectangles
for (NSValue *holeRectValue in _rectArray) {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect holeRect = [holeRectValue CGRectValue];
[[UIColor clearColor] setFill];
CGRect holeRectIntersection = CGRectIntersection( holeRect, rect );
CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextFillEllipseInRect( context, holeRectIntersection );
}
}
self.initialLoad=NO;
}
Ответ 14
Включает ответ для Xamarin Studio iOS с использованием С#. Это рисует один закругленный прямоугольник с 60% Alpha. В основном из ответа от @mikeho
public override void Draw(CGRect rect)
{
base.Draw(rect);
//Allows us to draw a nice clear rounded rect cutout
CGContext context = UIGraphics.GetCurrentContext();
// Create a path around the entire view
UIBezierPath clipPath = UIBezierPath.FromRect(rect);
// Add the transparent window to a sample rectangle
CGRect sampleRect = new CGRect(0f, 0f, rect.Width * 0.5f, rect.Height * 0.5f);
UIBezierPath path = UIBezierPath.FromRoundedRect(sampleRect, sampleRect.Height * 0.25f);
clipPath.AppendPath(path);
// This sets the algorithm used to determine what gets filled and what doesn't
clipPath.UsesEvenOddFillRule = true;
context.SetFillColor(UIColor.Black.CGColor);
context.SetAlpha(0.6f);
clipPath.Fill();
}
Ответ 15
Вы можете добиться этого, задав слой представления границ.
class HollowSquareView: UIView {
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = UIColor.clear
self.layer.masksToBounds = true
self.layer.borderColor = UIColor.black.cgColor
self.layer.borderWidth = 10.0
}
}
Это даст вам квадратную рамку шириной 10 и прозрачное ядро.
Вы также можете установить слой cornerRadius
на половину ширины вида, и это даст вам полый круг.
Ответ 16
Я использовал ответ от Бушры Шахид, и он работал хорошо, но у него есть проблема, если круги накладываются друг на друга.
Я использовал этот другой подход, который хорошо работает в таком случае:
class HoleView: UIView {
var holes: [CGRect] = [] {
didSet {
lastProcessedSize = .zero
createMask()
}
}
private var lastProcessedSize = CGSize.zero
override func layoutSubviews() {
super.layoutSubviews()
createMask()
}
private func createMask() {
guard lastProcessedSize != frame.size,
holes.count > 0
else { return }
let size = frame.size
// create image
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
guard let context = UIGraphicsGetCurrentContext()
else { return }
UIColor.white.setFill()
context.fill(CGRect(origin: .zero, size: size))
UIColor.black.setFill()
holes.forEach { context.fillEllipse(in: $0) }
// apply filter to convert black to transparent
guard let image = UIGraphicsGetImageFromCurrentImageContext(),
let cgImage = image.cgImage,
let filter = CIFilter(name: "CIMaskToAlpha")
else { return }
filter.setDefaults()
filter.setValue(CIImage(cgImage: cgImage), forKey: kCIInputImageKey)
guard let result = filter.outputImage,
let cgMaskImage = CIContext().createCGImage(result, from: result.extent)
else { return }
// Create mask
let mask = CALayer()
mask.frame = bounds
mask.contents = cgMaskImage
layer.mask = mask
}
}
В итоге:
- Вы создаете маску
UIImage
в черно-белом цвете вместо/прозрачного. - Используйте
CIMaskToAlpha
CIFilter
чтобы преобразовать его в прозрачную/белую маску. - Использовать сгенерированный
CGImage
качестве содержимого CALayer
- Пользователь
CALayer
качестве маски просмотра.
Ответ 17
Сделайте это наоборот! Поместите те виды, которые вы хотели бы увидеть через "дыру" в отдельном представлении нужного размера. Затем установите для "clipToBounds" значение YES и поместите этот вид сверху. Тогда представление с "прозрачным" фреймом является минимальным. "clipsToBounds" означает, что все, что находится за пределами коробки/отверстия, отключено.
Тогда вам, возможно, придется иметь дело с обработкой касаний. Но этот другой вопрос. Возможно, достаточно установить userInteractionEnabled в соответствующих представлениях.