How to redraw only a portion of the presenting framebuffer? - ios

I want to implement a simple paint feature on iOS.
I have some framebuffers/textures wired up to get the desired final composition.
Redrawing the whole screen is a huge overhead. I can determine a rectangle that has changed.
So what is the best way to draw only that portion?

glScissor(0, 0, 50, 50);
glEnable(GL_SCISSOR_TEST);

Related

How I could get access to circles drawn in drawRect?

So, I have custom view, in which I draw 3 circles. How I could pass a "colorView" as an argument? Other man added all of this views from IB (not from code). And how could I change frames or visibility of these circles? Could I add variables for them? Thank you.
- (void)drawRect:(CGRect)rect
{
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(con, colorOfView.CGColor);
CGContextFillEllipseInRect(con, CGRectMake(0,10,85,85));
CGContextSetFillColorWithColor(con, [UIColor clearColor].CGColor);
CGContextSetBlendMode(con, kCGBlendModeClear); // erase
CGContextFillEllipseInRect(con, CGRectMake(62, -1, 35, 35));
CGContextSetFillColorWithColor(con, colorOfView.CGColor);
CGContextSetBlendMode(con, kCGBlendModeNormal);
CGContextFillEllipseInRect(con, CGRectMake(65,0,30,30));
}
Now for update color I use other method:
- (void)setCustomTitle:(NSString *)title andIcon:(NSString *)iconName andCircle:(UIColor *)color
{
self.titleLabel.text = title;
self.iconImage.image = [UIImage imageNamed:iconName];
colorOfView = color;
self.circleView.backgroundColor = color;
[self setNeedsDisplay];
}
You should create a property for your color:
#property (nonatomic, strong) UIColor *colorOfView;
And then change colorOfView = color to self.colorOfView = color
And change CGContextSetFillColorWithColor(con, colorOfView.CGColor); to CGContextSetFillColorWithColor(con, self.colorOfView.CGColor);
Expanding upon the answers/comments you have received already ...
The easy way of doing this which will definitely work
If you want to animate your circles, then you will need to call drawRec every 1/50th of a second or so. The way to do this is to use NSTimer to "wait" your loop between every frame. This can be in the ViewController or the View. After you draw your View, set up another NSTimer instance to wait another 20 milliseconds (not repeating). The delegate triggers a redraw (setNeedsDisplay), which then instantiates another NSTimer to wait another 20 milliseconds and so on. If you want the animation to stop, you don't instantiate another NSTimer.
Don't just keep calling setNeedsRefresh without a delay, or you will find your ViewController will become unresponsive as all CPU cycles will be spent refreshing. And it will run at very different speeds on different hardware.
As for how you gain access to the circle size etc in your ViewController, you have two choices. Firstly, you could possibly move the code which needs the circle size into drawRec in which case you would also handle the NSTimer instance (and delegate) in your View and your problem goes away.
Secondly, (and probably better) you could set up the circle sizes, colours etc as properties in your View .h file. Use Interface Builder (the storyboard) and the Assistant Editor to get a reference to your View class in your ViewController as an IBOutlet. You can then directly read or set these view properties from within your ViewController. (It is much easier to create a reference to your View in your ViewController than vice-versa). Your NSTimer will be in your ViewController, not your View.
If your circles are large, then you may be disappointed in the refresh rate.
The slightly harder but much faster way of doing most of this
As Dan pointed out, there may be a better way. You could set up your circles as the images in an ImageView (very similar to a View, but be aware that you cannot perform a drawRec against an ImageView). You can then animate the position, size, transparency and rotation by setting the frame of the UIImageView. The advantage is that this is far more efficient (the transformations are essentially done in hardware). For size, draw a large circle and scale down so you don't get jaggies. As far as I know, you can't directly change the colour, but you can probably set up a cut-out in the shape of a circle and use it as a mask against a rectangle which you use memset (or something fast) to fill in the correct colour. This is pretty complicated to do, if you need to change colours I would definitely try option 1 first. You should read https://developer.apple.com/library/ios/qa/qa1708/_index.html to see how this option works in general and why it is better than Option 1.
So in summary I would try Option 1 first, and see if it is fast enough to give you smooth animation. If it doesn't, try Option 2 which (apart from changing colours) is easier than it probably sounds.
Good luck

