IOS - Working with autolayout and drawRect - ios

I'm working with autolayout and I have a RoundView class (subview of UIButton) which drawRect method is:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat lineWidth = 0.0;
CGContextSetLineWidth(context, lineWidth);
CGContextSetFillColorWithColor(context, _currentBackgroundColor.CGColor);
CGContextAddEllipseInRect(context, self.bounds);
CGContextFillPath(context);
}
Then I add it to self (a UIView), set its width and height using autolayout. Everything looks great.
But when I change its size constraints (to have a bigger view), and call
[self layoutIfNeeded];
in an animation block, the circle pixels look bigger and ugly.
Do I do this the correct way ?

I just got it from this post !
The problem was that the os wasn't calling drawRect at each animation step.
To force it, in RoundView's init:
self.contentMode = UIViewContentModeRedraw;

You need to take the content scale into account for the retina displays [UIDevice mainScreen].scale. See answers to this (question)[CGContext and retina display

Related

CGRect automatically resize after Action

I have some code in separate class, who bounded with UIView.
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect drawRect = CGRectMake(20, 262, 139, 169);
[self setFrame:drawRect];
CGContextSetLineWidth(context, 2);
CGContextSetStrokeColorWithColor(context, [[UIColor colorWithRed:125.0f/255.0f green:251.0f/255.0f blue:181.0f/255.0f alpha:1] CGColor]);
CGContextSetRGBFillColor(context, 200.0f/255.0f, 100.0f/255.0f, 100.0f/255.0f, 1.0f);
CGContextFillRect(context, drawRect);
But after clicking this view, size of UIView resizes to dimensions from interface builder model from my storyboard.
What does matter, and what's wrong with it?
drawRect: is not the place to change the frame. If the system can't automatically handle resizing the frame (by scaling the view, for instance), then drawRect: is called in response to resizing the frame. If you could change the frame size in the middle of drawing, you'd have an infinite loop.
If you want the view to be in the rectangle (20,262,139,169), then that's simple to put in autolayout. Just put the view where you want it, and create constraints to keep it there using Interface Builder.
If you don't want autolayout (not sure why you'd want to turn it off here, but if you did), you can turn it off and call setFrame: in viewDidLoad. But there's no reason to call it again in drawRect:.

iOS drawing in CALayers drawInContext is either pixelated or blurry on retina

I have a custom UIView that draws something in an overwritten
- (void)drawRect:(CGRect)rect
this works fine and gives sharp results on retina screens.
However, now I would like to make the properties on which the drawing is based animatable. Creating animatable properties seems to be possible only on the CALayer, so instead of doing the drawing in UIView, I create a custom CALayer subclass and do the drawing inside
- (void) drawInContext:(CGContextRef)ctx
I use pretty much the exact same drawing code in this function as I used in the drawRect function of the custom UIView.
The result looks the same - however, it's not retina resolution but pixelated (large square pixels)
if I put
self.contentsScale = [UIScreen mainScreen].scale;
To the beginning of my drawInContext implementation, then instead of a pixelated result, I get a blurry result (as if the rendering is still performed in non-retina resolution and then upscaled to retina resolution).
what's the correct way to render sharp retina paths in CALayers drawInContext ?
here are some screenshots (the blue line is part of the custom drawing in question. the yellow part is just an image)
Drawn inside custom UIView's drawRect:
Drawn inside custom CALayer's drawInContext:
Drawin inside custom CALayer's drawInContext, with setting self.contentScale first:
For completeness, here's (a stripped down version of the) drawing code:
//if drawing inside custom UIView sublcass:
- (void)drawRect:(CGRect)rect
{
CGContextRef currenctContext = UIGraphicsGetCurrentContext();
[[UIColor blackColor] set];
CGContextSetLineWidth(currenctContext, _lineWidth);
CGContextSetLineJoin(currenctContext,kCGLineJoinRound);
CGContextMoveToPoint(currenctContext,x1, y1);
CGContextAddLineToPoint(currenctContext,x2, y2);
CGContextStrokePath(currenctContext);
}
//if drawing inside custom CALayer subclass:
- (void) drawInContext:(CGContextRef)ctx {
{
//self.contentsScale = [UIScreen mainScreen].scale;
CGContextRef currenctContext = ctx;
CGContextSetStrokeColorWithColor(currenctContext, [UIColor blackColor].CGColor);
CGContextSetLineWidth(currenctContext, _lineWidth);
CGContextSetLineJoin(currenctContext,kCGLineJoinRound);
CGContextMoveToPoint(currenctContext,x1, y1);
CGContextAddLineToPoint(currenctContext,x2, y2);
CGContextStrokePath(currenctContext);
}
To restate what I want to achieve: I want to achieve the same crisp retina rendering as in the UIView approach, but when rendering in CALayer
The issue is most likely to be contentScale here; be aware that if you're assigning this to a custom view by overriding its layerClass function, the layer's content scale may be reset. There may be some other instances in which this also happens. To be safe, set the content scale only after the layer has been added to a view.
Try assigning the main screen's scale to your custom layer during your custom view's init method. In Swift 3 that looks like this:
layer.contentsScale = UIScreen.mainScreen().scale
Or, in Swift 4:
layer.contentsScale = UIScreen.main.scale
Are you using shouldRasterize = YES in the layer? Try drawing in the CALayer subclass but set the rasterizationScale to the screen's scale.
After adding layer to its superlayer. set shouldRasterize to YES , set contentsScale and resterizatioinScale to screen scale:
[self.progressView.layer addSublayer:self.progressLayer];
self.progressLayer.shouldRasterize = YES;
self.progressLayer.contentsScale = kScreenScale;
self.progressLayer.rasterizationScale = kScreenScale;
CABasicAnimation *animate = [CABasicAnimation animationWithKeyPath:#"progress"];// progress is a customized property of progressLayer
animate.duration = 1.5;
animate.beginTime = 0;
animate.fromValue = #0;
animate.toValue = #1;
animate.fillMode = kCAFillModeForwards;
animate.removedOnCompletion = NO;
animate.repeatCount = HUGE_VALF;
[self.progressLayer addAnimation:animate forKey:#"progressAnimation"];

How to improve performance in rendering image?

I am drawing image on a custom UIView. On resizing the view, the drawing performance goes down and it starts lagging.
My image drawing code is below:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
UIBezierPath *bpath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, width, height)];
CGContextAddPath(context, bpath.CGPath);
CGContextClip(context);
CGContextDrawImage(context, [self bounds], image.CGImage);
}
Is this approach correct?
You would be better using Instruments to find where the bottleneck is than asking on here.
However, what you will probably find is that every time the frame changes slightly the entire view will be redrawn.
If you're just using the drawRect to clip the view into an oval (I guess there's an image behind it or something) then you would be better off using a CAShapeLayer.
Create a CAShapeLayer and give it a CGPath then add it as a clipping layer to the view.layer.
Then you can change the path on the CAShapeLayer and it will update. You'll find (I think) that it performs much better too.
If your height and width are the same, you could just use a UIImageView instead of needing a custom view, and get the circular clipping by setting properties on the image view's layer. That approach draws nice and quickly.
Just set up a UIImageView (called "image" in my example) and then have your view controller do this once:
image.layer.cornerRadius = image.size.width / 2.0;
image.layer.masksToBounds = YES;

