Маскировка CALayer с другим CALayer

Я пытаюсь сделать форму пончика с помощью CALayers. Один CALayer будет большим кругом, другой - меньшим кругом, расположенным в его центре, маскирующим его.

Большой круг отображается нормально, но всякий раз, когда я вызываю circle.mask = circleMask;, представление выглядит пустым.

Здесь мой код:

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

Я пробовал установить большой круг суперслоя nil следующим образом:

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

Но это не изменило ситуацию.

Я также попытался установить свойство Circle masksToBounds в YES и NO, но это не помогло.

Любые мысли?

Ответ 1

Действительно, поскольку @David указывает, что текущие (iOS 5.1) маски CALayer не могут быть отменены, это создает проблему, если вы хотите использовать их, чтобы сделать прозрачное отверстие простым круговым CALayer.

Что вы можете сделать, чтобы получить пончик, сделайте круговой CALayer backgroundColor прозрачным, но дайте ему borderColor и широкий borderWidth. Здесь код 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];

Ответ 2

Это альфа-значение содержимого маскирующих слоев, которое используется в качестве маски. (Если бы вы добавили маску в качестве подслоя вместо того, чтобы использовать ее как маску, все, что покрывается подслоем, было бы видимым при использовании в качестве маски. Все, что не покрыто подслоем, будет скрыто при использовании в качестве маски.)

Поскольку ваш маленький круг полностью прозрачен, все замаскировано (скрыто). Если вы установите для него backgroundColor любой, полностью непрозрачный цвет (для маски используется только альфа-значение), тогда он будет пропускать эти пиксели.

Обратите внимание, что это обратное тому, что вы хотите. Это оставит вас только видимым "дырой пончика". Нет встроенного способа сделать обратную маску. Вместо этого вам придется рисовать содержимое маски каким-то другим способом, например, используя CAShapeLayer или используя drawInContext:.

Ответ 3

Это довольно легко, используя UIBezierPath и CAShapeLayer в качестве маскирующего слоя. Пример кода, написанный, как если бы он был в подклассе 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

Ответ 4

Мне удалось с CAShapeLayer маскировать a CALayer. Чтобы указать форму маскировки CAShapeLayer, я использовал UIBezierPath.

Я отправил код в свой ответ на этот вопрос: Как получить обратный путь UIBezierPath. Для формы пончика раскомментируйте прокомментированную строку.