0

End Goal
I'm doing a tooltip feature for one of my views. The tooltip view will be presented in a modal fashion over the view it's giving tips on. Most of the view will be a dark translucent background, except for one or two key points that need to be completely translucent. These points would be circles or rectangles themselves.

Basically I need to create a UIImage so it can go nicely into a UIImageView.

So far...
Currently I know how to draw an image with one color of any size:

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

In other words...

In other words, I could use the above code to make a translucent rectangle. Then all I'd need to do is find a way to "punch holes" in the rectangle of certain sizes at certain points.

Question How can I create these "keyhole"-like images? Is there another approach?

kraftydevil
  • 5,144
  • 6
  • 43
  • 65

2 Answers2

3

I don't think you need to do this with an image. You can do it with a CALayer with a mask that's the same size as the view, with added sublayers that are opaque (which will act as holes). The code below adds a darkened layer with square and a circle "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;
}

enter image description here

rdelmar
  • 103,982
  • 12
  • 207
  • 218
  • This is great! I have this working as I described. My challenge at this point is - how do I add text to this layer so that the text is not affected by the mask? I have tried this solution: http://stackoverflow.com/a/4210914/869936. The problem is that the text is always translucent. Can you advise how to achieve some white text that is unaffected by the dark mask? – kraftydevil Mar 20 '15 at 01:35
  • Nevermind. Ultimately what I did was add two CATextLayers. One is added as a sublayer of the mask and so it is automatically clear. The other is added as a sublayer of the view with the exact same attributes (string, position, font, fontSize, etc...), where I was able to change it to white. The mask CATextLayer then creates a window to the view's CATextLayer! – kraftydevil Mar 20 '15 at 03:55
0

Here is my full tooltip solution. Credit goes to @rdelmar for showing me that a UIIMage was not needed.

It's probably redundant in some places and the frame work looks atrocious, but it gets the job done. I'd love to hear some improvements:

Home.h file

@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 file

- (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;
}
kraftydevil
  • 5,144
  • 6
  • 43
  • 65