I am trying to tap one of five circles and start an animation that will gradually change the selected background to the same colour as the tapped circle. I’ve managed to get UITapGestureRecognizer to respond to a tap gesture on any one of five circles but I can’t work out how to find out how to identify each circle.
The UIKit Framework Reference documentation on UIGestureRecognizer says
A gesture recognizer operates on touches hit-tested to a specific view
and all of that view’s subviews
and that
Clients of gesture recognisers can also ask for the location of a
gesture by calling locationInView: or locationOfTouch:inView”.
This made me think the tapped circles probably need to be made into images.
But is this really what I need to do ?
Here’s the code so far
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorWithRed:240/255.0 green:240/255.0 blue:240/255.0 alpha:1.0];
[self.view setUserInteractionEnabled:YES];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(method:)];
tap.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tap];
CAShapeLayer *circleLayer1 = [CAShapeLayer layer];
[circleLayer1 setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(75.0, 260.0, 50.0, 50.0)] CGPath]];
[circleLayer1 setStrokeColor:[[UIColor cyanColor] CGColor]];
[circleLayer1 setFillColor:[[UIColor cyanColor] CGColor]];
[[self.view layer] addSublayer:circleLayer1];
CAShapeLayer *circleLayer2 = [CAShapeLayer layer];
[circleLayer2 setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(125.0, 260.0, 50.0, 50.0)] CGPath]];
[circleLayer2 setStrokeColor:[[UIColor redColor] CGColor]];
[circleLayer2 setFillColor:[[UIColor redColor] CGColor]];
[[self.view layer] addSublayer:circleLayer2];
CAShapeLayer *circleLayer3 = [CAShapeLayer layer];
[circleLayer3 setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(175.0, 260.0, 50.0, 50.0)] CGPath]];
[circleLayer3 setStrokeColor:[[UIColor yellowColor] CGColor]];
[circleLayer3 setFillColor:[[UIColor yellowColor] CGColor]];
[[self.view layer] addSublayer:circleLayer3];
CAShapeLayer *circleLayer4 = [CAShapeLayer layer];
[circleLayer4 setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(225.0, 260.0, 50.0, 50.0)] CGPath]];
[circleLayer4 setStrokeColor:[[UIColor magentaColor] CGColor]];
[circleLayer4 setFillColor:[[UIColor magentaColor] CGColor]];
[[self.view layer] addSublayer:circleLayer4];
CAShapeLayer *circleLayer5 = [CAShapeLayer layer];
[circleLayer5 setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(275.0, 260.0, 50.0, 50.0)] CGPath]];
[circleLayer5 setStrokeColor:[[UIColor greenColor] CGColor]];
[circleLayer5 setFillColor:[[UIColor greenColor] CGColor]];
[[self.view layer] addSublayer:circleLayer5];
}
- (void)method:(id)sender {
[UIView animateWithDuration:3.0 animations:^{
self.view.layer.backgroundColor = [UIColor cyanColor].CGColor;
} completion:NULL];
}
To show what I’m trying to do, I’ve set up the method so it changes the background layer to the colour of the circle on the left.
But what do I need to do [a] to identify which circle was tapped and [b] represent the tapped spots as circles that would also gradually change colour so the whole screen changes to the colour of the circle that was tapped ?
I suggest you to remove the UITapGestureRecognizer that you added to the whole view.
Then add a name to each CAShapeLayer so you can distinguish between them :
self.view.backgroundColor = [UIColor colorWithRed:240/255.0 green:240/255.0 blue:240/255.0 alpha:1.0];
[self.view setUserInteractionEnabled:YES];
CAShapeLayer *circleLayer1 = [CAShapeLayer layer];
[circleLayer1 setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(75.0, 260.0, 50.0, 50.0)] CGPath]];
[circleLayer1 setStrokeColor:[[UIColor cyanColor] CGColor]];
[circleLayer1 setFillColor:[[UIColor cyanColor] CGColor]];
[circleLayer1 setName:#"circleLayer1"];
[[self.view layer] addSublayer:circleLayer1];
CAShapeLayer *circleLayer2 = [CAShapeLayer layer];
[circleLayer2 setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(125.0, 260.0, 50.0, 50.0)] CGPath]];
[circleLayer2 setStrokeColor:[[UIColor redColor] CGColor]];
[circleLayer2 setFillColor:[[UIColor redColor] CGColor]];
[circleLayer2 setName:#"circleLayer2"];
[[self.view layer] addSublayer:circleLayer2];
CAShapeLayer *circleLayer3 = [CAShapeLayer layer];
[circleLayer3 setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(175.0, 260.0, 50.0, 50.0)] CGPath]];
[circleLayer3 setStrokeColor:[[UIColor yellowColor] CGColor]];
[circleLayer3 setFillColor:[[UIColor yellowColor] CGColor]];
[circleLayer3 setName:#"circleLayer3"];
[[self.view layer] addSublayer:circleLayer3];
CAShapeLayer *circleLayer4 = [CAShapeLayer layer];
[circleLayer4 setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(225.0, 260.0, 50.0, 50.0)] CGPath]];
[circleLayer4 setStrokeColor:[[UIColor magentaColor] CGColor]];
[circleLayer4 setFillColor:[[UIColor magentaColor] CGColor]];
[circleLayer4 setName:#"circleLayer4"];
[[self.view layer] addSublayer:circleLayer4];
CAShapeLayer *circleLayer5 = [CAShapeLayer layer];
[circleLayer5 setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(275.0, 260.0, 50.0, 50.0)] CGPath]];
[circleLayer5 setStrokeColor:[[UIColor greenColor] CGColor]];
[circleLayer5 setFillColor:[[UIColor greenColor] CGColor]];
[circleLayer5 setName:#"circleLayer5"];
[[self.view layer] addSublayer:circleLayer5];
Then you can add this method that allows you to detect wich CAShapeLayer has been touched:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInView:self.view];
for (id sublayer in self.view.layer.sublayers) {
if ([sublayer isKindOfClass:[CAShapeLayer class]]) {
CAShapeLayer *shapeLayer = sublayer;
if (CGPathContainsPoint(shapeLayer.path, 0, touchLocation, YES)) {
NSLog(#"Layer's name is: %#",shapeLayer.name);
}
}
}
}
}
Now that you can detect which CAShapeLayer has been touched, you can customize the color of your view as you prefer
There is a method called locationInView:(UIView *)view that will help you to find the location. You know the rects of all the circles and check whether the location is inside of any rect using the method CGRectContainsPoint(CGRect rect, CGPoint point) If its inside you can check whether the selected location is radius distance from the centre. Hope this will solve your issue.
For Q.(b) I attached a sample you can refer it,
UIColor *stroke = rippleColor ? rippleColor : [UIColor colorWithWhite:0.8 alpha:0.8];
CGRect pathFrame = CGRectMake(-CGRectGetMidX(self.bounds), -CGRectGetMidY(self.bounds), self.bounds.size.width, self.bounds.size.height);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:pathFrame cornerRadius:self.layer.cornerRadius];
// accounts for left/right offset and contentOffset of scroll view
CGPoint shapePosition = [self convertPoint:self.center fromView:nil];
CAShapeLayer *circleShape = [CAShapeLayer layer];
circleShape.path = path.CGPath;
circleShape.position = shapePosition;
circleShape.fillColor = [UIColor clearColor].CGColor;
circleShape.opacity = 0;
circleShape.strokeColor = stroke.CGColor;
circleShape.lineWidth = 3;
[self.layer addSublayer:circleShape];
[CATransaction begin];
//remove layer after animation completed
[CATransaction setCompletionBlock:^{
[circleShape removeFromSuperlayer];
}];
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scaleAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
scaleAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2.5, 2.5, 1)];
CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
alphaAnimation.fromValue = #1;
alphaAnimation.toValue = #0;
CAAnimationGroup *animation = [CAAnimationGroup animation];
animation.animations = #[scaleAnimation, alphaAnimation];
animation.duration = 0.5f;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[circleShape addAnimation:animation forKey:nil];
[CATransaction commit];
}
[UIView animateWithDuration:0.1 animations:^{
imageView.alpha = 0.4;
self.layer.borderColor = [UIColor colorWithWhite:1 alpha:0.9].CGColor;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.2 animations:^{
imageView.alpha = 1;
self.layer.borderColor = [UIColor colorWithWhite:0.8 alpha:0.9].CGColor;
}completion:^(BOOL finished) {
if([superSender respondsToSelector:methodName]){
[superSender performSelectorOnMainThread:methodName withObject:nil waitUntilDone:NO];
}
if(_block) {
BOOL success= YES;
_block(success);
}
}];
}];
}`
As pointed by the others answers you have some options, but I think that the easiest, is to check in your method what layer you tapped.
You just need to change your method to something like that:
- (void)method:(UITapGestureRecognizer *)gesture {
CGPoint touchLocation = [gesture locationInView:self.view];
for (id sublayer in self.view.layer.sublayers) {
if ([sublayer isKindOfClass:[CAShapeLayer class]]) {
CAShapeLayer *shapeLayer = sublayer;
if (CGPathContainsPoint(shapeLayer.path, 0, touchLocation, YES)) {
[UIView animateWithDuration:3.0 animations:^{
shapeLayer.strokeColor = [UIColor blackColor].CGColor;
shapeLayer.fillColor = [UIColor blackColor].CGColor;
} completion:NULL];
}
}
}
}
You don't need to add any UITapGestureRecognizer to your view, you just need to add names to your layers and implement the following method:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInView:self.view];
for (id sublayer in self.view.layer.sublayers) {
if ([sublayer isKindOfClass:[CAShapeLayer class]]) {
CAShapeLayer *shapeLayer = sublayer;
if (CGPathContainsPoint(shapeLayer.path, 0, touchLocation, YES)) {
// This touch is in this shape layer
NSLog(#"Name of layer is: %#",shapeLayer.name);
}
} else {
CALayer *layer = sublayer;
if (CGRectContainsPoint(layer.frame, touchLocation)) {
// Touch is in this rectangular layer
}
}
}
}
}
After identifing your layer you can change the color of layer accordingly.
Or if you have limited layers than you can identify it as follows for your no of layers:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInView:self.view];
if (CGPathContainsPoint(shape1.path, 0, touchLocation, YES)) {
// This touch is in this shape layer
NSLog(#"Name of layer 1 is: %#",shape1.name);
}
if (CGPathContainsPoint(shape2.path, 0, touchLocation, YES)) {
// This touch is in this shape layer
NSLog(#"Name of layer 2 is: %#",shape2.name);
}
if (CGPathContainsPoint(shape3.path, 0, touchLocation, YES)) {
// This touch is in this shape layer
NSLog(#"Name of layer 3 is: %#",shape3.name);
}
if (CGPathContainsPoint(shape4.path, 0, touchLocation, YES)) {
// This touch is in this shape layer
NSLog(#"Name of layer 4 is: %#",shape4.name);
}
if (CGPathContainsPoint(shape5.path, 0, touchLocation, YES)) {
// This touch is in this shape layer
NSLog(#"Name of layer 5 is: %#",shape5.name);
}
}
}
I have one UIView with size 141 X 161. Then I created a circle .Here is that code:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CAShapeLayer *circleLayer = [CAShapeLayer layer];
[circleLayer setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 86, 79)] CGPath]];
[[self.photoView layer] addSublayer:circleLayer];
[circleLayer setStrokeColor:[[UIColor blackColor] CGColor]];
[circleLayer setFillColor:[[UIColor clearColor] CGColor]];
}
photoview is my uiview
When I run it's not in centre,
I need my circle in perfect centre of inside my UIView ( 141 x 161). And also need to set outside border.
And my circle is think.how to set radius at 1.
And my circle fill should be #222222
Border: thickness = 1.9
color = white
position = centre
I am new to iOS.
Try this :
CAShapeLayer *circleLayer = [CAShapeLayer layer];
[circleLayer setPath:[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(27, 41, 86, 79)] CGPath]];
[[self.photoView layer] addSublayer:circleLayer];
[circleLayer setStrokeColor:[[UIColor whiteColor] CGColor]];
[circleLayer setFillColor:[[UIColor clearColor] CGColor]];
circleLayer.lineWidth = 0.5;
circleLayer.shadowColor = [UIColor whiteColor].CGColor;
circleLayer.shadowOpacity = 0.4;
circleLayer.borderColor = [UIColor whiteColor] .CGColor;
circleLayer.borderWidth = 2;
circleLayer.fillColor =[UIColor colorWithRed:0/255.0 green:0/255.0 blue:0/255.0 alpha:0.25].CGColor;
circleLayer.borderColor = [[UIColor whiteColor] CGColor];
I have a collection view with multiple cell styles and sizes I want to round the corners of the cell using this helper function:
void roundCornersOfAView(UIView *view, CGFloat radius, BOOL shadow){
CALayer* layer = [view layer];
[layer setCornerRadius:radius];
[layer setBorderColor:[UIColor blackColor].CGColor];
[layer setMasksToBounds:YES];
// CALayer* mask = [CALayer layer];
// [mask setFrame:CGRectMake(layer.frame.origin.x, layer.frame.origin.y, layer.frame.size.width, layer.frame.size.height)];
// [mask setCornerRadius:radius];
// [layer setMask:mask];
if (shadow) {
[layer setBorderWidth:0.5f];
//[layer setMasksToBounds:NO];
[layer setShadowColor:[UIColor blackColor].CGColor];
[layer setShadowOpacity:1.0];
[layer setShadowRadius:1.0];
[layer setShadowOffset:CGSizeMake(0.0, 0.0)];
}
[layer setMasksToBounds:YES];
}
using it like this in my cell for row at index path:
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:... forIndexPath:indexPath];
roundCornersOfAView([cell contentView], 30, YES);
The problem is that images on cell are not clipped in the rounded courners, neither labels or any other subviews, it only clips the background color... what is the best way to round courners and clip its content just like the instagram app?
Calling it like this:
roundCornersOfAView(cell, 30, YES);
using the helper function like this:
void roundCornersOfAView(UIView *view, CGFloat radius, BOOL shadow){
CALayer* layer = [view layer];
[layer setCornerRadius:radius];
[layer setBorderWidth:0.0f];
if (shadow) {
[layer setBorderColor:[UIColor blackColor].CGColor];
[layer setBorderWidth:0.1f];
[layer setShadowColor:[UIColor blackColor].CGColor];
[layer setShadowOpacity:0.5];
[layer setShadowRadius:1.5];
[layer setShadowOffset:CGSizeMake(0.0, 1.0)];
}
[layer setMasksToBounds:YES];
}
The problem was that I was sending the content view instead of the cell itself.
I am trying to draw some circles inside a UIImageView with a specific image. This is what I was trying to do:
UIGraphicsBeginImageContext(self.view.bounds.size);
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(contextRef, 2.0);
CGContextSetStrokeColorWithColor(contextRef, [color CGColor]);
CGRect circlePoint = (CGRectMake(coordsFinal.x, coordsFinal.y, 50.0, 50.0));
CGContextStrokeEllipseInRect(contextRef, circlePoint);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[photoView addSubview:image];
The circle is drawn fine, but I would like the PhotoView to act as a mask to it. So if for example I move the UIImageView out of the UIView using an animation, I would like the circle to move with it. Important is the fact that the coordinates are relative to the whole screen.
Use Core Animation's shape layer instead.
CAShapeLayer *circleLayer = [CAShapeLayer layer];
// Give the layer the same bounds as your image view
[circleLayer setBounds:CGRectMake(0.0f, 0.0f, [photoView bounds].size.width,
[photoView bounds].size.height)];
// Position the circle anywhere you like, but this will center it
// In the parent layer, which will be your image view's root layer
[circleLayer setPosition:CGPointMake([photoView bounds].size.width/2.0f,
[photoView bounds].size.height/2.0f)];
// Create a circle path.
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:
CGRectMake(0.0f, 0.0f, 50.0f, 50.0f)];
// Set the path on the layer
[circleLayer setPath:[path CGPath]];
// Set the stroke color
[circleLayer setStrokeColor:[[UIColor redColor] CGColor]];
// Set the stroke line width
[circleLayer setLineWidth:2.0f];
// Add the sublayer to the image view's layer tree
[[photoView layer] addSublayer:circleLayer];
Now, if you animate the UIImageView that contains this layer, the layer will move with it since it is a child layer. And there is now no need to override drawRect:.
I am using this code to draw a circle inside a UIImageView.
CAShapeLayer *circleLayer = [CAShapeLayer layer];
// Give the layer the same bounds as your image view
[circleLayer setBounds:CGRectMake(0.0f, 0.0f, [photoView bounds].size.width,
[photoView bounds].size.height)];
// Position the circle anywhere you like, but this will center it
// In the parent layer, which will be your image view's root layer
[circleLayer setPosition:CGPointMake(coordsFinal.x + 130, coordsFinal.y + 200)];
// Create a circle path.
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:
CGRectMake(0.0f, 0.0f, 50.0f, 50.0f)];
// Set the path on the layer
[circleLayer setPath:[path CGPath]];
// Set the stroke color
[circleLayer setStrokeColor:[color CGColor]];
// Set the stroke line width
[circleLayer setLineWidth:2.0f];
// Add the sublayer to the image view's layer tree
[[photoView layer] addSublayer:circleLayer];
I would like the circle to be empty, and to have only the border. How can I remove the fill?
Set the fill color to transparent (alpha 0)
[circleLayer setFillColor:[[UIColor colorWithWhite:0 alpha:0] CGColor]];
AKA...
[circleLayer setFillColor:[[UIColor clearColor] CGColor]];
(thanks Zev)
The CAShapeLayer manual page explicitly says set fillColor to nil to perform no fill operation. That is not the same as filling with a transparent color, which some have suggested doing, as the latter will overwrite existing contents making them clear/transparent, and should be considered a destructive operation.