CALayer hides its sublayer - iphone

I am trying to add a CALayer as a sublayer of another CALayer. However only the parent layer gets displayed. Here's my code:
//display a green square:
CALayer *shipContainer = [CALayer layer];
shipContainer.bounds = CGRectMake(0,0,200,200);
shipContainer.position = CGPointMake(600,500);
shipContainer.borderColor = [UIColor greenColor].CGColor;
shipContainer.borderWidth = 3;
//display a red dot inside the square:
CALayer *ship1 = [CALayer layer];
ship1.bounds = CGRectMake(0,0,20,20);
ship1.position = CGPointMake(600,500);
ship1.cornerRadius = 10;
ship1.backgroundColor = [UIColor redColor].CGColor;
[shipContainer addSublayer:ship1];
I then call [self.view.layer addSublayer:shipContainer]; but only the green square is displayed. Any thoughts?

As per documentation :
Position
The position property is a CGPoint that specifies the position of the
layer relative to its superlayer, and is expressed in the superlayer's
coordinate system.
so you need to change
ship1.position = CGPointMake(600,500);
so that ship1 can come in visible area. As superlayer has 200,200 as its bounds you need to make position's x and y less then these values.

Related

How to Draw Fixed Circle for Region in CLLocationManager when Radius Increased

I have Requirement Were i have to Draw a Circle for Region in CLLocationManager. i have Accomplished the Requirement with this Code as,
CLLocationDegrees latitude = 37.33492222;
CLLocationDegrees longitude = -122.03304215;
CLLocationCoordinate2D centerPoint = CLLocationCoordinate2DMake(latitude, longitude);
CLLocationDistance radius = 100.0;
CLRegion *region = [[CLRegion alloc]initCircularRegionWithCenter:centerPoint radius:radius identifier:#"Apple"];
[locationManager startMonitoringForRegion:region];
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Center the shape in self.view
circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor redColor].CGColor;
circle.lineWidth = 1;
circle.opacity = 0.5;
// Add to parent layer
[self.view.layer addSublayer:circle;
I have Got Out Put as
Problem is When i given the radius value as 100, i got result as ScreenShot. if i will give value as 200 then obviously the Circle Will Increase.
My Question is, I want the Circle as in same Size When any Value is Given say 200, 300 or 400.
Your question talks about increasing the radius, but in this forum, "radius" will generally be construed as the radius of the circle measured in points/pixels.
Clearly, that's not what you're asking for, though. Now, you've said you don't want to use a MKMapView, but I think it's a useful language to use when talking about your goals. Bottom line, you don't want to change the radius of the circle presented on the screen. You want to change the "span" (MKCoordinateSpan) of the "region" (MKCoordinateRegion) of the map which will be presented in the aforementioned circle.
There are two ways of approaching this problem of how to draw a circle of fixed size that represents a projection of a map (but you don't, for some reason, want a map).
Manually:
Define a few useful properties:
#property (nonatomic) MKCoordinateRegion region;
#property (nonatomic) CGRect frame;
The region defines the range of latitude and longitudes that will be presented in your user interface. The frame will be the screen coordinates in which we'll be presenting the those latitude and longitude points. Note, to do a visual representation of lat/long coordinates in a user interface you need both of these two aspects, something that defines what lat/long can be represented and something else that says where to put it on the screen.
So, the first question is how you define the region. Here I'm defining the center coordinate and defining a region as being 500 meters from that:
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(...);
self.region = MKCoordinateRegionMakeWithDistance(coordinate, 500.0, 500.0);
You asked "how do I change the radius [i.e. the span] of what is shown?" You do that by changing the region variable which says that range of lat/long will be represented in the UI.
Anyway, you can now you can add the circle to your screen:
- (void)addCircle
{
CGPoint center = CGPointMake(self.view.layer.bounds.size.width / 2.0, self.view.layer.bounds.size.height / 2.0);
CGFloat radius = self.view.layer.bounds.size.width * 0.40;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:0.0
endAngle:M_PI * 2.0
clockwise:YES];
self.frame = CGRectMake(center.x - radius, center.y - radius, radius * 2.0, radius * 2.0);
CAShapeLayer *layer = [CAShapeLayer layer];
layer.path = [path CGPath];
layer.strokeColor = [[UIColor darkGrayColor] CGColor];
layer.fillColor = [[UIColor whiteColor] CGColor];
layer.lineWidth = 3.0;
[self.view.layer addSublayer:layer];
self.view.backgroundColor = [UIColor lightGrayColor];
}
Write a routine to convert from a CLLocationCoordinate2D to a location on the screen:
- (CGPoint)determineCGPointFromCoordinate:(CLLocationCoordinate2D)coordinate
{
CGFloat percentX = (coordinate.longitude - self.region.center.longitude + self.region.span.longitudeDelta / 2.0) / self.region.span.longitudeDelta;
CGFloat percentY = 1.0 - (coordinate.latitude - self.region.center.latitude + self.region.span.latitudeDelta / 2.0) / self.region.span.latitudeDelta;
return CGPointMake(self.frame.origin.x + percentX * self.frame.size.width,
self.frame.origin.y + percentY * self.frame.size.height);
}
You can then add your points on your screen:
CGPoint center = [self determineCGPointFromCoordinate:coordinate];
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"spot.png"]];
imageView.center = center;
[self.view addSubview:imageView];
That yields us a view that looks like:
Even easier, in my opinion, is to reevaluate your decision to not to use a map view.
You could add a map view (see the Location Awareness Programming Guide)
Take your CAShapeLayer that you created, and rather than adding as a sublayer, you could instead clip the map to that circular CAShapeLayer:
[self.mapView.layer setMask:circleShapeLayer];
And, once you've done all of the adding of annotations to the view, you get something like this:
Personally, I like the map kit approach, as it gets you out of the business of manually calculating screen coordinates. But if you really don't want the map background there, you can do it manually.
I assume you want your circle to increase and decrease in size as the user zooms. That way, the circle will always be over the same area of land on the map.
In that case, use an MKCircle, created with +circleWithCenterCoordinate:radius:, conform to MKMapViewDelegate and set the stroke and fill in mapView:viewForOverlay:.

