Create a custom CALayer implementation - ios

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

Related

Create show Case view In Tabbar xcode

I am trying to create my own custom show case view , In which i have left button , right button and a tab bar view.
I want to highlight the buttons with round circle around it.
I am able to create a single Circle around my button by subclass UIView
But what i do so user swipe from left to right it will change the Circle to right button and the left button hide.
Here is my subclass of UIView
//--------.h class
#import <UIKit/UIKit.h>
#interface IntroView : UIView
- (void)drawRect:(CGRect)rect withBtnRect:(CGRect)btnrect ;
#end
//------. m class
#import "IntroView.h"
#implementation IntroView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
self.backgroundColor= [[UIColor blackColor] colorWithAlphaComponent:0.3];
return self;
}
- (void)drawRect:(CGRect)rect {
CGRect maskRect= CGRectMake(5, 5, 100, 100);//set and pass this maskRect
CGRect rBounds = self.bounds;
CGContextRef context = UIGraphicsGetCurrentContext();
// Fill background with 40% gray
CGContextSetFillColorWithColor(context, [[[UIColor grayColor] colorWithAlphaComponent:0.4] CGColor]);
CGContextFillRect(context, rBounds);
// Draw the window 'frame'
CGContextSetStrokeColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGContextSetLineWidth(context,2);
CGContextStrokeEllipseInRect(context, maskRect);
// make the window transparent
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextFillEllipseInRect (context, maskRect);
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
And inside my tabbar first view i call using
#import "IntroView.h"
then
- (void)viewDidLoad
{
IntroView *vw_intro=[[IntroView alloc]initWithFrame:CGRectMake(0, 0, 320, 480)];
vw_intro.tag=1000;
[self.view addSubview:vw_intro];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
Here is my output
And expected Output on Slide right to left , and so or in reverse (like we do in showcase android library )
In IntroView.h
#import <UIKit/UIKit.h>
#interface IntroView : UIView
- (id)initWithFrame:(CGRect)frame introElements:(NSArray *)iElements;
- (void)showIntro;
#end
In IntroView.m
#import "IntroView.h"
#implementation IntroView
{
CAShapeLayer *mask;
NSArray *introElements;
NSUInteger currentIndex;
}
- (id)initWithFrame:(CGRect)frame introElements:(NSArray *)iElements
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
introElements = iElements;
mask = [CAShapeLayer layer];
[mask setFillColor:[[UIColor grayColor] colorWithAlphaComponent:0.8f].CGColor];
[mask setFillRule:kCAFillRuleEvenOdd];
[self.layer addSublayer:mask];
UISwipeGestureRecognizer *swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(userDidSwipe)];
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
[self addGestureRecognizer:swipeGestureRecognizer];
}
return self;
}
- (void)userDidSwipe
{
[self showIntroAtIndex:currentIndex+1];
}
- (void)showIntro
{
//Initial index by default
[self showIntroAtIndex:0];
}
- (void)showIntroAtIndex:(NSUInteger)index
{
currentIndex = index;
CGRect rect = [[introElements objectAtIndex:index] CGRectValue];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRect:self.bounds];
UIBezierPath *elementPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:3.0f];
[maskPath appendPath:elementPath];
// Animate
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
animation.duration = 0.3f;
animation.fromValue = (__bridge id)(mask.path);
animation.toValue = (__bridge id)(maskPath.CGPath);
[mask addAnimation:animation forKey:#"path"];
mask.path = maskPath.CGPath;
}
In ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSArray *viewElements = [NSArray arrayWithObjects:[NSValue valueWithCGRect:button1.frame],[NSValue valueWithCGRect:button2.frame], nil];
IntroView *introView = [[IntroView alloc]initWithFrame:self.view.bounds introElements:viewElements];
[self.view addSubview:introView];
[introView showIntro];
}
Note: i made a highlight to the UIControl frame, if you need a circle highlight, update the changes in the CGRect.

Core Animation not running in View Controller that also contains a Sprite Kit scene