drawInRect method works too slow

Now i have following:
- (void)drawRect
{
// some drawing
[bgImage drawinRect:self.bounds];
// some drawing
}
I have more than 40 views with text and some marks inside. I need to repaint all these views on user tapping - it should be really fast!
I instrumented my code and saw: 75% of all execution time is [MyView drawRect:] and 95% of my drawRect time is [bgImage drawinRect:self.bounds] call. I need to draw background within GPU nor CPU. How is it possible?
What i have tried:
Using subviews instead of drawRect. In my case it is very slow because of unkickable color blending.
Adding UIImageView as background don't helps, we can't draw on subviews ...
Adding image layer to CALayer? Is it possible?
UPDATE:
Now i am trying to use CALayer instead of drawInRect.
- (void)drawRect
{
// some drawing
CALayer * layer = [CALayer layer];
layer.frame = self.bounds;
layer.contents = (id)image.CGImage;
layer.contentsScale = [UIScreen mainScreen].scale;
layer.contentsCenter = CGRectMake(capInsects.left / image.size.width,
capInsects.top / image.size.height,
(image.size.width - capInsects.left - capInsects.right) / image.size.width,
(image.size.height - capInsects.top - capInsects.bottom) / image.size.height);
// some drawing
}
Nothing instead of this new layer is visible right now. All my painting is under this layer i think...
I would use UILabels; you can reduce the cost of blending by giving the labels a background color or setting shouldRasterize = YES on the layer of the view holding those labels. (You'll probably also want to set rasterizationScale to be the same as [[UIScreen mainScreen] scale]).
Another suggestion:
There are lots of open-source calendar components that have probably had to try and solve the same issues. Perhaps you could look and see how they solved them.

IOS Quartz 2D drawRect and Resizing

I'm using an app called Quarkee to convert a logo from SVG to Quartz 2d code, which works a treat. Only problem is I can't seem to figure out how resize the result. If I set the frame of the UIView, the result from drawRect stays huge in the frame. How do I get it be the size of the frame I'm setting?
An example of the out is below.
Can someone help?
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorspace_1 = CGColorSpaceCreateDeviceRGB();
CGFloat components_1[] = {0.9961,0.9961,0.9961, 1.0000};
CGColorRef color_1 = CGColorCreate(colorspace_1, components_1);
CGContextSetFillColorWithColor(context,color_1);
CGContextMoveToPoint(context, 0.4960,0.1090);
CGContextAddLineToPoint(context,662.9260,0.1090);
CGContextAddLineToPoint(context,662.9260,227.8780);
CGContextAddLineToPoint(context,0.4960,227.8780);
CGContextAddLineToPoint(context,0.4960,0.1090);
CGContextClosePath(context);
CGContextFillPath(context);
The solution here was to use view.transform = CGAffineTransformMakeScale(0.5f, 0.5f); to resize the view
Take a look at these values:
CGContextMoveToPoint(context, 0.4960,0.1090);
CGContextAddLineToPoint(context,662.9260,0.1090);
CGContextAddLineToPoint(context,662.9260,227.8780);
CGContextAddLineToPoint(context,0.4960,227.8780);
CGContextAddLineToPoint(context,0.4960,0.1090);
when the drawRect method is called you can use self.bounds to take the current rect of your view and adjust those values according to it. You say you have generated this code from a application - those apps usually hardcode the size of the graphics you have drawn, when generate code, so you must see what is the relation between these values with the actual size of the image you have drawn and make them dynamic according to the self.bounds size...

Resources