Best and fastest way to round UIView's corners? - iphone

I'm currently working on a rewrite of a tweak (runs inside SpringBoard itself) and so (of cause) smoothness, speed and efficiency are some of the most important things for me as even the slightest bit of unnecessary lag will take away from the UX.
So my question is how can I round the views corners with the minimal amount of lag.
The obvious one is:
view.layer.cornerRadius = value;
view.layer.masksToBounds = YES;
However I've heard that setting a CALayer mask with layer.mask is faster? And if so which of these 2 solutions is best?: https://stackoverflow.com/a/4930166/458205
This code uses masking however the mask layer is using cornerRadius as well so is this actually any faster?
CALayer *maskLayer = [CALayer layer];
maskLayer.cornerRadius = radius;
// set the mask
self.view.layer.mask = maskLayer;
Or would Solution 1, of the above link, or this answer be more effecient?
I know I'm referencing another question several times, however that question is about masking only 2 corners (which throws up a few different solutions) however I'm asking for the most efficient way to have 0.6 screen size views scroll as smoothly as possible with rounded corners (like this image).

You need to set the rasterizationScale as well as view.layer.shouldRasterize.
I typically do this:
view.layer.shouldRasterize = YES;
view.layer.rasterizationScale = [[UIScreen mainScreen] scale];
If you don't set the rasterizationScale it will always default to a scale of 1 (non-retina in this case) for rasterization.
For me this usually puts my scroll performance on par with the UITableView's.

The first snippet is more efficient because the rounded corner is a mask by itself.
to increase performance of rounded corners use:
view.layer.shoudRasterize = YES;

Related

CAShapeLayer annoying clipping error

I am working on a map functionality. The map is built up out of multiple CAShapeLayers with CGPaths from calculated coordinates. I have a clipping problem. Look below on the screenshot where Alaska is badly clipped. The coordinates of the Alaska path extend beyond the bounds of my container layer. In effect, if i make my container layer big enough the clipping effect is gone (of course).
You see a dark line because at the bottom of Alaska is solid from left to right. Also the line is darker than the rest of the map because the map has opacity (it gets darker because it adds up).
I drilled down into the problem and i have narrowed it down to the single big polygon (there are not other polygons responsible for the clipping error).
As a workaround, i make the layer bigger to hide the line, then make the UIView smaller again to hide the line.
I'd like to know what is causing the issue instead of working with workarounds.
After a lot of digging, i managed to find an answer to my own question.
I was rendering the layers to an UIImage for improved performance. The background layer was scaled up by a UIScrollView and then several things went wrong:
Apparently, setting masksToBounds:YES has no effect when using renderInContext, just as it does with the mask property of a CALayer. MasksToBounds (or clipToBounds) only applies to childlayers.
When scaling a bitmap, be sure to include integral values to the scale argument of UIGraphicsBeginImageContextWithOptions. If not, the image will have fractional sizes, e.g. 24.2323 x 34.3290. Btw, that scale argument is used to create amazing detail on Retina screens, but it can be misused to zoom in on CAShapeLayer drawings.
When using fractional size images as a background layer, you get distortion at the edge.
The clipping effect disappeared after i updated my layer to image function. This one did the trick:
- (UIImage *)getImageWithSize:(CGSize)size opaque:(bool)opaque contentScale:(CGFloat)scale
{
CGContextRef context;
size = CGSizeMake(ceilf(size.width), ceilf(size.height));
scale = roundf(scale);
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
context = UIGraphicsGetCurrentContext();
[self renderInContext:context];
UIImage *outputImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImg;
}
Using ceilf, roundf, or floorf didn't really matter. As long as you lose the fractions.
Sorry if my stupidity wasted any of your time, but perhaps others have the same issue.

how to apply an imageview frame with the inclined coordinates