Combining and flipping CALayers as subviews to a CATransformLayer

Based on an earlier stackoverflow suggestion, I am trying to create a playing card using CALayers added to a CATransformLayer. The idea is to create front and back CALayers and combine them in the CATransformLayer which can then be rotated, flipped, etc, with the proper side automatically showing. Below is example code trying to create a single card with a green front and a red back.
The red back is flipped about the Y-axis so that it is facing away from the green front. The green front calayer has a "higher" Z location than the red back.
When I do the transform though, I am simply seeing the card in what appears to be its non-flipped state. Any ideas where I am going wrong here?
#define DEGREES_TO_RADIANS(angle) (angle * M_PI / 180.0)
- (void)viewDidLoad
{
[super viewDidLoad];
CATransformLayer *cardContainer = [CATransformLayer layer];
cardContainer.bounds = CGRectMake(0,0, 150,200);
CALayer *cardFront = [CALayer layer];
cardFront.frame = cardContainer.bounds;
cardFront.backgroundColor = [UIColor greenColor].CGColor;
cardFront.borderColor = [UIColor blackColor].CGColor;
cardFront.borderWidth = 2.0;
cardFront.cornerRadius = 30.0;
cardFront.zPosition = 2; // Put front of card on top relative to back of card
cardFront.doubleSided = NO;
[cardContainer addSublayer:cardFront];
CALayer *cardBack = [CALayer layer];
cardBack.frame = cardContainer.bounds;
cardBack.backgroundColor = [UIColor redColor].CGColor;
cardBack.zPosition = 1;
cardBack.doubleSided = NO;
// Flip cardBack image so it is facing outward and visible when flipped
cardBack.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(180),0.0,1.0,0.0);
[cardContainer addSublayer:cardBack];
UIView* cardView = [[UIView alloc] initWithFrame:cardContainer.bounds];
cardView.center = CGPointMake(self.view.center.x, self.view.center.y);
[cardView.layer addSublayer:cardContainer];
[self.view addSubview:cardView];
// Show the card flipped over (desired the red side to be showing, but instead shows green)
cardView.layer.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(180),0.0,1.0,0.0);
}
Found the problem, you must apply the transform to the sublayerTransform property, as in:
cardView.layer.sublayerTransform = ...

Gradually Decrease CAShapeLayer wrt radius

I have a UIImageView with image just like above so what i have to do is gradually decrease and increase the shape of the image.
For achieving above task obviously I have to apply mask so I have created a rounded CAShapeLayer and added to the UIImageView layer and its working fine if I change my radius it will show only that amount of image wrt radius.
My problem is how we can apply animation so it will show in animated form. Please guide me I am not able to achieve with keyframe animation.
Following are the code for masking wrt radius.
// Set up the shape of the circle
int rChange = 0; // Its doing proper masking while changing this value
int radius = 123.5-rChange;
CAShapeLayer *circle = [CAShapeLayer layer];
// Make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0+rChange, 0+rChange, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Configure the apperence of the circle
[circle setFillColor:[[UIColor blackColor] CGColor]];
[[self.originalImageView layer] setMask:circle];
self.originalImageView.layer.masksToBounds = YES;
where 123.5 is my maximum radius of the image
and originalImageView is myUIImageView
If all you want to do is show a brown circle with an animated varying radius, I suggest two changes:
Don't bother with an image. Just use a CAShapeLayer and set its fillColor to brown.
Set the layer's path once, to a circle with width and height of 1 point. Then use the CAShapeLayer's transform to vary the size. You can animate the transform.
For example, create the layer like this:
CGRect bounds = self.view.bounds;
circleLayer = [CAShapeLayer layer];
circleLayer.fillColor = [UIColor brownColor].CGColor;
    circleLayer.position = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
