Having Uiview Drawrect Occur in a Background Thread

Having UIView drawRect occur in a background thread

Yes it is possible. You will probably need to generate the content view as an image in the background and push the images into a nsdictionary or array.

So while your background is generating the images you can just show the image in drawrect function by rendering the image, providing the image has been generated.

A WWDC video that shows how to do it: WWDC 2012 session 211 - Building Concurrent User Interfaces on IOS. Here is the video description:

For a great user experience, it's essential to keep your application responsive while it renders complex UI elements and processes data. Learn how to use concurrency at the UIKit layer to perform drawing and other common operations without blocking user interaction.

Does UIView's -drawRect: have to be called on the main thread?

All UIKit calls should be done on the main thread.

Is it ok to create a UIView on a background thread?

From UIView:

Threading Considerations

Manipulations to your application’s user interface must occur on the main thread. Thus, you should always call the methods of the UIView class from code running in the main thread of your application. The only time this may not be strictly necessary is when creating the view object itself but all other manipulations should occur on the main thread.

The call to initWithFrame: is explicitly not thread safe. The call to setText: is likely not thread-safe, falling under the "manipulations" clause. These certainly are not promised to be thread-safe.

Do your work to figure out the data on a background thread. Then create your views on the main thread. If there are a huge number of views, you can try splitting up the work using several dispatch_async() calls onto the main queue. This may let the UI remain responsive; I haven't experimented extensively with it.

You may also want to consider switching from UIView to CALayer where possible. Most CALayer work can be done on background threads. If you have a huge number of views, that's probably inefficient anyway. If it's just that it takes a long time to calculate the data for the views, that suggests you're not properly separating Model and View information. The Model classes should calculate everything needed independently of creating the Views.

Drawing in the Background

Here's a framework to start with. The code assumes that the output view is a UIImageView. The background task uses a bitmapped memory context to create a new image for the output view. When the background drawing task completes, the main thread assigns the resulting image to the UIImageView.

The sequenceNumber is used to discard images that are out of date before they finish being drawn. One possible problem is that events arrive so quickly that every image is out of date before being finished. Solving that problem is left as an exercise for the reader.

@interface SomeClass()
@property (weak, nonatomic) IBOutlet UIImageView *someView;
@property (nonatomic) int64_t sequenceNumber;
@end

@implementation SomeClass

- (UIImage *)createImageUsingBitmapMemoryContextOfSize:(CGSize)size
{
// create the memory bitmap context
UIGraphicsBeginImageContext( size );
CGContextRef context = UIGraphicsGetCurrentContext();

// draw something
[[UIColor blueColor] set];
CGContextFillEllipseInRect( context, CGRectMake(0, 0, size.width, size.height) );

// create a UIImage
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

// return the image
return( result );
}

// Note: the following function should only be called on the main thread
- (void)updateInResponseToSomeEvent
{
self.sequenceNumber++;
int64_t sequenceNumber = self.sequenceNumber;

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^
{
UIImage *image = [self createImageUsingBitmapMemoryContextOfSize:self.someView.bounds.size];
dispatch_async( dispatch_get_main_queue(), ^
{
if ( sequenceNumber == self.sequenceNumber )
self.someView.image = image;
});
});
}

How do I draw text on a background thread in iOS?

Since iOS6 (might have been earlier) you can use those methods on a different thread, as long as you have created a new context using UIGraphicsBeginImageContext... on that same thread.

The drawRect: method defaults to the current context of their own thread.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), NO, 0);

UIFont* font = [UIFont systemFontOfSize:26];
NSString* string = @"hello";
NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:string attributes:@{NSFontAttributeName:font}];

[attributedString drawInRect:CGRectMake(0, 0, 100, 100)];

UIImage* image = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

[UIImagePNGRepresentation(image) writeToFile:@"/testImage.png" atomically:YES];

});

Run that on the simulator and it will output the result to the root directory of your hard drive.

Drawing in a background thread on iOS

I have never came across such situation on iPhone but on Mac I had similar problem once.

  1. Use CGLayer to delegate drawing activity of offline contexts.
  2. Try add timer for current NSRunLoop and on timed interval execute your graphic commands. It should look something like this...

...

kRenderFPS 25.0 //This is Maximum value

renderTimer = [[NSTimer timerWithTimeInterval:(1.0 / (NSTimeInterval)kRenderFPS) target:viewobject selector:@selector(RenderUI:) userInfo:nil repeats:YES] retain];

[[NSRunLoop currentRunLoop] addTimer:renderTimer forMode:NSDefaultRunLoopMode];

[[NSRunLoop currentRunLoop] addTimer:renderTimer forMode:NSModalPanelRunLoopMode];

[[NSRunLoop currentRunLoop] addTimer:renderTimer forMode:NSEventTrackingRunLoopMode];

//In view class
-(void)RenderUI:(id)param
{
[self setNeedsDisplayInRect:[self bounds]];
}

This should do the trick.

Also try to sample your process and check who is consuming the CPU. This will make UI responsive and very fast.

The performance problem you mentioned is can be due to something else. Try to cpu sample process. It will give you insight on who is actually taking CPU.

How to use GCD to draw image?

You can move your drawing code to a background thread. You just need to be sure to update the actual UI back on the main thread. There seems to be a few good resources on StackOverflow that cover this. Check out:
Having UIView drawRect occur in a background thread



Related Topics



Leave a reply



Submit