hi all upto now i know making rectangle with the CGrectmake and this rect(frame) i am using as imageview frame like UIImageView *someImage=[[uiimageview alloc]initwithframe:someRect]; now i can add an image with the frame of someRect. my problem here is when the coordinates like
(rectangleFirstx-coordinate,tectangleFirstY-cordinate)=(10,10)
(rectangleLastx-cordinate,rectangleLasty-cordinate)=(17,7) this, how can i give frame to the uiimageview....This is like a inclined rectangle..can any one suggest me how to apply frame through the ios library for these type of coordinates..Thanks in advance..
Your example isn't very clear because a rectangle with opposite corners at (10,10) and (10,7) can be in any one of a myriad of different orientations, including one perfectly aligned along the x and y axis.
What you can certainly do is create a UIImageView of the desired size and location and then rotate it by using one of many techniques, including animation methods.
[UIImageView animateWithDuration:0.1 animations:^
{
your_UIImageView_here.transform = CGAffineTransformMakeRotation((M_PI/180.0) * degrees);
}];
You can hide the UIImageView until the rotation is done and then show it.
If your question is about how to use the coordinates you provided to arrive at an angle I'd suggest that more data is needed because it is impossible to pick one of the billions of possible rectangles with corners at those two points without more information. Once you have more data then it is pretty basic trigonometry to figure out the angle to feed into the rotation.

UIView transparency shows how the sausages are made!

