iPhone: drawRect called once - iphone

I'm calling setNeedsDisplayInRect from an NSTimer function every two seconds. This works perfectly, and draws a square in a random position, with a random color. However, in some circumstances I would like to draw a square x number of times in the NSTimer function (using a for loop) - but, after numerous error testing it seems that drawRect is only called once even though I am running setNeedsDisplayInRect x number of times? I would love some help as I've been trying to figure out this problem all day long. Carl.
Edit below is my code...
View
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, currentColor.CGColor);
CGContextSetFillColorWithColor(context, currentColor.CGColor);
CGContextAddRect(context, redrawRect);
CGContextDrawPath(context, kCGPathFillStroke);
}
-(void)drawInitializer
{
int x = (int)arc4random() % [self.xCoordinates count];
int y = (int)arc4random() % [self.yCoordinates count];
self.currentColor = [UIColor randomColor];
self.redrawRect = CGRectMake ([[self.xCoordinates objectAtIndex:x] intValue], [[self.yCoordinates objectAtIndex:y] intValue], 25, 25);
[self setNeedsDisplayInRect:redrawRect];
}
Controller
- (void) handleTimer: (NSTimer *) timer
{
for(int i=0; i<5; i++)
{
[self.squareView drawInitializer];
}
}

You could refactor the code so that you have a simple class that:
stores color
stores position
has a method to generate random positions, colors, ...
You could then create as many instances as you want and push them into a NSMutableArray.
This way you can iterate over that list, and draw each object in your draw routine.
Whenever you add/delete/modify one of your objects, call setNeedsDisplay:

you could create a class that has the ability to draw itself, then create as many instances of this class that you need.

Related

Draw lines with centers without them overlap

I have a problem when i try to join lines. Here is a picture:
and I like that it looks like this:
and my code is:
- (void) drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
const CGFloat *components = CGColorGetComponents(self.lineColor.CGColor);
CGFloat red;
CGFloat green;
CGFloat blue;
CGFloat alpha;
if(CGColorGetNumberOfComponents(self.lineColor.CGColor) == 2)
{
red = 1;
green = 1;
blue = 1;
alpha = 1;
}
else
{
red = components[0];
green = components[1];
blue = components[2];
alpha = components[3];
if (alpha <= 0) alpha = 1;
}
// set the stroke color and width
CGContextSetRGBStrokeColor(context, red, green, blue, alpha);
CGContextSetLineWidth(context, 2.0);
if (self.points.count >0) {
BezierPoint *firstPoint = [self.points objectAtIndex:0];
CGContextMoveToPoint(context, firstPoint.center.x, firstPoint.center.y);
int index = 0;
for (BezierPoint *point in self.points ) {
if(index == 0){
index++;
continue;
}
CGContextAddLineToPoint(context, point.center.x, point.center.y);
}
CGContextAddLineToPoint(context, firstPoint.center.x, firstPoint.center.y);
}
CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 0.0);
CGContextDrawPath(context, kCGPathFillStroke);
}
the problem I have is that for every point you add, the lines overlap and I would like that as I stay I add points for geometric figure that globulins do not overlap
If anyone can help me I will thank!!
I would move the logic that determines the order the lines are drawn to your view controller, and access the data needed to draw the view with from a delegate using a protocol. Views should not own their data. For example, in your .h try something like;
#import <UIKit/UIKit.h>
#protocol CowDrawViewDataSource
-(NSArray *) pointsToDraw;
#end
#interface CowDrawView : UIView
#property (nonatomic , weak) id <CowDrawViewDataSource> dataSource;
#end
Then, (and I hope this does answer you question) in the view controller, you set as your views delegate, construct the
-(NSArray *) pointsToDraw;
method, in such a way, to send your array of points in an order in from which they can be drawn. Say by finding the point the top/left point, then the top/right, then bottom/right, bottom/left. (although such an approach might not work so well with irregular polygons, and shapes that are contiguous, but not polygons.)
After you figure out how to get the array in the order you want, you can get it in your drawRect by sending a message to it's delegate such as
NSArray *points = [self.dataSource pointToDraw];
with little re-work in the drawRect it's self.
The easy way to correct your problem is first draw only single line segments between two points and see the results. Then you can form loops. I guess you are not assigning perfect coordinates points or your loop must consist of :
CGContextAddLineToPoint(context, point.center.x, point.center.y);
And then,
CGContextMoveToPoint(context, point.center.x, point.center.y);
This shows that first draw line and move to next point.
Hope this helps.

Questions regarding drawing in iOS

