ios - Corte el agujero transparente en UIView
objective-c cgcontext (15)
Buscando 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 fuera de esta no se mostrarán. Entonces esencialmente una ventana dentro de la vista.
Esperando 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 fondo completo es negro. ¿Alguna idea a lo largo de estas líneas?
¡Hazlo al revés! Coloque las vistas que le gustaría ver a través del "agujero" en una vista separada del tamaño correcto. Luego ajuste "clipsToBounds" a YES y ponga esa vista en la parte superior. La vista con el marco "transparente" es la más inferior a continuación. "clipsToBounds" significa que todo lo que está fuera de la caja / agujero está cortado.
Entonces es posible que tenga que tratar cómo se manejan los toques. Pero esa es otra pregunta. Quizás sea suficiente con configurar userInteractionEnabled en las vistas correspondientes.
Aquí está mi implementación general rápida.
- Para vistas estáticas, agregue tuplas a la matriz holeViews como (theView, isRound)
- Si desea asignar dinámicamente las vistas como necesito, configure el generador en algo, digamos quizás
{someViewArray.map{($0,false)}} // array of views, not round
- Utilice 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 cuenta que isRound is isEllipseThatWillBeRoundIfTheViewIsSquare
- La mayoría del código no necesitará los públicos / internos.
Espero que ayude a alguien, gracias a los otros colaboradores
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)
}
}
}
}
}
Bueno, tendré que responder como me perdí el comentario y rellené un formulario de respuesta :) Realmente me gustaría que Carsten brinde más información sobre la mejor manera de hacer lo que él propone.
Podrías usar
+ (UIColor *)colorWithPatternImage:(UIImage *)image
para crear una imagen de "color" de fondo de cualquier complejidad. Una imagen puede crearse mediante programación si está familiarizado con las clases de dibujo o estáticamente si los marcos de Windows están predefinidos.
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 subclase UIView personalizada de 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 rects transparentes. Por supuesto, puede agregar tantas repeticiones como desee o simplemente usar una. El código anterior solo está manejando rectángulos, por lo que si deseas usar círculos, tendrás que modificarlo.
Esta implementación admite rectángulos y círculos, escritos de forma rápida: 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);
}
}
}
}
}
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);
}
Implementación de la answer en 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)
}
}
}
Incluyendo una respuesta para Xamarin Studio iOS usando C #. Dibuja un único rectángulo redondeado 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();
}
La respuesta de @ mosib fue de gran ayuda 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 como este (código en swift ... lo siento mal 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
}
La respuesta de Lefteris es absolutamente correcta, sin embargo, crea Rectos transparentes. Para la capa transparente CIRCULAR, modifique dibujar 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);
}
}
}
Otra solución: el rect grande es toda la vista (color amarillo) y el pequeño es el rect transparente. La opacidad del color es configurable.
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)
Si quieres algo rápido y eficaz, agregué una biblioteca ( TAOverlayView ) a CocoaPods que te permite crear superposiciones con orificios rectangulares / circulares, lo que permite al usuario interactuar con las vistas detrás de la superposición. Lo usé para crear este tutorial para una de nuestras aplicaciones:
Puede cambiar el fondo configurando backgroundColor
de la superposición con algo como UIColor(red: 0, green: 0, blue: 0, alpha: 0.85)
, dependiendo de su color y necesidades de opacidad.
Terminó "Fingiendo"
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 el 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 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;
}
UIBezierPath
para manejar el recorte del agujero transparente. El siguiente código va a una subclase 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 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];
}