What's the best most CPU efficient way to draw views with a lot of animations in iOS?

I'm trying to draw a graphic equaliser for an iOS project.
The equaliser will have 7 bars, representing different frequency bands, than move up and down based on real-time audio data.
Can anyone suggest the best way to approach this in iOS?
New frequency data comes in at about 11Hz, and so the bars would have to animate to a new size 11 times per second.
Do I create a UIView for each bar and dynamically resize it's frame height?
Do I draw the bars as thick CGStrokes and redraw them within the parent view as needed?
Another option?
Thanks in advance
You want to use Core Animation. The basic principle is to create a bunch of "layer" objects, which can either be bitmap images, vector shapes, or text. Each layer is stored on the GPU and most operations can be animated at 60 frames per second.
Think of layers like a DOM node in a HTML page, they can be nested inside each other and you can apply attributes to each one similar to CSS. The list of attributes available matches everything the GPU can do efficiently.
It sounds like you want vector shapes. Basically you create all your shapes at startup, for example in the awakeFromNib method of a UIView subclass. For simple rectangles use CALayer and set a background colour. For more complicated shapes create a CAShapeLayer and a UIBezierPath, then apply it with shapeLayer.path = bezierPath.CGPath;.
Then, whenever you want to change something, you apply those changes to the layer object. For example, here I'm rotating a layer with a 1 second linear animation:
[CATransaction begin];
[CATransaction setAnimationDuration:1];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
[self.needleLayer setValue:[NSNumber numberWithFloat:DegreesToRadians(degrees) forKeyPath:#"transform.rotation.z"];
[CATransaction commit];
// you'll want to declare this somewhere
CGFloat DegreesToRadians(CGFloat degrees)
{
return degrees * M_PI / 180;
}
More complicated animations, eg a series of changes scheduled to execute back to back, can be done using a CAKeyframeAnimation: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/CreatingBasicAnimations/CreatingBasicAnimations.html
Note Core Animation only does 2D graphics. Apple has Scene Kit which is basically the same thing for 3D, but so far it's only available on OS X. Hopefully iOS 8 will include it, but until then if you want 3D graphics on iOS you need to use Open GL.
CALayers which you resize on demand would probably be the most efficient way to do this, if the bars are solid colours. This allows you to optionally animate between sizes as well.
View resizing triggers off layout cycles, which you don't want. Drawing using CG calls is pretty slow.
Obviously the only real way to find out is to profile (on a device) using instruments and the core animation tool. But from experience, layer sizing is faster than drawing.
Definitely not a UIView for each - instead, a single UIView for the entire equalizer. Fill in the drawRect method with the appropriate CG calls to draw whatever is required. You can queue the view to refresh as needed with the appropriate data. Tapping into CADisplayLink will help you get the frame-rate you're looking for.
https://developer.apple.com/library/ios/documentation/QuartzCore/Reference/CADisplayLink_ClassRef/Reference/Reference.html
NOTE: You can also subclass CALayer and draw in it if you prefer something lighter-weight than UIView but I think you'll be fine with the former.

In which case does drawRect not receive the full frame of the UIView?

I am writing a patch bay control, and I'm using UIViews to draw the links between the patches.
These links are subviews of a large UIView, itself a subview of a UIScrollView.
Links can become quite large, typically four times the size of the screen.
Links need to be redrawn when one of their end patch moves.
However, there are situations where only a part of the link is visible.
Instruments indicates that most of the time is spent in my QCLink drawRect method.
I have checked that the drawRect method is called with the full bounds of the QCLink each time that this QCLink need to be redrawn.
Is this a situation where I should only have to redraw a part of the UIView (the rect argument in drawRect:) ?
Here are some screen captures to help you understand the problem I'm facing.
In which case does drawRect not receive the full frame of the UIView?
Your draw implementation should always be prepared to draw a portion of your view. For some tasks, the default clipping is good.
Just follow the view invalidation process -- if you invalidate a rect, then the view system traverses views and asks them to draw what lies in that rect (considering things like opacity). That rect may be (composed of) a union of rects, but that too may be clipped by the system.
So you are probably overdrawing or doing redundant drawing -- consider how that may be reduced. For starters, you might want to put all your cords in one view, and do everything you can to minimize surfaces which are not opaque. After that, you should determine where you are overdrawing. Quartz Debug can point out these redundant draws. You should be using setNeedsDisplayInRect: rather than setNeedsDisplay, especially where drawing times are critical.
You are probably looking for a CATiledLayer. Tiled layers only the draw the parts which are on screen and need to be rendered, making them great for views that may be too large for the screen or where you are going to pinch-to-zoom. They are core to how views like UIWebView are rendered.
To switch your view to use a tiled layer, you just have to declare this method:
+ (Class)layerClass
{
return [CATiledLayer class];
}
You will then start seeing calls to drawRect: with values like (0, 0, 256, 256), (0, 256, 256, 256)...
When moving things around, you can get an extra performance win by calling setNeedsDisplayInRect: instead of setNeedsDisplay. This will limit drawing to the invalidated rect.
It's always the full rectangle. What about using CAShapeLayers instead?
Something like:
CAShapeLayer * link = [ CAShapeLayer layer ] ;
link.strokeColor = [ [ UIColor greenColor ] CGColor ] ;
CGMutablePathRef p = CGPathCreateMutable() ;
CGPathMoveToPoint( p, NULL, start ) ;
CGPathAddLineToPoint( p, NULL, end ) ;
link.path = path ;
[ parentView.layer addSublayer:link ] ;

How to animate size of custom drawn text?

I'm working on a educational app involving complex scripts in which I paint parts of different 'letters' different colours. UILabel is out of the question, so I've drilled down into Core Text and am having a surprisingly successful go of painting glyphs in CALayers.
What I haven't managed to do is animate the size of my custom drawn text. Basically I have text on 'tiles' (CALayers) that move around the screen. The moving around is okay, but now I want to zoom in on the ones that users press.
My idea is to try to cache a 'full resolution' tile and then draw it to scale during an animation of an image bounds. So far I've tried to draw and cache and then redraw such a tile in the following way:
UIGraphicsBeginImageContext(CGSizeMake(50, 50));
CGContextRef context = UIGraphicsGetCurrentContext();
//do some drawing...
myTextImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Then in [CALayer drawInContext:(CGContextRef)context],
I call [myTextImage drawAtPoint:CGPointZero].
When I run the app, the console shows <Error>: CGContextDrawImage: invalid context 0x0. Meanwhile I can perfectly while just continue to draw text in context in the same method even after that error is logged.
So I have two questions: (1) Why isn't this working? Should I be using CGBitmap instead?
And more important: (2) Is there a smarter way of solving the overall problem? Maybe storing my text as paths and then somehow getting CAAnimation to draw it at different scales as the bounds of the enclosing CALayer change?
Okay, this is much easier than I thought. Simply draw the text in the drawInContext: of a CALayer inside of a UIView. Then animate the view using the transform property, and the text will shrink or expand as you like.
Just pay attention to scaling so that the text doesn't get blocky. The easiest way to do that is to make sure the transform scale factors do not go above 1. In other words, make the 'default' 1:1 size of your UIView the largest size you ever want to display it.

C4Shape, setFrame doesn't change the size of the shape (C4Framework)

I'm playing around with C4, and can't seem to figure out why my shapes don't animate. If I create a shape like so:
self.theShape = [C4Shape ellipse:CGRectMake(100, 100, 2, 2)];
... and later call
[theShape setFrame:CGRectMake(200, 200, 50,50)];
The shape doesn't change size. The implementation suggests that it should, but I'm not seeing it. Is there anything I'm doing wrong? Is it because I'm not updating the canvas?
In C4, calling setFrame: on a C4Shape object will not scale it.
The reason being is that changing the frame of a CAShapeLayer's view will not change the underlying bezier shape itself... Unlike calling the same thing on an image. UIKit will scale images, and other content, but will not scale beziers (as far as I know)...
So, if it's a rect, call:
[theShape rect:aNewFrame]
or if its an ellipse, call:
[theShape ellipse:aNewFrame]
This approach constructs a new shape and then leverages Core Animations ability to implicitly animate the transition between the old shape path and the new one.

Resources