CALayer mask not working correctly - ios

I am trying to mask a UIView to an image using it's layer's mask property. I have seen countless examples that say, "it's just that easy". However, after quite a bit of tweaking, I cannot seem to reproduce the results described. Setting the layer mask only makes the view disappear. Here is the code that I am using:
- (void)setMaskImage:(UIImage *)maskImage
{
_maskImage = maskImage;
self.layer.mask.contents = (__bridge id)(_maskImage.CGImage);
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil) {
self.layer.mask = [CALayer layer];
}
return self;
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
self.layer.mask.frame = self.layer.bounds;
}
And here is the image that I am trying to use to mask the view: http://cl.ly/0a300G2r133V

One possibility is that the system's not actually using setFrame: to set your view's geometry. It can use setCenter: and setBounds:. Another possibility is that the system is not setting your view's geometry at all, and it's only being set once, in the [super initWithFrame:frame] call, before you add the mask layer.
Anyway, instead of overriding setFrame:, you should override layoutSubviews:
- (void)layoutSubviews {
[super layoutSubviews];
self.layer.mask.frame = self.bounds;
}

The following works as expected:
- (void)setMaskImage:(UIImage *)maskImage
{
if (_maskView == nil) {
_maskView = [[UIImageView alloc] initWithImage:maskImage];
_maskView.frame = self.bounds;
self.layer.mask = _maskView.layer;
} else {
_maskView.image = maskImage;
}
}
- (UIImage *)maskImage
{
return _maskView.image;
}
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
_maskView.frame = self.bounds;
}
I'm not sure why just using a plain CALayer wasn't working, but this adds the bonus of working well with stretchable images.

You can override the UIView drawRect() method for its custom appearance on the screen.
Try to use following approach.
//code for drawRect()
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddRect(context,ImageFrame);
CGContextClip(context);
CGContextClearRect(context,ImageFrame);
[super drawRect:rect];
It will make your view transparent in your imageFrame area.

Related

Moving a frame half of the parent's size moves it all the way across (Coordinates doubled)?

I have no idea what is going on.
I am not doing anything special.
I have a UIViewController that I create programmatically, no storyboard.
Then I create a UIView with nothing special
#import "SettingsView.h"
#interface SettingsView()
#property UIImageView* bg;
#end
#implementation SettingsView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.bg = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"happy.png"]];
[self addSubview:self.bg];
}
return self;
}
-(void)layoutSubviews
{
self.bg.frame = self.frame;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
I instantiate it in the VC and when I want to show it I do:
-(void)showSettingsView
{
self.settings.frame = self.view.frame;
self.btnInfo.userInteractionEnabled = NO;
self.btnPause.userInteractionEnabled = NO;
self.btnSettings.userInteractionEnabled = NO;
self.view.userInteractionEnabled = NO;
[self.view addSubview:self.settings];
CGRect frame = self.settings.frame;
frame.origin.x = frame.size.width * 0.48;
frame.origin.y = 0;
self.settings.frame = frame;
}
Instead of seeing the image view roughly half way on the screen, it starts at exactly double (0.48 * 2 = 96%) of the screen.
I cannot understand why coordinates are doubled or something?
When I check the frame, I clearly see the origin at around 150 which is half the width of the iPhone.
What could be happening?
Thanks
Could it be that in...
-(void)layoutSubviews
{
self.bg.frame = self.frame;
}
you should be using bounds instead of frame of your self object...
-(void)layoutSubviews
{
self.bg.frame = self.bounds;
}

Label showing jagged text

I've created a custom button class to be used in my xibs that is basically just a button with a shadow with a label over it. However, the text in the label appears jagged (as though it is not being anti-aliased). Here's my code for the relevant part of the class (it's a very small class that inherits from UIButton).
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self internalInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self internalInit];
}
return self;
}
- (void)internalInit {
self.backgroundColor = [UIColor colorWithRed:22/255.0 green:72/255.0 blue:143/255.0 alpha:1.0];
CGRect frame = self.frame;
frame.origin = CGPointMake(floorf(frame.origin.x), floorf(frame.origin.y));
//self.frame = CGRectIntegral(frame);
frame = self.titleLabel.frame;
frame.origin = CGPointMake(floorf(frame.origin.x), floorf(frame.origin.y));
//self.titleLabel.frame = CGRectIntegral(frame);
// Shadow
self.layer.shadowOffset = CGSizeMake(0, 1.5);
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOpacity = 0.3;
self.layer.shouldRasterize = YES;
self.layer.shadowPath = [[UIBezierPath bezierPathWithRect:self.bounds] CGPath];
// Corner
self.layer.cornerRadius = 5;
}
I've tried troubleshooting the issue and I've found that this can occur when the origin for the label or the button is set at a non-integer value. However, I've checked the absolute value for both the button and the pixel and they are both set to integer values. I haven't been able to figure out what else could be going wrong and I cannot find any others who have had the same issue.
Generally when jaggies happen it's because the same view is being drawn multiple times over itself. Did you confirm this view is only being drawn once?

