CAGradientLayer not smooth enough? - iphone

I'm doing a CAGradientLayer for every background of my UIViews, but I have a small problem. The gradient doesn't seem that smooth enough. The transition between the colors are to present. You can see it in this picture.
This is how I implemented this gradient in the initializer of my UIView.
CAGradientLayer *layer = [CAGradientLayer layer];
layer.colors = [NSArray arrayWithObjects:
(id)[[UIColor darkKinepolisColor] CGColor],
(id)[[UIColor lightKinepolisColor] CGColor],
(id)[[UIColor lightKinepolisColor] CGColor],
(id)[[UIColor darkKinepolisColor] CGColor],
nil];
layer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:0.4],
[NSNumber numberWithFloat:0.6],
[NSNumber numberWithFloat:1],
nil];
layer.startPoint = CGPointMake(0, 0);
layer.frame = self.layer.bounds;
layer.endPoint = CGPointMake(1, 1);
layer.contentsGravity = kCAGravityResize;
[self.layer addSublayer:layer];
Could you, fine developers, help me with this problem? Thanks!

From what I've seen CAGradientLayer does not support dithering, but CGGradient does. See my answer here. Also see the other answers for example on using CGGradient.

Isn't this just a consequence of 8-bit colour values? The steps between the colours are as small as they can possibly be.
i.e. if you want a gradient between rgb(100,100,100) and rgb(109,109,109) it can only be perfectly smooth across a gradient width of 10 pixels. If you tried to draw this gradient across a width of 100 pixels you would get 10 blocks of colour 10px wide and it would look very blocky. What else could happen?
You can make it appear smoother by dithering and adding noise but there is no built in function for this.
So, either, choose colours farther apart, draw the gradient over a shorter distance or make the graphic in Photoshop and apply noise and dithering to make it appear smoother.

I'm developing an app that needs a base gradient, and various other gradients are faded into it, depending on various factors (eg: the time of day). I've found that I can continue to use CAGradientLayer for all but the base gradient. For the base gradient, i created it in photoshop and added 0.554% noise. Almost imperceptible, but it prevents the banding effect when I animate the alpha of CAGradientLayers above it.
So I guess the answer would be that you need dithering/noise on at least one of the gradients to avoid the banding effect. It may also be possible to add this programmatically, or by loading a pre-rendered black image with noise over a CAGradientLayer with a low alpha (~0.1f) on the noise layer.

Rasterize with your device's screen scale, will be very smooth and crisp!
[layer setShouldRasterize:YES];
[layer setRasterizationScale:[UIScreen mainScreen].scale];

Related

Place an image behind my labels and in front of my plot within my coreplot graph