In my project, there’s a main View Controller with a subview that contains a Sprite Kit scene. The subview (“MySubview”) is implemented like this:
- (id) initWithFrame: (CGRect) frame {
self = [super initWithFrame: frame];
if (self) {
myScene* scene = [myScene sceneWithSize: CGSizeMake(WIDTH, HEIGHT)];
[self presentScene: scene];
}
return self;
}
The main View Controller initialises this subview like this:
- (void) viewDidLoad {
[super viewDidLoad];
_mySubview = [[MySubview alloc] initWithFrame: CGRectMake(X, Y, WIDTH, HEIGHT)];
[self.view addSubview: mySubview];
}
All this is good and working so far. But then I try adding Core Animation on an object outside of the Sprite Kit view. I add the following to my main View Controller’s implementation:
CAShapeLayer* circleLayer;
- (void) setupCircle {
CGPathRef circlePath = CGPathCreateWithEllipseInRect(CGRectMake(CIRCLE_X, CIRCLE_Y, CIRCLE_WIDTH, CIRCLE_HEIGHT), nil);
circleLayer = [CAShapeLayer layer];
circleLayer.path = circlePath;
circleLayer.fillColor = [UIColor orangeColor].CGColor;
circleLayer.strokeColor = [UIColor blueColor].CGColor;
circleLayer.lineWidth = 10;
[self.view.layer addSublayer: circleLayer];
}
- (void) animateCircle {
CABasicAnimation* circleAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
circleAnimation.duration = 4;
circleAnimation.fromValue = #0;
circleAnimation.toValue = #1;
[circleLayer addAnimation: circleAnimation
forKey: #"circleAnimation"];
}
And then I change my viewDidLoad method to:
- (void) viewDidLoad {
[super viewDidLoad];
_mySubview = [[MySubview alloc] initWithFrame: CGRectMake(X, Y, WIDTH, HEIGHT)];
[self.view addSubview: mySubview];
[self setupCircle];
[self animateCircle];
}
This draws the circle, but does not animate its stroke. When I comment out all the Sprite Kit subview code, it does animate. Is it a problem with threading, run loops, or something else? I’m stuck here. Thanks for the help.

UIView with transparent in middle

Is it possible to create such a UIView fill with color, but in the middle is transparent?
I'm thinking about to create 5 UIViews here. Just wondering is it possible to accomplish by using only ONE UIView
From Duncan C, I get to know where should I start, then I found CALayer with transparent hole in it.
UIBezierPath *overlayPath = [UIBezierPath bezierPathWithRect:self.view.bounds];
UIBezierPath *transparentPath = [UIBezierPath bezierPathWithRect:CGRectMake(60, 120, 200, 200)];
[overlayPath appendPath:transparentPath];
[overlayPath setUsesEvenOddFillRule:YES];
CAShapeLayer *fillLayer = [CAShapeLayer layer];
fillLayer.path = overlayPath.CGPath;
fillLayer.fillRule = kCAFillRuleEvenOdd;
fillLayer.fillColor = [UIColor colorWithRed:255/255.0 green:20/255.0 blue:147/255.0 alpha:1].CGColor;
[self.view.layer addSublayer:fillLayer];
Make use of 2 UIBezierPath, then fill with color that I want (in my question is pink color), then add as sublayer
You create a view as subclass of UIView and add these codes:
In YourView.h
#import <UIKit/UIKit.h>
#interface YourView : UIView
#property (nonatomic, assign) CGRect rectForClearing;
#property (nonatomic, strong) UIColor *overallColor;
#end
In YourView.m
#import "YourView.h"
#implementation NBAMiddleTransparentView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder // support init from nib
{
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
[super drawRect:rect];
CGContextRef ct = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(ct, self.overallColor.CGColor);
CGContextFillRect(ct, self.bounds);
CGContextClearRect(ct, self.rectForClearing);
}
#end
Using:
yourView.overallColor = [UIColor redColor];
yourView.rectForClearing = CGRectMake(20, 20, 20, 20);
Hope this helps!
Yes it's possible. You could attach a mask layer to your view's layer, and "punch out" the center portion of the mask. It would actually be quite easy.

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?

CALayer mask not working correctly

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.

Resources