iOS: Color-changing circle button

I'm trying to make a button that will show/hide a color-selection slider. That's the easy part.
What I am not sure how to do is to make the button be a circle with an outline (say black) and the fill be the color shown in the slider. As the user moves the slider, the color of the circle should change accordingly.
I am new to iOS development, so probably I am approaching this all wrong. I would really appreciate any ideas even they are completely different from what I have here.
I started to approach this with a subclass using QuartzCore layers, and the result is decent, but there are issues/things that bother me:
For some reason I get no highlight state when pressing the button
It's obviously odd to use rounded corners to achieve a circle
Unfortunately, it seems at the point were I draw the layers, the button is not laid out yet so I have had to hardcode the radius instead of making it based on the button size.
So, yeah I hardly think this approach is ideal. I will greatly appreciate any and all help. Thank you!
#import "ColorPickerButton.h"
#implementation ColorPickerButton
#pragma mark - UIButton Overrides
+ (ColorPickerButton *)buttonWithType:(UIButtonType)buttonType
{
return [super buttonWithType:UIButtonTypeCustom];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
r = 0.3;
g = 0.6;
b = 0.9;
[self drawOutline];
[self drawColor];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)layoutSubviews
{
_outlineLayer.frame = CGRectInset(self.bounds, 5, 5);
_colorLayer.frame = CGRectInset(self.bounds, 5+_outlineLayer.borderWidth, 5+_outlineLayer.borderWidth);
[super layoutSubviews];
}
#pragma mark - Layer setters
- (void)drawOutline
{
if (!_outlineLayer)
{
_outlineLayer = [CALayer layer];
_outlineLayer.cornerRadius = 20.0;
_outlineLayer.borderWidth = 3;
_outlineLayer.borderColor = [[UIColor colorWithRed:170.0/255.0 green:170.0/255.0 blue:170.0/255.0 alpha:1.0] CGColor];
_outlineLayer.opacity = 0.6;
[self.layer insertSublayer:_outlineLayer atIndex:1];
}
}
- (void)drawColor
{
if (!_colorLayer)
{
_colorLayer = [CALayer layer];
}
_colorLayer.cornerRadius = 16.0;
_colorLayer.backgroundColor = [[UIColor colorWithRed:r green:g blue:b alpha:1.0] CGColor];
_colorLayer.opacity = 1.0;
[self.layer insertSublayer:_colorLayer atIndex:2];
}
- (void)changeColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue
{
r = red;
g = green;
b = blue;
[self drawColor];
}
#end
We do this in our app. We use the CALayer on the button and add rounded corners 1/2 the size of the component width. We add a border to the button and then set the button background color to achieve the 'fill' color. Works well.

Create a custom CALayer implementation

I'm trying to learn Core Animation to develop a certain app but I need to subclass the CALayer class, however I'm struggling to get the layer to draw itself.
I need the custom CALayer to have some additional properties and handle custom events (touching and such) but from the start the basic CALayer I'm implementing is not drawing itself, can anyone tell me what I'm doing wrong?
I'm have a
MagicSquare
#import "MagicSquare.h"
#implementation MagicSquare
-(id) initWithLayer:(id)layer {
self = [super initWithLayer:layer];
self.bounds = CGRectMake(0, 0, 200, 200);
self.position = CGPointMake(10,10);
self.cornerRadius = 100;
self.borderColor = [UIColor redColor].CGColor;
self.borderWidth = 1.5;
return self;
}
- (void)drawInContext:(CGContextRef)theContext
{
NSLog(#"Drawing");
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
CGPathAddCurveToPoint(thePath,
NULL,
15.f,250.0f,
295.0f,250.0f,
295.0f,15.0f);
CGContextBeginPath(theContext);
CGContextAddPath(theContext, thePath );
CGContextSetLineWidth(theContext,
1.0);
CGContextSetStrokeColorWithColor(theContext,
[UIColor redColor].CGColor);
CGContextStrokePath(theContext);
CFRelease(thePath);
}
and here's how I'm trying to have it draw on the main controller
#implementation BIDViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
MagicSquare *layer = [[MagicSquare alloc] initWithLayer:[CALayer layer]];
[self.view.layer addSublayer:layer];
}
Found the problem.
I needed to call to setNeedsDisplay on the layer, because it doesn't automatically draws itself:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
MagicSquare *layer = [[MagicSquare alloc] initWithLayer:[CALayer layer]];
[self.view.layer addSublayer:layer];
[layer setNeedsDisplay];
}

