iOS draw gradient in part of the view - objective-c

I've created a custom progress bar which subclass UIView and implements drawRect. I manage to draw a single gradient on the entire view. I'd like however to draw several different gradients, each one in a different position. How to I limit CGContextDrawLinearGradient to smaller rect inside my view?
glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGPoint topCenter = CGPointMake(start + (CGRectGetMidX(currentBounds)/currentBounds.size.width), 0.0f);`
CGPoint midCenter = CGPointMake(start + (CGRectGetMidX(currentBounds)/currentBounds.size.width), currentBounds.size.height);
CGContextDrawLinearGradient(currentContext, glossGradient, topCenter, midCenter, 0);
start = start + (values[i] / currentBounds.size.width);
CGGradientRelease(glossGradient);
}

You can use CGContectClipToRect to restrict the drawing area
Then for each gradient do:
CGContextSaveGState(currentContext);
CGContextClipToRect(theRect); // theRect should be the area where you want to draw the gradient
... // gradient drawing code
CGContextRestoreGState(currentContext);

As stated in Quartz 2D Programming Guide:
When you paint a gradient, Quartz fills the current context. Painting
a gradient is different from working with colors and patterns, which
are used to stroke and fill path objects. As a result, if you want
your gradient to appear in a particular shape, you need to clip the
context accordingly.
Since you want to draw each gradient in a rectangle, you will want to do something like this for each gradient and rectangle:
CGContextSaveGState(currentContext); {
CGContextClipToRect(currentContext, currentBounds);
CGContextDrawLinearGradient(currentContext, glossGradient, topCenter, midCenter, 0);
} CGContextRestoreGState(currentContext);

Related

Different animation styles like flipboard animations

I am new to ios development. i want different animation styles like flipboard animations.can any one give me some sample examples.
Thanks in advance
I don't have any experience in this, but after reading the docs, I think you're going to need a library for that. A quick Google search suggested:
Canvas (provides animations: bounce, slide, fade, fun)
MZFormSheetController
Pop
If you want to code your own custom animation, it appears that the apple documentation explains how.
You can use a library, but it is possible to transform the layer of a UIView in many ways, quite easily.
Say the view you want to animate is called view and is of type UIView
Here is how to animate it similar to a flip animation, where it rotates around the y-axis:
CALayer *layer = view.layer;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform = CATransform3DTranslate(rotationAndPerspectiveTransform, 0, 0, 20);
rotationAndPerspectiveTransform.m34 = 5.0 / -500;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, angle* M_PI / 180.0f, 0.0f, 1.0f, 0.0f);
layer.transform = rotationAndPerspectiveTransform;
The key function here is CATransform3DRotate which rotates the layer in 3d.
You specify the axis around which to rotate with the last 3 parameters (x, y, z) which is (0,1,0) in this case, i.e. the y-axis.
Note that this won't produce an animation, but rather, will orient the layer using the provided axis and angle.
To animate the layer, you will have to incrementally change the angle, in another function (e.g. using NSTimer).

UIView Rounded Corners Without a Mask Layer

I'm building an iPad app that includes several bar graphs (30+ bars per graph) and a pull-up menu from the bottom of the screen. The design calls for these bars to have rounded corners on the top left and top right corner, but square corners on the bottom left and bottom right.
Right now, I am rounding the top corners of each individual graph bar by using a mask layer:
#import "UIView+RoundedCorners.h"
#import <QuartzCore/QuartzCore.h>
#implementation UIView (RoundedCorners)
-(void)setRoundedCorners:(UIRectCorner)corners radius:(CGFloat)radius {
CGRect rect = self.bounds;
// Create the path
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect
byRoundingCorners:corners
cornerRadii:CGSizeMake(radius, radius)];
// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = rect;
maskLayer.path = maskPath.CGPath;
// Set the newly created shape layer as the mask for the view's layer
self.layer.mask = maskLayer;
self.layer.shouldRasterize = YES;
}
#end
This works, but there is serious frame loss / lag when I pull up the pull-up menu (Note that the menu overlaps, appearing on top of the graph). When I take away the rounded corners, the menu pulls up beautifully. From what I gather, I need a way to round the corners by directly modifying the view, not by using a mask layer. Does anyone know how I could pull this off or have another possible solution? Any help greatly appreciated
From what I gather, I need a way to round the corners by directly
modifying the view, not by using a mask layer. Does anyone know how I
could pull this off or have another possible solution?
The usual way is to get the view's layer and set the cornerRadius property:
myView.layer.cornerRadius = 10.0;
That way there's no need for a mask layer or a bezier path. In the context of your method, it'd be:
-(void)setRoundedCorners:(UIRectCorner)corners radius:(CGFloat)radius
{
self.layer.cornerRadius = radius;
}
Update: Sorry -- I missed this part:
The design calls for these bars to have rounded corners on the top
left and top right corner, but square corners on the bottom left and
bottom right
If you only want some of the corners rounded, you can't do that by setting the layer's cornerRadius property. Two approaches you could take:
Create a bezier path with two corners rounded and two square, and then fill it. No need for a mask layer to do that.
Use a resizable image. See UIImage's -resizableImageWithCapInsets: and -resizableImageWithCapInsets:resizingMode: methods. There are even some methods for animating reizable images, so you could easily have your bars grow to the right length.
Try setting shouldRasterize on your layers. Should help.

Draw rounded linear gradient (or extended radial gradient) with CoreGraphics

I want to do some custom drawing with CoreGraphics. I need a linear gradient on my view, but the thing is that this view is a rounded rectangle so I want my gradient to be also rounded at angles. You can see what I want to achieve on the image below:
So is this possible to implement in CoreGraphics or some other programmatic and easy way?
Thank you.
I don't think there is an API for that, but you can get the same effect if you first draw a radial gradient, say, in an (N+1)x(N+1) size bitmap context, then convert the image from the context to a resizable image with left and right caps set to N.
Pseudocode:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(N+1,N+1), NO, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
// <draw the gradient into 'context'>
UIImage* gradientBase = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage* gradientImage = [gradientBase resizableImageWithCapInsets:UIEdgeInsetsMake(0,N,0,N)];
In case you want the image to scale vertically as well, you just have to set the caps to UIEdgeInsetsMake(N,N,N,N).
I just want to add more sample code for this technique, as some things weren't obvious for. Maybe it will be useful for somebody:
So, let's say, we have our custom view class and in it's drawRect: method we put this:
// Defining the rect in which to draw
CGRect drawRect=self.bounds;
Float32 gradientSize=drawRect.size.height; // The size of original radial gradient
CGPoint center=CGPointMake(0.5f*gradientSize,0.5f*gradientSize); // Center of gradient
// Creating the gradient
Float32 colors[4]={0.f,1.f,1.f,0.2f}; // From opaque white to transparent black
CGGradientRef gradient=CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceGray(), colors, nil, 2);
// Starting image and drawing gradient into it
UIGraphicsBeginImageContextWithOptions(CGSizeMake(gradientSize, gradientSize), NO, 1.f);
CGContextRef context=UIGraphicsGetCurrentContext();
CGContextDrawRadialGradient(context, gradient, center, 0.f, center, center.x, 0); // Drawing gradient
UIImage* gradientImage=UIGraphicsGetImageFromCurrentImageContext(); // Retrieving image from context
UIGraphicsEndImageContext(); // Ending process
gradientImage=[gradientImage resizableImageWithCapInsets:UIEdgeInsetsMake(0.f, center.x-1.f, 0.f, center.x-1.f)]; // Leaving 2 pixels wide area in center which will be tiled to fill whole area
// Drawing image into view frame
[gradientImage drawInRect:drawRect];
That's all. Also if you're not going to ever change the gradient while app is running, you would want to put everything except last line in awakeFromNib method and then in drawRect: just draw the gradientImage into view's frame. Also don't forget to retain the gradientImage in this case.

Subclass of UIView with CAShapeLayer as mask does not animate correctly

I have added round corners to my subclassed UIView by adding a layer mask. The following is Montouch code, but should be easy enough to get the meaning also in ObjC:
// Add a layer that holds the rounded corners.
UIBezierPath oMaskPath = UIBezierPath.FromRoundedRect (this.Bounds, this.eRoundedCorners, new SizeF (this.fCornerRadius, this.fCornerRadius));
private void UpdateMask()
{
if(this.Layer.Mask == null)
{
CAShapeLayer oMaskLayer = new CAShapeLayer ();
oMaskLayer.Frame = this.Bounds;
// Set the newly created shape layer as the mask for the image view's layer
this.Layer.Mask = oMaskLayer;
}
((CAShapeLayer)this.Layer.Mask).Path = oMaskPath.CGPath;
}
I have overloaded the Frame property. Setting the frame adjusts the mask to keep the rounded corners in shape.
However if I animate the change in frame size, the mask is immediately set to the destination value of the animation instead.
Is there a way to make my view detect that is is part of an animation and make the mask animate correctly?
This is how CoreAnimation works for explicit animations.
With explicit animations, you never actually alter the values of your original objects, you merely animate objects in the presentation layer. So you need to make sure that your object has been setup before you start the animation with the value that you want to have in the end.
A complete explanation of how to solve this class of problems is explained in "Core Animation Essentials" on the WWDC 2011 online videos. Look for session 421

How do I implement a soft eraser stroke in a CGBitmapContext

I am trying to erase an image with my touch in iOS. By setting the blend mode to kCGBlendModeClear I am able to do this - but only with hard edges. I could draw my stroke with varying line widths and alphas, but it appears that CGContextSetAlpha does not have an effect with kCGBlendModeClear.
How do I go about this?
I would use a transparency layer compositing with kCGBlendModeDestinationOut (Da * (1 - Sa), Dc * (1 - Sa).) Something like this:
CGPathRef pathToErase = ...; // The path you want erased
// could also be an image or (nearly) anything else
// that can be drawn in a bitmap context
CGContextSetBlendMode(ctx, kCGBlendModeDestinationOut);
CGContextBeginTransparencyLayer(ctx, NULL);
{
CGContextSetGrayFillColor(ctx, 0.0, 1.0); // solid black
CGContextAddPath(ctx, pathToErase);
CGContextFillPath(ctx);
// the above two lines could instead be CGContextDrawImage()
// or whatever else you're using to clear
}
CGContextEndTransparencyLayer(ctx);
Note that you should also save and restore the gstate (CGContextSaveGState()/CGContextRestoreGState()) before/after the transparency layer to ensure that the blend mode and any other gstate changes do not persist.
Note: This is brain-compiled and it's possible that transparency layers don't play nice with all blend modes. If so, try drawing the path/image into a second bitmap context, then drawing the contents of that context with the above blend mode.
You can also experiment with other blend modes for different effects.