I want to create a little navigation bar on the bottom of my iPhone screen where I basically just draw 5 rectangles next to each other. However, only the active page should have the opacity of 1.0 and others should be slightly transparent (alpha=0.4). That is what I already have.
Now my questions:
How do I change the opacity of the individual elements of my navigation ? Do I have to redraw the whole thing whenever something changes ? So I would have global variables called nav1Opacity,nav2Opacity...nav5Opacity, change them when the navigation changes and redraw the whole thing ? If so,
How do I clear what I have drawn before ? Do i create the rectangles as CGMutablePathRef()s and store them in an array and clear them all ?
I have very little experience with drawing, so I am a little lost there. I have read the documentation of Quartz2d and contexts, but still, as I mentioned I have not fully figured out how it works.
Here is some code I use:
-(void)drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
//save state
CGContextSaveGState(context);
//NAV1
CGMutablePathRef nav1 = CGPathCreateMutable();
CGPathAddRect(nav1, NULL, CGRectMake(0 , 15, 64, 10));
UIColor *blueColor = UIColorFromRGB(0x35BFE5,0.1);
CGColorRef bC = [blueColor CGColor];
[colorArray addObject:(__bridge id)bC];
[navArray addObject:(__bridge id)nav1];
CGPathRelease(nav1);
/*
*
*
... I do this for all 5 navigation elements
*
*
*/
//then I go through all my rectangles and add/fill them
for(int i=0;i<[navArray count];i++){
CGContextAddPath(context, (__bridge CGMutablePathRef)[navArray objectAtIndex:i]);
CGContextSetFillColorWithColor(context, (__bridge CGColorRef)[colorArray objectAtIndex:i]);
CGContextFillPath(context);
}
// restore to last saved context state
CGContextRestoreGState(context);
}
//and this is how I redraw
-(void)updateActiveNav{
[navArray removeAllObjects];
[colorArray removeAllObjects];
[self setNeedsDisplay];
}
If you actually draw the interface, you will have to redraw it whenever it changes, at least the rectangles that change. You can reuse CGPaths, but they aren't graphical objects on screen, they are just instructions on how to draw shapes, so you will have to draw everything again.
That being said, you can use individual UIViews instead, which represent onscreen objects, and you can change their opacity, which will reflect on screen.
This is the problem:
for(int i=0;i<[navArray count];i++){
CGContextAddPath(context, (__bridge CGMutablePathRef)[navArray objectAtIndex:i]);
CGContextSetFillColorWithColor(context, (__bridge CGColorRef)[colorArray objectAtIndex:i]);
CGContextFillPath(context);
}
You are added the path to your context, then setting a fill color, then filling it. Then without restoring your context you're doing it again so your filling the previous path and the new one. Its not the drawing from the last drawRect its the drawing from here. Try something like below so that after you fill the path you reset the context and draw the next block by itself instead of both the 1st and 2nd etc.
for(int i=0;i<[navArray count];i++){
CGContextSaveGState(context);
// Add Path, Fill
CGContextRestoreGState(context);
}

iOS: CGContextRef drawRect does not agree with input