iOS: Drawing a CGImage into a subclass of CALayer during custom property animation in correct resolution

I'm at the end of my wisdom...
I have a view which should display an animated circular section which is pre-rendered and available as png files (both retina and non-retina versions, correctly named; pie_p_000.png vs. pie_p_000#2x.png). This section should be animated according the change to some percentage value within the app. So I made a subclass of UIView which has a custom CALayer within its layer hierarchy. The plan is to implement a custom animatable property for the CALayer subclass and change pictures in the overridden method drawInContext:. So far so good, the plan worked and the animation is shown when I change the percentage value using the function setPercentageWithFloat: of the view (full source below).
The thing is: I really don't know why my iPhone4 always presents the low-res image. I tried already playing around with scale factors, but that didn't help. Either the images are presented in the right size and low-res or the images are presented double size.
Overriding display: and setting the contents property directly has the effect that the animation doesn't appear (during the animation no image is presented); after the animation time the final image is presented. In this case the correct resolution image is presented.
Btw: the following code is not very sophisticated in error-handling, flexibility and elegance yet as it is an attempt just to get that thing running ;) - so the image is still presented flipped and so on...
I hope that somebody has a hint for me.
Thanks
The view:
#import "ScrollBarView.h"
#import "ScrollBarLayer.h"
#implementation ScrollBarView
#synthesize sbl;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)setImagesWithName:(NSString*)imageName {
ScrollBarLayer *ssbl = [[ScrollBarLayer alloc] init];
ssbl.frame = CGRectMake(0, 0, 30, 30);
[ssbl setImagesWithName:imageName];
[self.layer addSublayer:ssbl];
self.sbl = ssbl;
[ssbl release];
}
- (void) dealloc {
self.sbl = nil;
[super dealloc];
}
- (void)setPercentageWithFloat:(CGFloat)perc {
if(perc > 1.0){
perc = 1.0;
} else if(perc < 0) {
perc = 0;
}
[self.sbl setPercentage:perc];
CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:#"percentage"];
ba.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
ba.duration = 0.8;
ba.toValue = [NSNumber numberWithFloat:perc];
[self.sbl addAnimation:ba forKey:nil];
}
#end
The View can be configured to work with different images (using the function setImagesWithName:) using different names (theName). Within this method the View adds the ScrollBarLayer to its layer property.
The Layer:
#import "ScrollBarLayer.h"
#implementation ScrollBarLayer
#synthesize filename;
#dynamic percentage;
- (id)init {
self = [super init];
if (self) {
}
return self;
}
- (void)setImagesWithName:(NSString*)imageName {
self.frame = CGRectMake(0, 0, 30, 30);
self.percentage = 0;
self.filename = imageName;
}
+ (BOOL) needsDisplayForKey:(NSString*)key {
if([key isEqualToString:#"percentage"]) {
return YES;
}
return [super needsDisplayForKey:key];
}
- (void) drawInContext:(CGContextRef)ctx {
int imageIdx = (int)roundf((float)199 * self.percentage);
NSString *thisfilename = [self.filename stringByAppendingString:[NSString stringWithFormat:#"%03d.png", i+1]];
UIImage* c = [UIImage imageNamed:thisfilename];
CGImageRef img = [c CGImage];
CGSize sz = CGSizeMake(CGImageGetWidth(img), CGImageGetHeight(img));
UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width, sz.height), NO, 0.0);
CGContextDrawImage(ctx, CGRectMake(0, 0, sz.width, sz.height), img);
UIGraphicsEndImageContext();
}
- (void) dealloc {
self.pictures = nil;
self.filename = nil;
[super dealloc];
}
#end
I also would need light on this.
I think that the issue is between points and pixels... I think that you should check if the device has a retina display, and if so, you should replace the CGContextDrawImage(...) CGRect (in second parameter) for one with half width and height.
#define IS_RETINA_DISPLAY() ([[UIScreen mainScreen] respondsToSelector:#selector(scale)] == YES && [[UIScreen mainScreen] scale] == 2.00)
...
UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width, sz.height), NO, 0.0);
if (IS_RETINA_DISPLAY())
{
CGContextDrawImage(ctx, CGRectMake(0, 0, sz.width/2.0f, sz.height/2.0f), img);
} else
{
CGContextDrawImage(ctx, CGRectMake(0, 0, sz.width, sz.height), img);
}
UIGraphicsEndImageContext();
...
But I'm not sure this gill give you a "smooth" result...

Resources