如何绘制带有一个或多个 100% 半透明圆圈的 UIImage 矩形?

How to draw a UIImage rectangle with one or more 100% translucent circles?

最终目标
我正在为我的一个观点做一个工具提示功能。工具提示视图将以模态方式显示在它提供提示的视图上。除了需要完全半透明的一两个关键点外,大部分视图将是深色半透明背景。这些点本身就是圆形或矩形。

基本上我需要创建一个 UIImage 以便它可以很好地进入 UIImageView。

到目前为止...
目前我知道如何用一种颜色绘制任意大小的图像:

- (UIImage *)imageWithColor:(UIColor *)color scaledToSize:(CGSize)size {

    UIImage *image;

    CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);

    UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, color.CGColor);
    CGContextFillRect(context, rect);
    image = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return image;
}

也就是说...

换句话说,我可以使用上面的代码制作一个半透明的矩形。然后我需要做的就是找到一种方法在某些点的某些尺寸的矩形中"punch holes"。

问题 我怎样才能创建这些 "keyhole" 样的图像?还有其他方法吗?

我认为您不需要对图像执行此操作。您可以使用 CALayer 来实现,该 CALayer 具有与视图大小相同的蒙版,并添加不透明的子层(将充当孔)。下面的代码添加了一个带有方形和圆形的暗层 "holes".

-(IBAction)addMask:(id)sender {
    CALayer *maskLayer = [CALayer layer];
    maskLayer.frame = self.view.bounds;

    CALayer *square = [CALayer layer];
    square.frame = CGRectMake(100, 200, 50, 50);
    square.backgroundColor = [UIColor blackColor].CGColor;

    CAShapeLayer *circle = [CAShapeLayer layer];
    UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(150, 280, 50, 50)];
    circle.path = circlePath.CGPath;

    [maskLayer addSublayer:square];
    [maskLayer addSublayer:circle];

    maskLayer.backgroundColor = [UIColor colorWithWhite:1 alpha:.4].CGColor;
    self.view.layer.mask = maskLayer;
}

这是我的完整工具提示解决方案。感谢@rdelmar 向我展示了不需要 UIIMage。

它在某些地方可能是多余的,框架工作看起来很糟糕,但它完成了工作。我很想听到一些改进:

Home.h 文件

@property (strong, nonatomic) CALayer *maskLayer;
@property (strong, nonatomic) CATextLayer *profileTitleLayerBack;
@property (strong, nonatomic) CATextLayer *profileDetailLayerBack;
@property (strong, nonatomic) CATextLayer *checkinTitleLayerBack;
@property (strong, nonatomic) CATextLayer *checkinDetailLayerBack;
@property (strong, nonatomic) CATextLayer *dismissLayerBack;
@property (strong, nonatomic) UIButton *tooltipButton;

Home.m 文件

- (void)viewDidLoad {
    [super viewDidLoad];

    if ([self firstTimeViewingHome]) {
        [self showTooltipLayers];
    }
}

//FYI if you want to allow the user to see the tooltip again - just remove the 'DoesNotNeedHomeTooltip' key

- (BOOL)firstTimeViewingHome {

    BOOL firstTime = YES;

    if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DoesNotNeedHomeTooltip"])
    {
        firstTime = NO;
    }
    else {

        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"DoesNotNeedHomeTooltip"];
        [[NSUserDefaults standardUserDefaults] synchronize];

        firstTime = YES;
    }

    return firstTime;
}

