I have a UIView with a hierarchy of CALayers.
I am creating the layers using images, so I don't know the final size of the UIView until I have completed creating the layers.
How would I find out a final UIView size that allows touches to all the layers?
It's also complicated by the fact that a layer might be rectangular, and then rotated, so that it sticks out further. I wouldn't be able to use the frame size of the layer, because of its affine transform.
I do have a solution that makes one UIView the size of the screen, and I can add all my CALayers to this UIView, but I will have several sets of these layer hierarchies, which I would prefer to be in separate UIViews so that I can use UIView gestures to translate/scale/rotate.
You can use CGMutablePathRef for this. And then loop over all CALayer-s in view.layer.sublayers:
CGPathAddRect(path,
&transform,
CGRectOffset(i.bounds,
-CGRectGetMidX(i.bounds),
-CGRectGetMidY(i.bounds)));
Rectangle is offset to have its center in CGPointZero. The transform here is rotation of your layer (or scale) plus translation to layer's position. I.e.:
CGAffineTransform transform =
CGAffineTransformConcat(CATransform3DGetAffineTransform(i.transform),
CGAffineTransformMakeTranslation(i.position.x,
i.position.y));
Finally:
view.bounds = CGPathGetBoundingBox(path);
Bounds now are minimal and enclose all layers.
Related
Hi in my application i need a view which is of round shape instead of a rectangle shape. How to create a uiview object of round shape please let me know. Thanks in advance.
Technically, all UIView's will always be "rectangles", meaning they will be placed on the screen using {x, y} coordinates and they will be dimensioned with a height and a width (Making them rectangles). However, within the bounds of a UIView you can do a lot to make it appear as a circle. Here are some methods:
Use UIImageView and set it's Image to be an image of a circle. This is easy, but not very flexible.
Learn Core Graphics (also known as Quartz2D) and draw a circle in the UIView's -drawRect: method. Quartz 2D Programming Guide
Use a CAShapeLayer for the UIView's layers. CAShapeLayer Class Reference
There are certainly other methods but this should be a good start. If you need to detect touches within the circle, you can use either option 2. or 3. and keep a reference to the CGPathRef (or UIBezierPath) and use CGPathContainsPoint to determine if the touch is within the bounds of the circle and act accordingly.
You can set the cornerRadius of the layer of your view.
#import <QuartzCore/QuartzCore.h>
yourView.layer.cornerRadius = 20;
How is a CALAyer.frame related to its UIView.frame? (in this case specifically a UIImageView.frame).
Given that the values for a CALayer.frame potentially differ from its UIView.frame following a CAtransform3D transformation, do they no longer have any connection? (and subsequently, should I be more concerned about managing the dimensions of my CALayer rather than my UIImageView in my superview?).
If you are working with Core Animation and layers, you should focus on the following CALayer properties:
position
bounds
anchorPoint
transform
A quote from Apple Technical Q&A QA1620 available here:
Q: When I try to animate the frame of a CALayer nothing happens. Why?
A: The frame property of a CALayer is a derived property, dependent on
the position, anchorPoint, bounds and transform of the layer. Instead
of animating the frame, you should instead animate the position or
bounds, depending on what effect you are trying to accomplish.
I have a view in which I am doing some specific drawings in drawRect. These drawings are dynamic and are based on the view's width and height. Then, the view which contains it applies a rotation transformation to it. However this transformation seems to adjust the values for my view's frame which impacts my drawing in drawRect.
NSLog(#"before:%f,%f,%f,%f",button.frame.origin.x,button.frame.origin.y,button.frame.size.width,button.frame.size.height);
CGAffineTransform currentTransform = button.transform;
CGAffineTransform transformRotate = CGAffineTransformMakeRotation(degreesToRadians);
button.transform = transformRotate;
NSLog(#"after:%f,%f,%f,%f",button.frame.origin.x,button.frame.origin.y,button.frame.size.width,button.frame.size.height);
Here is the output:
before:50.000000,100.000000,150.000000,50.000000
after:65.849365,47.548096,118.301262,154.903809
Is this correct behaviour or am I applying the transformation incorrectly?
See the reference documentation on UIView's frame property;
frame
The frame rectangle, which describes the view’s location and size in
its superview’s coordinate system.
#property(nonatomic) CGRect frame
Warning
If the transform property is not the identity transform, the value of
this property is undefined and therefore should be ignored.
Despite that warning, things work as intended. Once a transformation (other that identity) is applied, the frame usually results into the projection rectangle of the original view.
But then again, you should not ignore that warning if you really want to find out about the frame with the applied transformation and use CGRectApplyAffineTransform for properly getting it.
Greetings... I come in peace, shoot to kill...
I have a container of type UIView (A Grid) and add many sublayers to the layer of the UIView (CALayers representing cells within the grid).
Within the Cell, I render many UIImages at different locations using CGContextDrawImage. I am well aware of the need to Translate and Scale, but the scaling (flipping) is with reference to the superviews (Grid) co-ordiantes and the origin of the Cell CALayer is not (0,0).
Therefore my rendering is all over the shop (mostly off screen). What is the best way to handle the translating and scaling when the UIImage is not at (0,0). Is there an established design pattern I should be using.
I solved this issue by just manually offsetting the translation by double the y origin.
There are methods to transfer a CGPoint from one UIView to another and from one CALayer to another. I cannot find a way to change a CGPoint from a UIView's coordinate system to a CALayer's coordinate system.
Does a layer and it's host view have the same coordinate system? Do I need to transform points between them?
Thanks,
Corey
[EDIT]
Thanks for the answer! It is hard to find information on the differences/similarities between CA on the iPhone and Mac. I am surprised I couldn't find this issue addressed directly in any Apple Documentation.
I was pursuing this answer to help with a bug I am troubleshooting, and this was so far my best guess, but I suppose I am barking up the wrong tree. If the coordinate systems are the same, then I have another issue...
The actual issue I am having can be found here on Stack Overflow:
layer hit test only returning layer when bottom half of layer is touched
The documentation for hitTest says:
/* Returns the farthest descendant of the layer containing point 'p'.
* Siblings are searched in top-to-bottom order. 'p' is in the
* coordinate system of the receiver's superlayer. */
So you need to do (something like this):
CGPoint thePoint = [touch locationInView:self];
thePoint = [self.layer convertPoint:thePoint toLayer:self.layer.superlayer];
CALayer *theLayer = [self.layer hitTest:thePoint];
In terms of Apple documentation, I found an "iPhone OS Note" in the Layer Coordinate System section of the Core Animation Programming Guide (2008-11-13).
The default root layer of a UIView instance uses a flipped coordinate system that matches the default coordinate system of a UIView instance–the origin is in the top-left and values increase down and to the right. Layers created by instantiating CALayer directly use the standard Core Animation coordinate system.
You can set the transform property of CALayers to the appropriate transform if you want to flip their coordinate system, but note that this will probably flip their drawing of the contents as well (I have not tested that, but it makes sense that this would be true). My assertion that the CALayer associated with a UIView shares the same coordinate system could in fact be entirely erroneous. It could also be that CALayers use the same coordinate system as UIViews (i.e. they're never flipped vertically), but I thought they were since CoreGraphics uses a flipped coordinate system relative to UIKit.
A simple way to test would be to add a screen-sized CALayer as the sublayer of a view's layer, then add another small CALayer as a sublayer of that. You could set it to show up at (0, 0, 320, 100) and see if it shows up on the top or the bottom of the iPhone's screen. This will tell you in which direction the Y axis goes for CoreAnimation.
- (void)viewDidLoad {
[super viewDidLoad];
CALayer *rootLayer = [CALayer layer];
rootLayer.frame = self.view.layer.bounds;
CALayer *smallLayer = [CALayer layer];
smallLayer.frame = CGRectMake(0, 0, rootLayer.bounds.size.width, 50);
smallLayer.backgroundColor = [UIColor blueColor].CGColor;
[rootLayer addSublayer:smallLayer];
[self.view.layer addSublayer:rootLayer];
}
I just performed this test myself, and it appears CALayers actually use the same coordinate system as UIViews, so my assertion that CALayer's flip the Y axis is definitely wrong. However, if you do drawing with CoreGraphics directly, be aware that CoreGraphics does use a flipped Y axis (though when drawing in a UIView subclass or, I assume, a CALayer delegate, the CoreGraphics context has already been flipped to match the UIView's (or CALayer's) coordinate system).
So the short answer, if you made it this far, is the coordinate system for CALayer should match the coordinate system for its corresponding UIView.
From my tests I've found out that sublayers share the same coordinate system as it's parent layer and therefore if you are adding sublayer to a UIView layer then they will share the same coordinate system.
In case you still want to flip the coordinate system so that it's origin is in the upper left corner you should use this transformation that flips the y axis.
(x,y,1) = (x', y', 1) * [1 0 0],[0 -1 0],[0 heigth 1]
which translated to code is:
[your_layer setAffineTransform:CGAffineTransformMake(1,0,0,-1,0,heigth)];
I believe the coordinate system of a UIView and its associated layer should be the same. However, if you create layers yourself, the coordinate system is flipped vertically.
Methods that fixing frames and points
- (CGRect) fixRect:(CGRect)rect inRect:(CGRect)container
{
CGRect frame = rect;
frame.origin.y = container.size.height - frame.origin.y - frame.size.height;
return frame;
}
- (CGPoint) fixPoint:(CGPoint)point fromPoint:(CGSize)containerSize
{
CGPoint frame = point;
frame.y = size.height - frame.y;
return frame;
}