Background : I would like to draw blocks when the user touch up somewhere. If the block is there, I want to erase it. I manage the blocks by using NSMutableArrayto keep track of points where the block should go. Every time user touches, it will determine if the touch place already contained a block or not and manage the array accordingly.
Problem : I got a very weird feedback from this. First of all, everything in the array works as I wanted. The problem comes when the user wanted to erase a block. While the array is maintained correctly, the drawing seems to ignore the change in the array. It will not remove anything but the last dot. And even that flashes toggles on and off when the user clicked elsewhere.
Here is the code :
- (void)drawRect:(CGRect)rect
{
NSLog(#"drawrect current array %#",pointArray);
for (NSValue *pointValue in pointArray){
CGPoint point = [pointValue CGPointValue];
[self drawSquareAt:point];
}
}
- (void) drawSquareAt:(CGPoint) point{
float x = point.x * scale;
float y = point.y * scale;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, x, y);
CGContextAddLineToPoint(context, x+scale, y);
CGContextAddLineToPoint(context, x+scale, y+scale);
CGContextAddLineToPoint(context, x, y+scale);
CGContextAddLineToPoint(context, x, y);
CGContextSetFillColorWithColor(context, [UIColor darkGrayColor].CGColor);
CGContextFillPath(context);
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *aTouch = [touches anyObject];
CGPoint point = [aTouch locationInView:self];
point = CGPointMake( (int) (point.x/scale), (int) (point.y/scale));
NSLog(#"Touched at %#", [NSArray arrayWithObject: [NSValue valueWithCGPoint:point]]);
NSValue *pointValue = [NSValue valueWithCGPoint:point];
int i = [pointArray indexOfObject:pointValue];
NSLog(#"Index at %i",i);
if (i < [pointArray count]){
[pointArray removeObjectAtIndex:i];
NSLog(#"remove");
}else {
[pointArray addObject:pointValue];
NSLog(#"add");
}
NSLog(#"Current array : %#", pointArray);
[self setNeedsDisplay];
}
scale is defined as 16.
pointArray is a member variable of the view.
To Test : You can drop this into any UIView and add that to the viewController to see the effect.
Question : How do I get the drawing to agree with the array?
Update + Explanation: I am aware of the cost of this approach but it is only created for me to get a quick figure. It will not be used in the real application, thus, please do not get hung up about how expensive it is. I only created this capability to get a value in NSString (#"1,3,5,1,2,6,2,5,5,...") of a figure I draw. This will become more efficient when I am actually using it with no redrawing. please stick to the question asked. Thank you.
I don't see anywhere where you are actually clearing what you drew previously. Unless you explicitly clear (such as by filling with UIRectFill() - which, as an aside, is a more convenient way to draw rectangles than filling an explicit path), Quartz is going to just draw over your old content, which will cause unexpected behavior on attempts at erasure.
So... what happens if you put at the beginning of -drawRect::
[[UIColor whiteColor] setFill]; // Or whatever your background color is
UIRectFill([self bounds]);
(This is of course horrendously inefficient, but per your comment, I am disregarding that fact.)
(As a separate aside, you probably should wrap your drawing code in a CGContextSaveGState()/CGContextRestoreGState() pair to avoid tainting the graphics context of any calling code.)
EDIT: I always forget about this property since I usually want to draw more complex backgrounds anyway, but you can likely achieve similar results by setting clearsContextBeforeDrawing:YES on the UIView.
This approach seems a little weird to me because every time the touchesEnded method is called you need to redraw (which is an expensive operation) and also need keep track of the squares. I suggest you subclass an UIView and implement the drawRect: method, so the view knows how to draw itself and implement the touchesEnded method in your view controller, where you can check if you have touched a squareView then remove it from view controller's view otherwise create a squareView and add it as subview to the view controller's view.

MKMapView - Redrawing overlays in the most efficient way

I have an MKMapView with 2 overlays. They represent 1. The route someone has taken. 2. A series of circular regions of interest. In order to update either of the overlays I update their data, then invalidate their related view:
[(RoutePolyline *)self.overlay appendPolylines:polylines];
MKOverlayPathView *overlayView = (MKOverlayPathView *)[self.mapView viewForOverlay:self.overlay];
[overlayView invalidatePath];
The problem is that adding a single line to my RoutePolyline and invalidating its related view causes every overlay view to be redrawn about 80 times. Given that this happens for every location update this is incredibly expensive.
Here is the code from the only method in my RouteOverlayView:
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context
{
RoutePolyline *routePolyline = (RoutePolyline *)self.overlay;
int polylineCount = [routePolyline.polylines count];
for (int i = 0; i < polylineCount; i++)
{
MKPolyline *polyline = [routePolyline.polylines objectAtIndex:i];
CGPathRef path = [MKUtils newPolyPathWithPolyline:polyline overlayView:self];
if (path)
{
[self applyFillPropertiesToContext:context atZoomScale:zoomScale];
CGContextBeginPath(context);
CGContextAddPath(context, path);
CGContextDrawPath(context, kCGPathStroke);
[self applyStrokePropertiesToContext:context atZoomScale:zoomScale];
CGContextBeginPath(context);
CGContextAddPath(context, path);
CGContextStrokePath(context);
CGPathRelease(path);
}
}
}
What could be causing these extra redraws?

Draw line or rectangle on cocos2d Layer

Can you let me know what is the best way to draw a line or rectangle on a scene layer using Cocos2d ios4 iphone.
So far have tried Texture2d, but it is more like a paint brush and is not so good. Tried drawing a line using draw method, but previous line disappears on drawing another line.
Basically want to draw multiple horizontal ,vertical, oblique beams. Please suggest. Any code would help a lot .
The code to draw using texture is below:
CGPoint start = edge.start;
CGPoint end = edge.end;
// begin drawing to the render texture
[target begin];
// for extra points, we'll draw this smoothly from the last position and vary the sprite's
// scale/rotation/offset
float distance = ccpDistance(start, end);
if (distance > 1)
{
int d = (int)distance;
for (int i = 0; i < d; i++)
{
float difx = end.x - start.x;
float dify = end.y - start.y;
float delta = (float)i / distance;
[brush setPosition:ccp(start.x + (difx * delta), start.y + (dify * delta))];
[brush setScale:0.3];
// Call visit to draw the brush, don't call draw..
[brush visit];
}
}
// finish drawing and return context back to the screen
[target end];
The rendering is not good esp. with oblique lines as the scaling affects the quality.
Cheers
You could create a separate layer and call the draw method like this:
-(void) draw
{
CGSize s = [[Director sharedDirector] winSize];
drawCircle( ccp(s.width/2, s.height/2), circleSize, 0, 50, NO);
It's for a circle but the principle is the same. This is from a project I made a while back and it worked then. Don't know if anything has changed since.
You need to add draw method to your layer:
-(void) draw {
// ...
}
Inside it you can use some openGL like functions and cocos2d wrapper methods for openGL.
Hint: other methods can be called inside draw method.
But keep in mind that using other name for method
containing openGL instructions, that's not called inside draw mentioned above simply won't work.
Even when called from update method or other method used by scheduleUpdate selector.
So you will end up with something like this:
-(void) draw {
glEnable(GL_LINE_SMOOTH);
glColor4ub(255, 0, 100, 255);
glLineWidth(4);
CGPoint verts[] = { ccp(0,200), ccp(300,200) };
ccDrawLine(verts[0], verts[1]);
[self drawSomething];
[self drawSomeOtherStuffFrom:ccp(a,b) to:ccp(c,d)];
[someObject doSomeDrawingAsWell];
}
For more information check out cocos2d-iphone programming guide :
http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:draw_update?s[]=schedule#draw