How to Make Drawrect Work Right Now

Is there a way to make drawRect work right NOW?

Updates to the user interface happen at the end of the current pass through the run loop. These updates are performed on the main thread, so anything that runs for a long time in the main thread (lengthy calculations, etc.) will prevent the interface updates from being started. Additionally, anything that runs for a while on the main thread will also cause your touch handling to be unresponsive.

This means that there is no way to "force" a UI refresh to occur from some other point in a process running on the main thread. The previous statement is not entirely correct, as Tom's answer shows. You can allow the run loop to come to completion in the middle of operations performed on the main thread. However, this still may reduce the responsiveness of your application.

In general, it is recommended that you move anything that takes a while to perform to a background thread so that the user interface can remain responsive. However, any updates you wish to perform to the UI need to be done back on the main thread.

Perhaps the easiest way to do this under Snow Leopard and iOS 4.0+ is to use blocks, like in the following rudimentary sample:

dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// Do some work
dispatch_async(main_queue, ^{
// Update the UI
});
});

The Do some work part of the above could be a lengthy calculation, or an operation that loops over multiple values. In this example, the UI is only updated at the end of the operation, but if you wanted continuous progress tracking in your UI, you could place the dispatch to the main queue where ever you needed a UI update to be performed.

For older OS versions, you can break off a background thread manually or through an NSOperation. For manual background threading, you can use

[NSThread detachNewThreadSelector:@selector(doWork) toTarget:self withObject:nil];

or

[self performSelectorInBackground:@selector(doWork) withObject:nil];

and then to update the UI you can use

[self performSelectorOnMainThread:@selector(updateProgress) withObject:nil waitUntilDone:NO];

Note that I've found the NO argument in the previous method to be needed to get constant UI updates while dealing with a continuous progress bar.

This sample application I created for my class illustrates how to use both NSOperations and queues for performing background work and then updating the UI when done. Also, my Molecules application uses background threads for processing new structures, with a status bar that is updated as this progresses. You can download the source code to see how I achieved this.

Canvas' drawLine and drawRect not including end position?

Your question reveals an inconsistency in the drawing API of Android. You say

So, if I want to draw a vertical line that goes exactly from beginY to endY (inclusive), I have to do the following:

canvas.drawLine(constX, beginY, constX, endY + 1)

Notice that I didn't add 1 to the ending X position, only to ending Y (xstays the same as I want a vertical line).

The sentence in brackets is in my opinion the key to understand the nature of the inconsistency:

You also could add 1 to the ending X position (or even to the beginning X position!), and you would get exactly the pixel-wise identical line. Why is that? Because the underlying algorithm to transform from Android's "left pixel in/right pixel out"-concept to the "start and end pixel in"-concept is as follows (shown only for x, for y it's the same):

int left, top, right, bottom; // left/top pixel inclusive, right/bottom pixel exclusive
int x1, y1, x2, y2; // x1/y1 and x2/y2 pixels inclusive

if ( left == right ) {
x1 = x2 = left;
} else if ( left < right ) {
x1 = left;
x2 = right - 1;
} else {
x1 = right;
x2 = left - 1;
}

The result of this (in my opinion sub-optimal) conversion is that the line

canvas.drawLine(150, 150, 149, 160, paint);

is exactly parallel to

canvas.drawLine(150, 150, 151, 160, paint);

I think everybody would expect kind of inverse V, since the end points are separated by at least 1 pixel (their distance being two pixels) and the starting points are identical.

But tested on various devices and Android versions, the 1st perfectly vertical line is in pixel column 149 and the 2nd in column 150.

BTW: The correct transformation to use the "start and end pixel in"-concept is:

int x1, y1, x2, y2;           // x1/y1 and x2/y2 pixels inclusive

if ( x1 <= x2 )
++x2;
else
++x1;
if ( y1 <= y2 )
++y2;
else
++y1;
canvas.drawLine(x1, y1, x2, y2, paint);

How does drawRect and CGGraphicsContext work?

What happen's when I create a UIView and fill out drawRect then set another object's UIView to be that custom view? When is drawRect being called?

