Corte el agujero transparente en UIView

Busca crear una vista que tenga un marco transparente dentro de ella para que las vistas detrás de la vista se puedan ver a través de este marco transparente, pero las áreas externas a esta no se mostrarán. Entonces, esencialmente una window dentro de la vista.

Con la esperanza de poder hacer algo como esto:

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

pero el claro no anula el negro, por lo que el background integer es negro. ¿Alguna idea a lo largo de estas líneas?

Esta es mi implementación (ya que necesitaba una vista con partes transparentes):

Archivo de encabezado (.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 

Archivo de implementación (.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 

Ahora para agregar una vista con transparencia parcial, debe importar la subclass UIView personalizada PartialTransparentView y luego usarla de la siguiente manera:

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

Esto creará una vista con 2 rectos transparentes. Por supuesto, puede agregar tantos rectos como desee, o simplemente usar uno. El código anterior es solo el event handling rectangularjs, por lo que si desea utilizar círculos, tendrá que modificarlo.

La respuesta de Lefteris es absolutamente correcta, sin embargo, crea Rectos transparentes. Para la capa transparente CIRCULAR, modifique draw rect como

 - (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); } } } 

Utilicé UIBezierPath para manejar el corte del agujero transparente. El siguiente código entra en una subclass de UIView que desea dibujar un agujero transparente:

 - (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 networkingraws 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]; } 

Esto hará el recorte:

 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); } 

La respuesta de @ mosib fue muy útil para mí hasta que quise dibujar más de un corte circular en mi opinión. Después de luchar un poco, actualicé mi drawRect así (código en swift … lo siento mala edición):

 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 } 

Otra solución: The Big rect es toda la vista (color amarillo) y lo pequeño es el rect transparente. La opacidad del color se puede ajustar.

 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) 

introduzca la descripción de la imagen aquí

Esta implementación admite rectangularjs y círculos, escritos en swift: 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 } requinetworking 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); } } } } } 

Aquí está mi implementación rápida general.

  • Para las vistas estáticas, agregue tuplas a la matriz de holeViews como (theView, isRound)
  • Si desea asignar dinámicamente las vistas según lo necesite, establezca el generador en algo, por ejemplo, {someViewArray.map{($0,false)}} // array of views, not round
  • Use el radio de la esquina de la vista en lugar de la bandera isRound si lo desea, isRound es más fácil para hacer círculos.
  • Tenga en count que isRound es realmente isEllipseThatWillBeRoundIfTheViewIsSquare
  • La mayoría de los códigos no necesitarán los públicos / internos.

Espero que ayude a alguien, gracias a los otros queueboradores.

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

Si quieres algo rápido y efectivo, agregué una biblioteca ( TAOverlayView ) a CocoaPods que te permite crear superposiciones con agujeros rectangulares / circulares, lo que permite al usuario interactuar con las vistas detrás de la superposition. Lo usé para crear este tutorial para una de nuestras aplicaciones:

Tutorial utilizando el TAOverlayView

Puede cambiar el background configurando el UIColor(networking: 0, green: 0, blue: 0, alpha: 0.85) de backgroundColor de la superposition con algo como UIColor(networking: 0, green: 0, blue: 0, alpha: 0.85) , según sus necesidades de color y opacidad.

Bueno, tendré que responder como se perdió el comentario y llenar un formulario de respuesta 🙂 Realmente me gustaría que Carsten proporcione más información sobre la mejor manera de hacer lo que propone.

Podrías usar

 + (UIColor *)colorWithPatternImage:(UIImage *)image 

para crear una image de background 'color' de cualquier complejidad. Se podría crear una image mediante progtwigción si está familiarizado con las classs de dibujo o estáticamente si los frameworks de windows están pnetworkingefinidos.

Terminó "falsificando"

windowFrame es una propiedad

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

en este código crea más que un círculo

 - (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 requinetworking.. // 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; } 

Incluyendo una respuesta para Xamarin Studio iOS usando C #. Esto dibuja un solo rectángulo networkingondeado con un 60% de alfa. Mayormente tomado de la respuesta de @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(); } 

¡Hágalo al revés! Coloque esas vistas que le gustaría ver a través del "agujero" en una vista separada del tamaño correcto. Luego, establezca "clipsToBounds" en YES y coloque esa vista en la parte superior. La vista con el marco "transparente" es la más inferior en ese momento. "clipsToBounds" significa que todo lo que está fuera de la caja / agujero se corta.

Entonces podría tener que lidiar con cómo se manejan los toques. Pero esa es otra pregunta. Tal vez baste con userInteractionEnabled en las vistas de acuerdo.