2012-07-30 18 views
10

Estoy tratando de hacer una forma de donut con CALayers. Un CALayer será un círculo grande, el otro será un círculo más pequeño colocado en su centro, enmascarando.Enmascaramiento de un CALayer con otro CALayer

El círculo grande se muestra bien, pero cada vez que llamo al circle.mask = circleMask;, la vista aparece vacía.

Aquí está mi código:

AriDonut.h

#import <UIKit/UIKit.h> 

@interface AriDonut : UIView 
-(id)initWithRadius:(float)radius; 
@end 

AriDonut.m

#import "AriDonut.h" 
#import <QuartzCore/QuartzCore.h> 

@implementation AriDonut 

-(id)initWithRadius:(float)radius{ 
    self = [super initWithFrame:CGRectMake(0, 0, radius, radius)]; 
    if(self){ 

     //LARGE CIRCLE 
     CALayer *circle = [CALayer layer]; 
     circle.bounds = CGRectMake(0, 0, radius, radius); 
     circle.backgroundColor = [UIColor redColor].CGColor; 
     circle.cornerRadius = radius/2; 
     circle.position = CGPointMake(radius/2, radius/2); 

     //SMALL CIRLCE 
     CALayer *circleMask = [CALayer layer]; 
     circleMask.bounds = CGRectMake(0, 0, 10, 10); 
     circleMask.cornerRadius = radius/2; 
     circleMask.position = circle.position; 

     //circle.mask = circleMask; 

     [self.layer addSublayer:circle]; 

    } 

    return self; 
} 

He tratado de establecer nula superlayer del círculo grande como esto:

CALayer *theSuper = circle.superlayer; 
theSuper = nil; 

Pero no hizo la diferencia.

También intenté configurar la propiedad masksToBounds de Circle para YES y NO, pero no hizo la diferencia.

¿Alguna idea?

Respuesta

13

De hecho, como @David indica que las máscaras CALayer actuales (iOS 5.1) no se pueden revertir, lo que plantea un problema si se quieren usar para hacer un agujero transparente un CALayer circular simple.

Lo que puedes hacer para obtener una dona es hacer una circular de 0 de CALAYER backgroundColor transparente, pero dale una borderColor y una amplia borderWidth. Aquí está el código Dunkin':

CALayer *theDonut = [CALayer layer]; 
    theDonut.bounds = CGRectMake(0,0, radius, radius); 
    theDonut.cornerRadius = radius/2; 
    theDonut.backgroundColor = [UIColor clearColor].CGColor; 

    theDonut.borderWidth = radius/5; 
    theDonut.borderColor = [UIColor orangeColor].CGColor; 

    [self.layer addSublayer:theDonut]; 
4

Es el valor alfa del contenido de las capas de enmascaramiento que se utiliza como máscara. (Si agrega la máscara como una subcapa en lugar de usarla como máscara, todo lo que cubre la subcapa estará visible cuando se use como máscara. Todo lo que no esté cubierto por la subcapa quedará oculto cuando se use como máscara .)

Dado que su pequeño círculo es completamente transparente, todo está enmascarado (está oculto). Si configura el backgroundColor en un color completamente opaco (solo se usa el valor alfa para la máscara), permitirá el paso de esos píxeles.

Tenga en cuenta que esto es lo contrario de lo que desea. Esto te dejará solo con "el agujero de la rosquilla" visible. No hay una forma incorporada de hacer una máscara inversa En su lugar, debería dibujar el contenido de la máscara de otra manera, como usar CAShapeLayer o usar drawInContext:.

1

tuve éxito con un CAShapeLayer enmascarar un CALayer. Para especificar la forma de la máscara CAShapeLayer utilicé UIBezierPath.

Publiqué el código en mi respuesta a esta pregunta: How to Get the reverse path of a UIBezierPath. Para la forma del donut descomenta la línea comentada.

4

Esto es bastante fácil usando UIBezierPath y CAShapeLayer como capa de enmascaramiento. Ejemplo de código escrito como si estuviera en una subclase de UIView.

Objective-C:

CGRect outerRect = self.bounds; 
CGFloat inset = 0.2 * outerRect.size.width; // adjust as necessary for more or less meaty donuts 
CGFloat innerDiameter = outerRect.size.width - 2.0 * inset; 
CGRect innerRect = CGRectMake(inset, inset, innerDiameter, innerDiameter); 
UIBezierPath *outerCircle = [UIBezierPath bezierPathWithRoundedRect:outerRect cornerRadius:outerRect.size.width * 0.5]; 
UIBezierPath *innerCircle = [UIBezierPath bezierPathWithRoundedRect:innerRect cornerRadius:innerRect.size.width * 0.5]; 
[outerCircle appendPath:innerCircle]; 
CAShapeLayer *maskLayer = [CAShapeLayer new]; 
maskLayer.fillRule = kCAFillRuleEvenOdd; // Going from the outside of the layer, each time a path is crossed, add one. Each time the count is odd, we are "inside" the path. 
maskLayer.path = outerCircle.CGPath; 
self.layer.mask = maskLayer; 

Swift:

let outerRect = self.bounds 
let inset: CGFloat = 0.2 * outerRect.width // adjust as necessary for more or less meaty donuts 
let innerDiameter = outerRect.width - 2.0 * inset 
let innerRect = CGRect(x: inset, y: inset, width: innerDiameter, height: innerDiameter) 
let outerCircle = UIBezierPath(roundedRect: outerRect, cornerRadius: outerRect.width * 0.5) 
let innerCircle = UIBezierPath(roundedRect: innerRect, cornerRadius: innerRect.width * 0.5) 
outerCircle.appendPath(innerCircle) 
let mask = CAShapeLayer() 
mask.fillRule = kCAFillRuleEvenOdd 
mask.path = outerCircle.CGPath 
self.layer.mask = mask