How can I get an image behind my labels within my graph. I succeeded with putting an image in front of the graph with:
CPTPlotSpaceAnnotation *imageAnnotation;
CGRect imageRect = CGRectMake(0, 0 , 480, 320);
CPTBorderedLayer * ImageLayer = [[CPTBorderedLayer alloc] initWithFrame:imageRect];
ImageLayer.fill = [CPTFill fillWithImage:[CPTImage imageWithCGImage:[[UIImage imageNamed:#"Overlay_3_digits.png"] CGImage]]];
imageAnnotation = [[CPTPlotSpaceAnnotation alloc] initWithPlotSpace:plotSpace anchorPlotPoint:nil];
imageAnnotation.contentLayer = ImageLayer;
[graph addAnnotation:imageAnnotation];
[graph addSublayer:ImageLayer];
[newImagelLayer release];
[imageAnnotation release];
adding the image with [graph insertSubLayer:ImageLayer below:y]; didn't worked, this also puts the image in front of the graphs and axislabels. Hope someone can help me out.
EDIT:
I added a screenshot from the simulator. The three black rectangles needs to be in front of my plots and behind my labels. The labels will be readible more easily.
I also editted the hierarchy of layers from my graph by putting the axislabels at the first index:
NSArray *chartLayers = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:CPTGraphLayerTypeAxisLabels],
[NSNumber numberWithInt:CPTGraphLayerTypePlots],
[NSNumber numberWithInt:CPTGraphLayerTypeMajorGridLines],
[NSNumber numberWithInt:CPTGraphLayerTypeMinorGridLines],
[NSNumber numberWithInt:CPTGraphLayerTypeAxisLines],
[NSNumber numberWithInt:CPTGraphLayerTypeAxisTitles],
nil];
graph.topDownLayerOrder = chartLayers;
I also found a better way of putting my image inside the graph. I did this by means of a new hostlayer:
CPTAnnotationHostLayer *newHostLayer = [[CPTAnnotationHostLayer alloc]
initWithFrame:CGRectMake(0, 0, 480, 220)];
UIImage * annotationBG = [UIImage imageNamed:#"Overlay_3_digits.png"];
newHostLayer.backgroundColor = [UIColor colorWithPatternImage:annotationBG].CGColor;
Any ideas how I can put the three black triangle image at the back of my labels and in front of the plots?
The -addAnnotation: method takes care of updating the layer tree; you should remove your call to -addSublayer:.
Do you want the image to completely fill the background behind the graph? If so, just set the fill on the graph instead of making an annotation.
graph.fill = [CPTFill fillWithImage:[CPTImage imageWithCGImage:[[UIImage imageNamed:#"Overlay_3_digits.png"] CGImage]]];
To put the box behind each label, you can use custom labels. Build a CPTBorderedLayer the same way you did for the annotation. Add a CPTTextLayer with the label text as a sublayer of the bordered layer. Make the bordered layer the contentLayer of the custom label. This should remove the need to change the topDownLayerOrder, too.

How to do vignette effect on iOS?

When UIAlertViews pop up, there is a vignette effect in the background. That is, the edges are darker and the center is lighter.
I was wondering if this vignette effect was built into Cocoa Touch. I would like to show the vignette behind one of my custom views.
It is built into UIKit (as UIAlertView is part of UIKit), but it's not public.
It shouldn't be too hard to create the same effect, though. It's just a radial gradient, which you can draw in code or Photoshop.
UPDATE: If you must know, the background is a class called _UIAlertNormalizingOverlayWindow with the following class hierarchy:
_UIAlertNormalizingOverlayWindow
_UIAlertOverlayWindow
UIWindow
Create a class called VignetteEffect as a subclass of UIView
Add this code to your -drawRect: method:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef colSp = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColors(colSp, (__bridge CFArrayRef)[NSArray arrayWithObjects:(id)[[UIColor colorWithRed:0 green:0 blue:0 alpha:0] CGColor], (id)[[UIColor colorWithRed:0 green:0 blue:0 alpha:0.5] CGColor], nil], 0);
CGContextDrawRadialGradient(context, gradient, self.center, 0, self.center, self.frame.size.height+self.frame.size.height/4, 0);
CGColorSpaceRelease(colSp);
CGGradientRelease(gradient);
}
Tweak the values to your liking.
Add it to any view you have. Et voila. A nice vignette effect.
In fact this effect is achieved by an extra image - a separate window with an imageview is shown underneath a uialertview. That window makes it so you can't select or touch any other views. If you want that image it can be found right here
SVProgressHud does this type of effect, look at the code where SVProgressHUDMaskTypeGradient is detailed.
I contest the picked answer is not correct and is potentially dangerous. Here's my solution:
I copied the gradient drawing code from SVProgressHud into my fork of SSGradientView:
SSGradientView *vignette = [SSGradientView new];
vignette.frame = [UIScreen mainScreen].currentBounds;
vignette.backgroundColor = [UIColor clearColor];
vignette.direction = SSGradientViewDirectionRadial;
UIColor *startColor = // We just need the colorspace.
UIColor *endColor = // Visible vignette color.
vignette.colors = #[
[startColor colorWithAlphaComponent:0.0f],
[endColor colorWithAlphaComponent:1.0f] ];
vignette.locations = #[ #0.4f, #1.0f ];
[view insertSubview:vignette atIndex:0]; // Or equivalent.

creating a bar with core animation

So I am trying to create an animated bar graph using apple core animation. The bar is just basically a rectangular figure, which have a value of 0-100%. When it first appears I wanted it to show an animation going from 0 to x %. How can I draw a rectangular form like this?
UPDATE:
Most probably I will have a bar as an image, so I need to animate this image to a certain height...
If your requirements are really that simple, you could create a view, set its background color and adjust (or animate) its frame.width (or height) as needed.
Of course there are more elaborate ways to do this, but no need to over-engineer for a simple problem.
This should do exactly what you want:
- (UIImageView*)createNewBarWithValue:(float)percent atLocation:(CGPoint)location
{
UIImageView *newBar = [[[UIImageView alloc] initWithFrame:CGRectMake(location.x, location.y, 50, 200)] autorelease];
newBar.image = [UIImage imageNamed:#"bar.png"];
CABasicAnimation *scaleToValue = [CABasicAnimation animationWithKeyPath:#"transform.scale.y"];
scaleToValue.toValue = [NSNumber numberWithFloat:percent];
scaleToValue.fromValue = [NSNumber numberWithFloat:0];
scaleToValue.duration = 1.0f;
scaleToValue.delegate = self;
newBar.layer.anchorPoint = CGPointMake(0.5, 1);
[newBar.layer addAnimation:scaleToValue forKey:#"scaleUp"];
CGAffineTransform scaleTo = CGAffineTransformMakeScale( 1.0f, percent );
newBar.transform = scaleTo;
return newBar;
}

Bad performance on scroll view loaded with 3 view controllers, drawn with CALayer

i have a scroll view loaded with 3 view controllers. each view controller is drawing its layers with that code -
(there us more then that but I pulled it out to check if it will help). still i have very crappy sliding.
any help ?
shani
CALayer *sublayer = [CALayer layer];
sublayer.backgroundColor = [Helper cardBackGroundColor:card].CGColor;
sublayer.shadowOffset = CGSizeMake(0, 3);
sublayer.shadowRadius = 5.0;
sublayer.shadowColor = [UIColor blackColor].CGColor;
sublayer.shadowOpacity = 0.8;
sublayer.frame = CGRectInset(self.view.layer.frame, 20, 20);
sublayer.borderColor = [UIColor blackColor].CGColor;
sublayer.borderWidth = 2.0;
sublayer.cornerRadius = 10.0;
[self.view.layer addSublayer:sublayer];
Drawing things with CALayer often yields poor performance. We usually use a stretchable image to get adequate performance. When you think of it, it does make sense to render it before hand rather than using the iPhone's limited processing power to render it in real time.
It's possible that you can get adequate performance from CALayer, but drawing a png will probably still be faster, thus saving battery life time.
EDIT: So here's an example to explain the concept.
This code actually replaced a CALayer drawing that was too slow.
UIImageView *shadow = [[UIImageView alloc] initWithFrame:frame];
shadow.image = [[UIImage imageNamed:#"shadow.png"] stretchableImageWithLeftCapWidth:16.0 topCapHeight:16.0];
[contentView addSubview:shadow];
[shadow release];
shadow.png is 34 by 34 pixels and contains a shadowed square. Thanks to the stretchable image it's possible to resize the square without stretching the shadow. For more information about this I would suggest reading the documentation for stretchableImageWithLeftCapWidth:topCapHeight:. Also Google will help you find guides on how to work with stretchable images. If you have more questions I'll be happy to answer them.
You have a mask (assuming you somewhere say masksToBounds=YES) and a shadow on this layer. Both cause an off screen rendering pass.
Please watch the WWDC 2010 Session 425 - Core Animation in Practice Part 2
Which you can find here;
http://developer.apple.com/videos/wwdc/2010/

iPhone Layer confusion

I am trying to do something that should be really simple. I want to add a gradient to one of my views. I can add it to self.view, but not to viewWithGradient. Here is the code:
CAGradientLayer *gradient = [CAGradientLayer layer];
UIColor *colorFirst = [UIColor colorWithWhite:0.10 alpha:0.15];
UIColor *colorLast = [UIColor colorWithWhite:0.535 alpha:0.8];
NSArray *colors = [NSArray arrayWithObjects:(id)colorFirst.CGColor, colorLast.CGColor, nil];
gradient.colors = colors;
NSNumber *stopFirst = [NSNumber numberWithFloat:0.00];
NSNumber *stopLast = [NSNumber numberWithFloat:1.00];
NSArray *locations = [NSArray arrayWithObjects:stopFirst, stopLast, nil];
gradient.locations = locations;
gradient.frame = CGRectMake(0.0, viewWithGradient.frame.origin.y, viewWithGradient.frame.size.width, height_of_gradient);
[self.view.layer addSublayer:gradient]; // this works. I get a nice gradient at
// the bottom of the view, where I want it,
// but I need the gradient further down in
// my view hierarchy.
// [viewWithGradient.layer addSublayer:gradient]; // this doesn't. There
// is no visually noticeable change
// if this is uncommented and the
// above line is.
viewWithGradient is a small view inside my main view inside a viewController (self.view). There is only one other view over this viewWithGradient. It is a UILabel, that only takes up about one half of the area of viewWithGradient. It has a transparent background, but the label draws its text in white. I need to have the gradient be under the UILabel, not on self.view, over the UILabel.
I currently suspect that my frame/bounds may put the gradient offscreen. Does anyone see anything that I am missing? This seems like the simplest of the CALayer usages, and I have spent way too much time on it.
The y coordinate of your gradient's frame is viewWithGradient.frame.origin.y. Did you actually want 0? If viewWithGradient is more than halfway down the screen, your gradient will draw offscreen.