// Create a circle with 1-point width/height.
circleLayer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 1, 1)].CGPath;
// Use the layer transform to scale the circle up to the size of the view.
[circleLayer setValue:#(bounds.size.width) forKeyPath:#"transform.scale"];
Then you can change its size like this:
[circleLayer setValue:#(newSize) forKeyPath:#"transform.scale"];
That will implicitly (automatically) animate the size change using default parameters. If you want to use different animation parameters, you can explicitly animate the transform:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
animation.fromValue = [circleLayer valueForKeyPath:#"transform.scale"];
animation.toValue = #(newSize);
animation.duration = 3.0;
animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
// Important: change the actual layer property before installing the animation.
[circleLayer setValue:animation.toValue forKeyPath:animation.keyPath];
// Now install the explicit animation, overriding the implicit animation.
[circleLayer addAnimation:animation forKey:animation.keyPath];

use a CALayer as a UIImageView

I would like to create a CALayer bezier path in a circle shape. And I would like to set it like it was a UIImageView (to be able to move it with animation etc.) Is it possible? If so how can I do it? Thank you.
You could always add a CAShapeLayer to your view. During init, do something like (all untested):
CAShapeLayer *circle = [[CAShapeLayer alloc] init];
[self.layer addSublayer:circle];
Then, in drawRect, layoutSubViews, or some other place that seems appropriate to update the layout:
circle.fillColor = [[UIColor redColor].CGColor;
CGMutablePathRef p = CGPathCreateMutable();
CGPathAddEllipseInRect(p, NULL, self.bounds);
circle.path = p;
circle.bounds = self.bounds;
circle.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
I'm not sure what you mean by a "CALayer bezier path" but you have a couple of options here:
Create a subclass of UIView, where you draw your circle using UIBezierPath methods in the drawRect:. This view can then be added to another view as a subview and animated using standard methods. However, you are warned that this may affect performance, (as it has to keep re-drawing) so if that does turn out to be a problem...
Create a graphics context and draw your circle in there. Take an image from this graphics context and set it as the image for a UIImageView. Animate as in answer (1). This way you aren't redrawing all the time so performance would be better, but try with option 1 first as it is simpler, and if you have simple drawing code it shouldn't affect you too much.
For option 2, you begin a graphics context using UIGraphicsBeginImageContextWithOptions - you then do your drawing, and extract the image using UIGraphicsGetImageFromCurrentImageContext, then end the context using UIGraphicsEndImageContext. Those functions are all documented here
Sample code to do this:
CGFloat radius = 50;
CGSize size = CGSizeMake(radius*2,radius*2);
CGPoint centre = CGPointMake(radius,radius);
UIGraphicsBeginImageContextWithOptions(size,NO, 0.0);
UIBezierPath * solidPath = [UIBezierPath bezierPathWithArcCenter:centre radius:edgeSize/20 startAngle:0 endAngle:2 * M_PI clockwise:YES];
[solidPath closePath];
[[UIColor whiteColor] set];
[solidPath fill];
UIImage *circle = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imageView = [[UIImageView alloc] initWithImage:circle];

UIImageView XY Coordinates with Layer Movement

I move my UIImageView with layer around the screen. When at some point I'm trying to retrieve x and y coordinates of a moved imageView using imageView.frame.origin.x I receive original coordinates not the new ones after the move. How to get X and Y correctly?
Here is my code:
UIImageView *imageView = [[UIImageView alloc] initWithFrame:rect];
imageView.animationImages = [NSArray arrayWithArray:imgNameArr];
imageView.animationDuration = 2;
imageView.animationRepeatCount = 0;
[imageView startAnimating];
[self.view addSubview:imageView];
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.duration = speed;
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
CGMutablePathRef pointPath = CGPathCreateMutable();
CGPathMoveToPoint(pointPath, NULL, rect.origin.x, rect.origin.y);
CGPathAddLineToPoint(pointPath, NULL, x, y);
pathAnimation.path = pointPath;
CGPathRelease(pointPath);
[imageView.layer addAnimation:pathAnimation forKey:[NSString stringWithFormat:#"pathAnimation%#",objId]];
[imageView release];
Per the UIView documentation for the frame property:
Warning: If the transform property is not the identity transform, the
value of this property is undefined and therefore should be ignored.
The frame property also becomes undefined if you adjust that view's layer's transform or affineTransform properties (which are actually what the UIView alters when you set its transform). In Cocoa Touch transforms are applied at the time of compositing by the compositor. At present what you seem to get when a transform is applied is the original frame as though there were no transform, but as the docs say it's undefined so don't rely on that.
If you're just moving the view around with no other transformation, I recommend you use the view's center property rather than applying a transform. That will update your frame correctly. Based on empirical evidence, UIKit also seems smart enough that just adjusting the centre doesn't have any performance downside compared to applying a transform.