How do I use Core Graphics to draw inside of a instantiated UIImageView (No class files to override drawrect with -- just a reference to the instance)? I want to draw a simple point of fixed width (say, 10 pixels diameter) and of a certain color.
How do I use Core Graphics to … draw a simple point of fixed width (say, 10 pixels diameter) and of a certain color[?]
Set the fill color.
Move to the point.
Plot an arc, centered at the point, with the desired radius, giving a start angle of 0 and an end angle of 2 * M_PI.
Close the path.
Fill.
… draw inside of a instantiated UIImageView (No class files to override drawrect with -- just a reference to the instance)
I know that on the Mac, that's really not a good idea, as an NSView may redraw at any time if anything has set it as needing display (or told it directly to display), and would then clobber anything you'd drawn over it. No idea about the iPhone, but I do notice that UIView doesn't have methods like NSView's lockFocus and unlockFocus.
There's a more architectural reason not to do this: Drawing is the exclusive domain of views, and you're proposing to break that exclusivity and sprinkle some drawing code in (I'm assuming) a controller. Scattering code for a purpose in an object that doesn't have that purpose is a path to messy, unnavigable code.
Better to make a subview for each point and add those as subviews of the image view, or subclass UIImageView, give your instance of that subclass an array of the points to draw, and have the subclass's drawRect: call up to super before drawing its points.
The above solution, then, goes in drawRect:.
You can also just add a Core Animation layer to the view. Set the corner radius to half the size of the point you want (assuming a square) and it will render as a circle. Something like:
CALayer *layer = [CALayer layer];
[layer setBounds:CGRectMake(0.0f, 0.0f, 10.0f, 1.0f)];
[layer setCornerRadius:5.0f];
[layer setMasksToBounds:YES];
[layer setBackgroundColor:[[UIColor redColor] CGColor]];
// Center the layer in the view.
[layer setPosition:CGPointMake([view bounds].size.width/2,
[view bounds].size.height/2)];
// Add the layer to your view
[[view layer] addSublayer:layer];
Not Core Graphics exactly, but it's easy to implement.
Best Regards,
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;
I've got few pixels wide circle (circle is UIBezierPath based). I have to put an arc (which is basically UIView subclass with custom drawing) on the circle so the arc covers circle. I know how to calculate rotation of arc and position but something is not right. I mean I know the reason - it's beacause center property which is assigned to center of UIView, if it was center of the arc, everything would be great. But it's not.
I also know how to solve that. I have to calculate smaller radius where I will put arcs on. But how to do that. Basically it seems easy but because of the arc is in rectangular UIView it gets a bit harder. I'll show you some images so you can see the problem.
The easiest way to do this is to change the anchor point of each arc view's layer. You can read about the anchor point here if you don't already know about it.
You will need to add the QuartzCore framework to your build target and add #import <QuartzCore/QuartzCore.h>.
CGRect circleBounds = circleView.bounds;
topArcView.layer.anchorPoint = CGPointMake(.5, 0);
topArcView.layer.position = CGPointMake(CGRectGetMidX(circleBounds), 0);
bottomArcView.layer.anchorPoint = CGPointMake(.5, 1);
bottomArcView.layer.position = CGPointMake(CGRectGetMidX(circleBounds), CGRectGetMaxY(circleBounds));
leftArcView.layer.anchorPoint = CGPointMake(0, .5);
leftArcView.layer.position = CGPointMake(circleBounds.origin.x, CGRectGetMidY(circleBounds));
rightArcView.layer.anchorPoint = CGPointMake(1, .5);
rightArcView.layer.position = CGPointMake(CGRectGetMaxX(circleBounds), CGRectGetMidY(circleBounds));
Perhaps you could make the UIView subclass the same size and center point as the circle's rect. Then draw the arc in the correct location.
I have an application with a custom CALayer subclass. In this layer subclass i have overwritten the - (void) drawInContext:(CGContextRef)ctx method. This works fine and all my custom content is drawn.
Now I wanted to add a custom property which gets animated when it's content is changed. And I need to redraw while the animation is running. I added the property like given in the answer to the following question: Animating a custom property of CALayer subclass
And I also added an CABasicAnimation instance for this property to my layer:
CABasicAnimation* theAnimation=[CABasicAnimation animationWithKeyPath:#"moveContentToLeft"];
theAnimation.duration=1.0;
theAnimation.fromValue=[NSNumber numberWithFloat:0.0];
theAnimation.toValue=[NSNumber numberWithFloat:10.0];
[area addAnimation:theAnimation forKey:#"moveContentToLeft"];
In my draw method I have a NSLog statement in which I output the value of my property I'm animating.
My problem is that as soon as the animation starts all the content of the layer subclass is cleared. The NSLog outputs the values of my property that gets interpolated (so the drawInContext method) is called. But somehow the things it draws are not visible during the animation. At the end of the animation the original content gets visible again.
At the moment the animated property is not yet used while drawing the layer so I would expect that the normal content would get drawn again and I get the output of the NSLog with the interpolated values.
My layer is a child of another layer (inside a UIView). I tried to redraw the super layer at the end of the drawing method (with calling _parent setNeedsDisplay [parent is a ivar to the parent view]). This did not help. And I also have the feeling that this is not a good idea to do so.
I hope somebody can help me with this problem.
Update
Thanks to the example project from Matt Long I could see that my problem lies inside the drawing method. I extended his project so it shows the same problem. I think it is simpler to show than the original code ;)
- (void)drawInContext:(CGContextRef)ctx
{
NSLog(#"Getting called: Bob: %i", [self bob]);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(50 + [self bob], 50, 100, 100));
CGContextAddPath(ctx, path);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextSetLineWidth(ctx, 1.0);
CGContextStrokePath(ctx);
CGPathRelease(path);
[_test testInContext:ctx]; // here is the problem
}
The ivar _test is a custom class which contains some drawing code (at the moment it draws a green box). The problem now is that this green box will not be drawn while the animation runs. As soon as the animation is ended the green box is visible again. The extended example project: http://dl.dropbox.com/u/5426092/ArbitraryPropertyAnimationNew.zip
It's hard to say what's going wrong without seeing more code. I have an example app that animates a custom property and the position of a layer at the same time. You can take a look at my source and see what's different.
I have a UIView container that has two UIImageViews inside it, one partially obscuring the other (they're being composed like this to allow for occasional animation of one "layer" or another.
Sometimes I want to make this container 50% alpha, so what the users sees fades. Here's the problem: setting my container view to 50% alpha makes all my subviews inherit this as well, and now you can see through the first subview into the second, which in my application has a weird X-Ray effect that I'm not looking for.
What I'm after, of course, is for what the user currently sees to become 50% transparent-- the equivalent of flattening the visible view into one bitmap, and then making that 50% alpha.
What are my best bets for accomplishing this? Ideally would like to avoid actually, dynamically flattening the views if I can help it, but best practices on that welcome as well. Am I missing something obvious? Since most views have subviews and would run into this issue, I feel like there's some obvious solution here.
Thanks!
EDIT: Thanks for the thoughts folks. I'm just moving one image around on top of another image, which it only partially obscures. And this pair of images has to move together sometimes, as well. And sometimes I want to fade the whole thing out, wherever it is, and whatever the state of the image pair is at the moment. Later, I want to bring it back and continue animating it.
Taking a snapshot of the container, either by rendering its layer (?) or by doing some other offscreen compositing on the fly before alpha'ing out the whole thing, is definitely possible, and I know there are a couple ways to do it. But what if the animation should continue to happen while the whole thing's at 50% alpha, for example?
It sounds like there's no obvious solution to what I'm trying to do, which seems odd to me, but thank you all for the input.
Recently I had this same problem, where I needed to animate layers of content with a global transparency. Since my animation was quite complex, I discovered that flattening the UIView hierarchy made for a choppy animation.
The solution I found was using CALayers instead of UIViews, and setting the .shouldRasterize property to YES in the container layer, so that any sublayers would be flattened automatically prior to applying the opacity.
Here's what a UIView could look like:
#import <QuartzCore/QuartzCore.h> //< Needed to use CALayers
...
#interface MyView : UIView{
CALayer *layer1;
CALayer *layer2;
CALayer *compositingLayer; //< Layer where compositing happens.
}
...
- (void)initialization
{
UIImage *im1 = [UIImage imageNamed:#"image1.png"];
UIImage *im2 = [UIImage imageNamed:#"image2.png"];
/***** Setup the layers *****/
layer1 = [CALayer layer];
layer1.contents = im1.CGImage;
layer1.bounds = CGRectMake(0, 0, im1.size.width, im1.size.height);
layer1.position = CGPointMake(100, 100);
layer2 = [CALayer layer];
layer2.contents = im2.CGImage;
layer2.bounds = CGRectMake(0, 0, im2.size.width, im2.size.height);
layer2.position = CGPointMake(300, 300);
compositingLayer = [CALayer layer];
compositingLayer.shouldRasterize = YES; //< Here we turn this into a compositing layer.
compositingLayer.frame = self.bounds;
/***** Create the layer three *****/
[compositingLayer addSublayer:layer1]; //< Add first, so it's in back.
[compositingLayer addSublayer:layer2]; //< Add second, so it's in front.
// Don't mess with the UIView's layer, it's picky; just add sublayers to it.
[self.layer addSublayer:compositingLayer];
}
- (IBAction)animate:(id)sender
{
/* Since we're using CALayers, we can use implicit animation
* to move and change the opacity.
* Layer2 is over Layer1, the compositing is partially transparent.
*/
layer1.position = CGPointMake(200, 200);
layer2.position = CGPointMake(200, 200);
compositingLayer.opacity = 0.5;
}
I think that flattening the UIView into a UIImageView is your best bet if you have your heart set on providing this feature. Also, I don't think that flattening the image is going to be as complicated as you might think. Take a look at the answer provided in this question.
Set the bottom UIImageView to have .hidden = YES, then set .hidden = NO when you setup a cross-fade animation between the top and bottom UIImageViews.
When you need to fade the whole thing, you can either set .alpha = 0.5 on the container view or the top image view - it shouldn't matter. It may be computationally more efficient to set .alpha = 0.5 on the image view itself, but I don't know enough about the graphics pipeline on the iPhone to be sure about that.
The only downside to this approach is that you can't do a cross-fade when your top image is set to 50% opacity.
A way to do this would be to add the ImageViews to the UIWindow (the container would be a fake one)
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;
}