I have a UIView container that has two UIImageViews inside it, one partially obscuring the other (they're being composed like this to allow for occasional animation of one "layer" or another.
Sometimes I want to make this container 50% alpha, so what the users sees fades. Here's the problem: setting my container view to 50% alpha makes all my subviews inherit this as well, and now you can see through the first subview into the second, which in my application has a weird X-Ray effect that I'm not looking for.
What I'm after, of course, is for what the user currently sees to become 50% transparent-- the equivalent of flattening the visible view into one bitmap, and then making that 50% alpha.
What are my best bets for accomplishing this? Ideally would like to avoid actually, dynamically flattening the views if I can help it, but best practices on that welcome as well. Am I missing something obvious? Since most views have subviews and would run into this issue, I feel like there's some obvious solution here.
Thanks!
EDIT: Thanks for the thoughts folks. I'm just moving one image around on top of another image, which it only partially obscures. And this pair of images has to move together sometimes, as well. And sometimes I want to fade the whole thing out, wherever it is, and whatever the state of the image pair is at the moment. Later, I want to bring it back and continue animating it.
Taking a snapshot of the container, either by rendering its layer (?) or by doing some other offscreen compositing on the fly before alpha'ing out the whole thing, is definitely possible, and I know there are a couple ways to do it. But what if the animation should continue to happen while the whole thing's at 50% alpha, for example?
It sounds like there's no obvious solution to what I'm trying to do, which seems odd to me, but thank you all for the input.
Recently I had this same problem, where I needed to animate layers of content with a global transparency. Since my animation was quite complex, I discovered that flattening the UIView hierarchy made for a choppy animation.
The solution I found was using CALayers instead of UIViews, and setting the .shouldRasterize property to YES in the container layer, so that any sublayers would be flattened automatically prior to applying the opacity.
Here's what a UIView could look like:
#import <QuartzCore/QuartzCore.h> //< Needed to use CALayers
...
#interface MyView : UIView{
CALayer *layer1;
CALayer *layer2;
CALayer *compositingLayer; //< Layer where compositing happens.
}
...
- (void)initialization
{
UIImage *im1 = [UIImage imageNamed:#"image1.png"];
UIImage *im2 = [UIImage imageNamed:#"image2.png"];
/***** Setup the layers *****/
layer1 = [CALayer layer];
layer1.contents = im1.CGImage;
layer1.bounds = CGRectMake(0, 0, im1.size.width, im1.size.height);
layer1.position = CGPointMake(100, 100);
layer2 = [CALayer layer];
layer2.contents = im2.CGImage;
layer2.bounds = CGRectMake(0, 0, im2.size.width, im2.size.height);
layer2.position = CGPointMake(300, 300);
compositingLayer = [CALayer layer];
compositingLayer.shouldRasterize = YES; //< Here we turn this into a compositing layer.
compositingLayer.frame = self.bounds;
/***** Create the layer three *****/
[compositingLayer addSublayer:layer1]; //< Add first, so it's in back.
[compositingLayer addSublayer:layer2]; //< Add second, so it's in front.
// Don't mess with the UIView's layer, it's picky; just add sublayers to it.
[self.layer addSublayer:compositingLayer];
}
- (IBAction)animate:(id)sender
{
/* Since we're using CALayers, we can use implicit animation
* to move and change the opacity.
* Layer2 is over Layer1, the compositing is partially transparent.
*/
layer1.position = CGPointMake(200, 200);
layer2.position = CGPointMake(200, 200);
compositingLayer.opacity = 0.5;
}
I think that flattening the UIView into a UIImageView is your best bet if you have your heart set on providing this feature. Also, I don't think that flattening the image is going to be as complicated as you might think. Take a look at the answer provided in this question.
Set the bottom UIImageView to have .hidden = YES, then set .hidden = NO when you setup a cross-fade animation between the top and bottom UIImageViews.
When you need to fade the whole thing, you can either set .alpha = 0.5 on the container view or the top image view - it shouldn't matter. It may be computationally more efficient to set .alpha = 0.5 on the image view itself, but I don't know enough about the graphics pipeline on the iPhone to be sure about that.
The only downside to this approach is that you can't do a cross-fade when your top image is set to 50% opacity.
A way to do this would be to add the ImageViews to the UIWindow (the container would be a fake one)

How to set up a user Quartz2D coordinate system with scaling that avoids fuzzy drawing?

This topic has been scratched once or twice, but I am still puzzled. And Google was not friendly either.
Since Quartz allows for arbitrary coordinate systems using affine transforms, I want to be able to draw things such as floorplans using real-life coordinate, e.g. feet.
So basically, for the sake of an example, I want to scale the view so that when I draw a 10x10 rectangle (think a 10-inch box for example), I get a 60x60 pixels rectangle.
It works, except the rectangle I get is quite fuzzy. Another question here got an answer that explains why. However, I'm not sure I understood that reason why, and moreover, I don't know how to fix it. Here is my code:
I set my coordinate system in my awakeFromNib custom view method:
- (void) awakeFromNib {
CGAffineTransform scale = CGAffineTransformMakeScale(6.0, 6.0);
self.transform = scale;
}
And here is my draw routine:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect r = CGRectMake(10., 10., 10., 10.);
CGFloat lineWidth = 1.0;
CGContextStrokeRectWithWidth(context, r, lineWidth);
}
The square I get is scaled just fine, but totally fuzzy. Playing with lineWidth doesn't help: when lineWidth is set smaller, it gets lighter, but not crisper.
So is there a way to set up a view to have a scaled coordinate system, so that I can use my domain coordinates? Or should I go back and implementing scaling in my drawing routines?
Note that this issue doesn't occur for translation or rotation.
Thanks
the [stroked] rectangle I get is quite fuzzy.
Usually, this is because you plotted the rectangle on whole-number co-ordinates and your line width is 1.
In PostScript (and thus in its descendants: AppKit, PDF, and Quartz), drawing units default to points, 1 point being exactly 1/72 inch. The Mac and iPhone currently* treat every such point as 1 pixel, regardless of the actual resolution of the screen(s), so, in a practical sense, points (by default, on the Mac and iPhone) are equal to pixels.
In PostScript and its descendants, integral co-ordinates run between points. 0, 0, for example, is the lower-left corner of the lower-left point. 1, 0 is the lower-right corner of that same point (and the lower-left corner of the next point to the right).
A stroke is centered on the path you're stroking. Thus, half will be inside the path, half outside.
In the (conceptually) 72-dpi world of the Mac, these two facts combine to produce a problem. If 1 pt is equal to 1 pixel, and you apply a 1-pt stroke between two pixels, then half of the stroke will hit each of those pixels.
Quartz, at least, will render this by painting the current color into both pixels at one-half of the color's alpha. It determines this by how much of the pixel is covered by the conceptual stroke; if you used a 1.5-pt line width, half of that is 0.75 pt, which is three-quarters of each 1-pt pixel, so the color will be rendered at 0.75 alpha. This, of course, goes to the natural conclusion: If you use a 2-pt line width, each pixel is completely covered, so the alpha will be 1. That's why you can see this effect with a 1-pt stroke and not a 2-pt stroke.
There are several workarounds:
Half-point translation: Exactly what it says on the box, you translate up and right by half a point, compensating for the aforementioned 1-pt-cut-in-half division.
This works in simple cases, but flakes out when you involve any other co-ordinate transformations except whole-point translations. That is to say, you can translate by 30, 20 and it'll still work, but if you translate by 33+1/3, 25.252525…, or if you scale or rotate at all, your half-point translation will be useless.
Inner stroke: Clip first, then double the line width (because you're only going to draw half of it), then stroke.
This can require gstate juggling if you have a lot of other drawing to do, since you don't want that clipping path affecting your other drawing.
Outer stroke: Essentially the same as an inner stroke, except that you reverse the path before clipping.
Can be better (less gstate juggling) than an inner stroke if you're sure that the paths you want to stroke won't overlap. On the other hand, if you also want to fill the path, the gstate juggling returns.
*This won't last forever. Apple's been dropping hints for some time that they're going to change at least the Mac's drawing resolution at some point. The API foundation for such a change is pretty much all there now; it's all a matter of Apple throwing the switch.
Well, as often, explaining the issue lead me to a solution.
The problem is that the view transform property is applied to it after it has been drawn into a bit buffer. The scaling transform has to be applied before drawing, ie. in the drawRect method. So scratch the awakeFromNib I gave, and here is a correct drawRect:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform scale = CGAffineTransformMakeScale(6.0, 6.0);
CGContextConcatCTM(context, scale);
CGRect r = CGRectMake(10., 10., 10., 10.);
CGFloat lineWidth = 0.1;
CGContextStrokeRectWithWidth(context, r, lineWidth);
}

How do I draw a point inside of an instance of UIImageView?

How do I use Core Graphics to draw inside of a instantiated UIImageView (No class files to override drawrect with -- just a reference to the instance)? I want to draw a simple point of fixed width (say, 10 pixels diameter) and of a certain color.
How do I use Core Graphics to … draw a simple point of fixed width (say, 10 pixels diameter) and of a certain color[?]
Set the fill color.
Move to the point.
Plot an arc, centered at the point, with the desired radius, giving a start angle of 0 and an end angle of 2 * M_PI.
Close the path.
Fill.
… draw inside of a instantiated UIImageView (No class files to override drawrect with -- just a reference to the instance)
I know that on the Mac, that's really not a good idea, as an NSView may redraw at any time if anything has set it as needing display (or told it directly to display), and would then clobber anything you'd drawn over it. No idea about the iPhone, but I do notice that UIView doesn't have methods like NSView's lockFocus and unlockFocus.
There's a more architectural reason not to do this: Drawing is the exclusive domain of views, and you're proposing to break that exclusivity and sprinkle some drawing code in (I'm assuming) a controller. Scattering code for a purpose in an object that doesn't have that purpose is a path to messy, unnavigable code.
Better to make a subview for each point and add those as subviews of the image view, or subclass UIImageView, give your instance of that subclass an array of the points to draw, and have the subclass's drawRect: call up to super before drawing its points.
The above solution, then, goes in drawRect:.
You can also just add a Core Animation layer to the view. Set the corner radius to half the size of the point you want (assuming a square) and it will render as a circle. Something like:
CALayer *layer = [CALayer layer];
[layer setBounds:CGRectMake(0.0f, 0.0f, 10.0f, 1.0f)];
[layer setCornerRadius:5.0f];
[layer setMasksToBounds:YES];
[layer setBackgroundColor:[[UIColor redColor] CGColor]];
// Center the layer in the view.
[layer setPosition:CGPointMake([view bounds].size.width/2,
[view bounds].size.height/2)];
// Add the layer to your view
[[view layer] addSublayer:layer];
Not Core Graphics exactly, but it's easy to implement.
Best Regards,