Adding a view to a 'live' view graph marks the view's frame as in need of display. The main run loop then creates and coalesces invalid rects and soon returns to invoke drawing. It does not draw immediately upon invalidation. This is a good thing because resizing, for example, would result in significant overdrawing -- redundant work which would kill many apps' drawing performance. When drawing, a context is created to render to -- which ultimately outputs to its destination.

Graphics Contexts are abstractions which are free to work optimally for their destination -- a destination could be a device/screen, bitmap, PDF, etc.. However, a context handle (CGContextRef) itself refers to a destination and holds a set of parameters regarding its state (these parameters are all documented here). These parameter sets operate like stacks: Push = CGContextSaveGState, Pop = CGContextRestoreGState. Although the context pointer isn't changing, the stack of parameter sets is changing.

As far as resources, see Programming with Quartz. It's 8 years old now, and was originally written for OS X -- but that ultimately doesn't matter a whole lot because the fundamentals of the drawing system and APIs really haven't evolved significantly since then -- And that is what you intend to focus on. The APIs have been extended, so it would be good to review which APIs were introduced since 10.4 and see what problems they solve, but it's secretly a good thing for you because it helps maintain focus on the fundamental operation of the drawing system. Note that some functionalities were excluded from iOS (e.g. often due to floating point performance and memory constraints, I figure), so every example may not be usable on iOS, but I know of no better guide.

Tip: Your drawing code can be easily reused on OS X and iOS if you use Quartz rather than AppKit/UIKit. Plus, the Quartz APIs have a lower update frequency (i.e. the APIs tend to be longer lived).

drawRect Doesn't want to draw anything

The documentation for UIView says to not call [super drawRect] if you're overriding UIView. Remove that call! It's been known to cause strange behaviour.

According to the docs:

If you subclass UIView directly, your implementation of this method
does not need to call super. However, if you are subclassing a
different view class, you should call super at some point in your
implementation.

NSView drawRect method not working?

I solved my problem thanks to @Sega-Zero's suggestion of using layerContentsRedrawPolicy and through a bit of research I found out that I couldn't directly call setNeedsDisplay in a thread, so I used performSelectorOnMainThread.

My Thread class:

- (void)updateLOOP{
while (true){

[self performSelectorOnMainThread: @selector(refreshClock)
withObject: nil
waitUntilDone: NO];

[NSThread sleepForTimeInterval:.1];
}
}
}

External update method:

- (void) refreshClock{
[self setNeedsDisplay:YES];
}

Now everything works as it should. The drawRect method is getting called without having to use [self display].

Animate creating of cgrect on drawRect not working

Instead of simply using the +animateWithDuration:animations: convenience method, use the more detailed +animateWithDuration:delay:options:animations:completion: method which allows you to pass the UIViewAnimationOptionAllowAnimatedContent option. Then change the height of your view in the animations block to let the view and bars height grow during the animation.

This option is specially here to tell CoreAnimation to redraw your content during the animation (calling drawRect: at each step of the animation). Without it, the animation is done on a snapshot image.


From the UIView class Reference documentation:

UIViewAnimationOptionAllowAnimatedContent

Animate the views by changing the property values dynamically and redrawing the view. If this key is not present, the views are animated using a snapshot image.


Alternatively, you could instead use one UIView for each of your bar, and use +animateWithDuration:animations: to animate those bars frame, instead of drawing them using CoreGraphics (as UIView animation methods are meant for animating properties of the view (frame, alpha, …) and its subviews, not primarily to animate the drawing done with CoreGraphics)

drawRect: isn't working

The problem is with the following line:

CGContextFillEllipseInRect(context, [self frame]);

You need to change this to the following:

CGContextFillEllipseInRect(context, [self bounds]);

Origin of frame is the left-top corner location of view in the parent. So, it can be any value within the limits of the size of parent view.

So, while drawing with frame, you will be offsetting Fillellipse rect location which happens to be beyond the visible rect of the handle view.

Hence, you were not able to see anything.



Related Topics



Leave a reply



Submit