- (void)showTooltipLayers {

    //I use JASidePanels so if I want to mask the full screen I need to use its view that I store in my session controller
    UIView *sidePanelView = self.sessionController.sidePanelController.view;

    //A mask is used to cover the view
    self.maskLayer = [CALayer layer];
    self.maskLayer.frame = sidePanelView.bounds;
    self.maskLayer.backgroundColor = [UIColor colorWithWhite:1 alpha:.25].CGColor;
    sidePanelView.layer.mask = self.maskLayer;

    //Shapes are then used to highlight points in view behind the mask
    CAShapeLayer *circle1 = [CAShapeLayer layer];
    UIBezierPath *circlePath1 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-4, 10, 60, 60)];
    circle1.path = circlePath1.CGPath;
    [self.maskLayer addSublayer:circle1];

    CAShapeLayer *circle2 = [CAShapeLayer layer];
    CGRect checkinCircleFrame = CGRectMake(sidePanelView.frame.size.width/2 - 35, sidePanelView.frame.size.height - 65, 70, 70);
    UIBezierPath *circlePath2 = [UIBezierPath bezierPathWithOvalInRect:checkinCircleFrame];
    circle2.path = circlePath2.CGPath;
    [self.maskLayer addSublayer:circle2];

    //Text Layers
    //
    //Each layer is added twice.
    //
    //The first layer goes in the back (in the main view).
    //
    //The second layer goes in front (in the mask),
    //creating a clear window to the text layer in the back.
    //
    //This solution was used because the front layer text color
    //could not be changed to white (only clear).
    //Using only white in the back results being affected by the mask

    //Title for Profile tip
    self.profileTitleLayerBack = [[CATextLayer alloc] init];
    self.profileTitleLayerBack.contentsScale = UIScreen.mainScreen.scale;
    self.profileTitleLayerBack.frame = CGRectMake(30, 70, 250, 40);
    self.profileTitleLayerBack.font = (__bridge CFTypeRef)(@"Oswald-Light");
    self.profileTitleLayerBack.fontSize = 35;
    self.profileTitleLayerBack.alignmentMode = kCAAlignmentLeft;
    self.profileTitleLayerBack.string = @"OBFUSCATED";
    self.profileTitleLayerBack.foregroundColor = UIColor.whiteColor.CGColor;
    [sidePanelView.layer addSublayer:self.profileTitleLayerBack];

    CATextLayer *profileTitleLayerFront = [[CATextLayer alloc] init];
    profileTitleLayerFront.contentsScale = UIScreen.mainScreen.scale;
    profileTitleLayerFront.frame = self.profileTitleLayerBack.frame;
    profileTitleLayerFront.font = self.profileTitleLayerBack.font;
    profileTitleLayerFront.fontSize = self.profileTitleLayerBack.fontSize;
    profileTitleLayerFront.alignmentMode = self.profileTitleLayerBack.alignmentMode;
    profileTitleLayerFront.string = self.profileTitleLayerBack.string;
    [self.maskLayer addSublayer:profileTitleLayerFront];

    //Detail for Profile Tip
    self.profileDetailLayerBack = [[CATextLayer alloc] init];
    self.profileDetailLayerBack.contentsScale = UIScreen.mainScreen.scale;
    self.profileDetailLayerBack.frame = CGRectMake(self.profileTitleLayerBack.frame.origin.x + 5,
                                                   self.profileTitleLayerBack.frame.origin.y +
                                                   self.profileTitleLayerBack.frame.size.height + 8,
                                                   300,
                                                   150);
    self.profileDetailLayerBack.font = (__bridge CFTypeRef)(@"Oswald-Light");
    self.profileDetailLayerBack.fontSize = 20;
    self.profileDetailLayerBack.alignmentMode = kCAAlignmentLeft;
    self.profileDetailLayerBack.string = @"This is your space.\nEverything about your obfuscated,\nobfuscated, and obfuscated.";
    self.profileDetailLayerBack.foregroundColor = UIColor.whiteColor.CGColor;
    [sidePanelView.layer addSublayer:self.profileDetailLayerBack];

    CATextLayer *profileDetailLayerFront = [[CATextLayer alloc] init];
    profileDetailLayerFront.contentsScale = UIScreen.mainScreen.scale;
    profileDetailLayerFront.frame = self.profileDetailLayerBack.frame;
    profileDetailLayerFront.font = self.profileDetailLayerBack.font;
    profileDetailLayerFront.fontSize = self.profileDetailLayerBack.fontSize;
    profileDetailLayerFront.alignmentMode = self.profileDetailLayerBack.alignmentMode;
    profileDetailLayerFront.string = self.profileDetailLayerBack.string;
    [self.maskLayer addSublayer:profileDetailLayerFront];

    //Title for Checkin tip
    self.checkinTitleLayerBack = [[CATextLayer alloc] init];
    self.checkinTitleLayerBack.contentsScale = UIScreen.mainScreen.scale;
    self.checkinTitleLayerBack.frame = CGRectMake(sidePanelView.frame.size.width/2 - 125,
                                                  checkinCircleFrame.origin.y - 40 - 115,
                                                  250,
                                                  40);
    self.checkinTitleLayerBack.font = (__bridge CFTypeRef)(@"Oswald-Light");
    self.checkinTitleLayerBack.fontSize = 35;
    self.checkinTitleLayerBack.alignmentMode = kCAAlignmentCenter;
    self.checkinTitleLayerBack.string = @"OBFUSCATED";
    self.checkinTitleLayerBack.foregroundColor = UIColor.whiteColor.CGColor;
    [sidePanelView.layer addSublayer:self.checkinTitleLayerBack];

    CATextLayer *checkinTitleLayerFront = [[CATextLayer alloc] init];
    checkinTitleLayerFront.contentsScale = UIScreen.mainScreen.scale;
    checkinTitleLayerFront.frame = self.checkinTitleLayerBack.frame;
    checkinTitleLayerFront.font = self.checkinTitleLayerBack.font;
    checkinTitleLayerFront.fontSize = self.checkinTitleLayerBack.fontSize;
    checkinTitleLayerFront.alignmentMode = self.checkinTitleLayerBack.alignmentMode;
    checkinTitleLayerFront.string = self.checkinTitleLayerBack.string;
    [self.maskLayer addSublayer:checkinTitleLayerFront];

    //Detail for Checkin Tip
    self.checkinDetailLayerBack = [[CATextLayer alloc] init];
    self.checkinDetailLayerBack.contentsScale = UIScreen.mainScreen.scale;
    self.checkinDetailLayerBack.frame = CGRectMake(sidePanelView.frame.size.width/2 - 150,
                                                   checkinCircleFrame.origin.y - 115 + 8,
                                                   300,
                                                   150);
    self.checkinDetailLayerBack.font = (__bridge CFTypeRef)(@"Oswald-Light");
    self.checkinDetailLayerBack.fontSize = 20;
    self.checkinDetailLayerBack.alignmentMode = kCAAlignmentCenter;
    self.checkinDetailLayerBack.string = @"Tap to view your obfuscated\nobfuscated and check in to your\nobfuscated and obfuscated.";
    self.checkinDetailLayerBack.foregroundColor = UIColor.whiteColor.CGColor;
    [sidePanelView.layer addSublayer:self.checkinDetailLayerBack];

    CATextLayer *checkinDetailLayerFront = [[CATextLayer alloc] init];
    checkinDetailLayerFront.contentsScale = UIScreen.mainScreen.scale;
    checkinDetailLayerFront.frame = self.checkinDetailLayerBack.frame;
    checkinDetailLayerFront.font = self.checkinDetailLayerBack.font;
    checkinDetailLayerFront.fontSize = self.checkinDetailLayerBack.fontSize;
    checkinDetailLayerFront.alignmentMode = self.checkinDetailLayerBack.alignmentMode;
    checkinDetailLayerFront.string = self.checkinDetailLayerBack.string;
    [self.maskLayer addSublayer:checkinDetailLayerFront];

    //Add a notice disclosing how to dismiss the tooltip
    self.dismissLayerBack = [[CATextLayer alloc] init];
    self.dismissLayerBack.contentsScale = UIScreen.mainScreen.scale;
    self.dismissLayerBack.frame = CGRectMake(sidePanelView.frame.size.width/2 - 75,
                                             sidePanelView.frame.size.height/2 - 10,
                                             150,
                                             20);
    self.dismissLayerBack.font = (__bridge CFTypeRef)(@"Oswald-Light");
    self.dismissLayerBack.fontSize = 16;
    self.dismissLayerBack.alignmentMode = kCAAlignmentCenter;
    self.dismissLayerBack.string = @"(Tap anywhere to dismiss)";
    self.dismissLayerBack.foregroundColor = self.view.backgroundColor.CGColor;
    [sidePanelView.layer addSublayer:self.dismissLayerBack];

    CATextLayer *dismissLayerFront = [[CATextLayer alloc] init];
    dismissLayerFront.contentsScale = UIScreen.mainScreen.scale;
    dismissLayerFront.frame = self.dismissLayerBack.frame;
    dismissLayerFront.font = self.dismissLayerBack.font;
    dismissLayerFront.fontSize = self.dismissLayerBack.fontSize;
    dismissLayerFront.alignmentMode = self.dismissLayerBack.alignmentMode;
    dismissLayerFront.string = self.dismissLayerBack.string;
    [self.maskLayer addSublayer:dismissLayerFront];

    //Add a clear button over top the view
    self.tooltipButton = [[UIButton alloc] initWithFrame:CGRectMake(sidePanelView.frame.origin.x,
                                                                    sidePanelView.frame.origin.y,
                                                                    sidePanelView.frame.size.width,
                                                                    sidePanelView.frame.size.height)];
    self.tooltipButton.backgroundColor = UIColor.clearColor;

    [self.tooltipButton addTarget:self action:@selector(tooltipButtonPressed) forControlEvents:UIControlEventTouchUpInside];

    [sidePanelView addSubview:self.tooltipButton];
    [sidePanelView bringSubviewToFront:self.tooltipButton];
}

//dismisses the tooltip view / cleans up
- (void)tooltipButtonPressed {

    self.sessionController.sidePanelController.view.layer.mask = nil;
    self.maskLayer = nil;

    [self.profileTitleLayerBack removeFromSuperlayer];
    self.profileTitleLayerBack = nil;

    [self.profileDetailLayerBack removeFromSuperlayer];
    self.profileDetailLayerBack = nil;

    [self.checkinTitleLayerBack removeFromSuperlayer];
    self.checkinTitleLayerBack = nil;

    [self.checkinDetailLayerBack removeFromSuperlayer];
    self.checkinDetailLayerBack = nil;

    [self.dismissLayerBack removeFromSuperlayer];
    self.dismissLayerBack = nil;

    [self.tooltipButton removeFromSuperview];
    self.tooltipButton = nil;
}