Fill UIImage like a gauge using CG/Quartz - ios

Lets say i have a an image (a semi-circle gauge) with colors starting from green to yellow to red. How can i programmatically clip and fill the image for a given percentage dynamically using CoreGraphics/Quartz ?

You can use a CALayer with a custom mask.
The mask will be a CAShapeLayer with a path that defines the given percentage of the guage.
CALayer *guageLayer = //your CALayer, could be the backing view
CAShapeLayer *guageMask = [[CAShapeLayer alloc] init];
guageMask.path = [self _pathForCurrentGuage]; //bezier path based on your current percentage
guageLayer.mask = guageMask;

Related

CAShapeLayer : Identify shape : Add CATextLayer in that shape

I have SVG file and i was able to access SVG's shapes using SVGKit.
Now, I have CAShapeLayer which may contain circle, square or any closed shape.
I want to add CATextLayer on CAShapeLayer in such way that text should not cross the defined shape.
Here is the example of crossing CATextLayer on CAShapeLayer :
It's crossing just because, CATextLayer is starting from 0 position of CAShapeLayer which contains circle in particular this case.
In my case, CAShapeLayer can contain any closed shape. It can be oval also.
How can I identify shape inside CAShapeLayer? or How can I apply path to CATextLayer? which will makes sure text will be drawn inside shape?
Here is code to add CATextLayer On CAShapeLayer:
-(CATextLayer *)addTrackerNumberToLayer:(NSString *)numbers{
self.numbersTextLayer = [CATextLayer new];
self.numbersTextLayer.foregroundColor = [UIColor blackColor].CGColor;
self.numbersTextLayer.font = (__bridge CFTypeRef _Nullable)([UIFont boldSystemFontOfSize:15]);
self.numbersTextLayer.fontSize=25;
self.numbersTextLayer.anchorPoint = CGPointZero;
self.numbersTextLayer.frame = CGRectMake(self.parentShapeLayer.bounds.size.width*0.05, self.parentShapeLayer.bounds.size.height*0.05, self.parentShapeLayer.bounds.size.width*0.90, self.parentShapeLayer.bounds.size.height*0.30);
self.numbersTextLayer.position = CGPointMake(self.parentShapeLayer.bounds.size.width*0.05,self.parentShapeLayer.bounds.size.height*0.05);
self.numbersTextLayer.alignmentMode = kCAAlignmentCenter;
[self.parentShapeLayer addSublayer:self.numbersTextLayer];
}
Use isWrapped property of CATextLayer this determines whether the text is wrapped to fit within the receiver’s bounds.
-(CATextLayer *)addTrackerNumberToLayer:(NSString *)numbers{
self.numbersTextLayer = [CATextLayer new];
self.numbersTextLayer.foregroundColor = [UIColor blackColor].CGColor;
self.numbersTextLayer.font = (__bridge CFTypeRef _Nullable)([UIFont boldSystemFontOfSize:15]);
self.numbersTextLayer.fontSize=25;
self.numbersTextLayer.anchorPoint = CGPointZero;
self.numbersTextLayer.frame = CGRectMake(self.parentShapeLayer.bounds.size.width*0.05, self.parentShapeLayer.bounds.size.height*0.05, self.parentShapeLayer.bounds.size.width*0.90, self.parentShapeLayer.bounds.size.height*0.30);
self.numbersTextLayer.position = CGPointMake(self.parentShapeLayer.bounds.size.width*0.05,self.parentShapeLayer.bounds.size.height*0.05);
self.numbersTextLayer.alignmentMode = kCAAlignmentCenter;
[self.numbersTextLayer setWrapped:YES];//User Wrapped to YES to imbound the text to fit in the region.
[self.parentShapeLayer setMasksToBounds:YES];
[self.parentShapeLayer addSublayer:self.numbersTextLayer];
return self.numbersTextLayer;
}

How to create shadow effect on UIImage bounds

I have a UIImageView in which I have a UIImage obviously. I want to create a shadow effect only on the UIImage. My problem is that I cannot get the CGRect of the UIImage inside the UIImageView so I can apply the shadow effect on it by using the following method.
[mImageView.layer.shadowColor = [UIColor grayColor].CGColor;
mImageView.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
mImageView.layer.shadowOpacity = 0.9f;
mImageView.layer.masksToBounds = NO;
CGRect imageFrame = mImageView.frame;
UIEdgeInsets shadowInsets = UIEdgeInsetsMake(0, 0, -1.5f, 0);
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:UIEdgeInsetsInsetRect(imageFrame, shadowInsets)];
mImageView.layer.shadowPath = shadowPath.CGPath;
Please consider the image attached for this problem.
The problem is critical too because the UIImage can be an image of a rigid dimension because it is a cropped image as you can see in the picture attached.
The UIImageView’s bound is equal to the view’s bound here. So when applying the effect using the method above, it creates a UIBezierPath on the whole UIImageView, not only to the UIImage. As in the method, I cannot get the exact CGRect of the UIImage.
Any solution? What am I missing?
cropped image
UIImage is always rectangular, so is UIImageView. I believe you want to put shadow only around the jagged border of the cropped area right? If that is the case, you cannot use this method. You need to use CoreGraphics or others, to get the effect you want. For example, you can create a copy of this image in memory, blackened it, and blur it and paste it behind your image to create a shadowy effect.

IOS: Cannot Change Color of CAShapeLayer

I want to create a layer to act as a mask for my UIImageView which was created in interface builder. However, no matter what I do the layer remains white. The code is pretty straightforward any ideas what is causing this behavior ?
UIImage* image = [UIImage imageNamed:#"face"];
self.imageView.image = image;
CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
maskLayer.frame = self.imageView.bounds;
maskLayer.fillColor = [[UIColor blackColor] CGColor];
maskLayer.path = CGPathCreateWithRect(self.imageView.bounds, NULL);
self.imageView.layer.mask = maskLayer;
self.maskLayer = maskLayer;
I edited the code and added the path but it still does not work.
You seem to have a misconception as to what a mask is. It is a set of instructions, in effect, for injecting transparency into a view, thus "punching a hole" through the view to a greater or lesser extent (depending on the degree of transparency). It has no color. You are seeing white because that is the color of what is behind your image view — you have punched a hole through the entire image view and made it invisible.
From the docs:
The layer’s alpha channel determines how much of the layer’s content
and background shows through. Fully or partially opaque pixels allow
the underlying content to show through but fully transparent pixels
block that content.
Black fill (interior of the closed path) = opaque pixels
Area outside of the closed path = transparent pixels
Image will appear on the interior region of your path and not appear outside of that path. The actual colour is irrelevant: when you set a layer as a mask, all we are interested in now is opacity.
The path is ostensively the same size as the imageView, so you won't see any difference as the mask matches the imageView bounds. Additionally , if you use this code before the geometry is fully set, such as in viewDidLoad, you may not get the results you expect.
As matt suggests - you need to think what result you are after.
If you want a "black translucent color" - or similar - consider adding another translucent-colored layer to the mix. But this is not a mask.

Implement Blur over parts of view

How can I implement the image below pragmatically - meaning the digits can change at runtime or even be replaced with a movie?
Just add a blurred UIView on top of your thing.
For example...make a UIImage of your desired view size, blur it using CIFilter and then add it to your view .It should achieve the desired effect.
This is generally the same question and is answered by quite a few methods.. Anyway I would propose 1 more:
Get the image from UIView
+ (UIImage *)imageFromLayer:(CALayer *)layer {
UIGraphicsBeginImageContext([layer frame].size);
[layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
rather yet play around a bit with this to get the desired part of the view as the image. Now create a new view and add to it image views (with the image you get from layer). Then move the centers of the image views to achieve gaussian algorithm and take the image from this layer again and place it back on the original view.
Moving the center should be defined by radius fragment (I'd start with .5f) and resample range.
for(int i=1; i<resampleCount; i++) {
view1.center = CGPointMake(view1.center.x + radiusFragment*i, view1.center.y);
view2.center = CGPointMake(view2.center.x - radiusFragment*i, view2.center.y);
view3.center = CGPointMake(view3.center.x, view3.center.y + radiusFragment*i);
view4.center = CGPointMake(view4.center.x, view4.center.y - radiusFragment*i);
//add the subviews
}
//get the image from view
All the subviews need to have alpha set to 1.0f/(resampleCount*4)
This method might not be the fastest but it would be extremely easy to implement and if you can pimp the radius and resample range to minimum fragments it should do pretty well.
use a UIView whith white background and decrease the alpha property
blurView.backgroundColor=[UIColor colorWithRed:255 green:255 blue:255 alpha:0.3]

How to mask UIViews in iOS

I've seen similar questions, but haven't found workable answers.
I want to mask a UIView using a grey image (need to convert to alpha scale for masking). The UIView has background. It should be easy to mask an image, but I want to mask any UIView.
Any clues will be appreciated.
I've been working on this problem for a couple of hours and have a solution that I think will do what you want. First, create your masking image using whatever means you see fit. Note that we only need the alpha values here, all other colours will be ignored, so make certain that the method you use supports alpha values. In this example I'm loading from a .png file, but don't try it with .jpg files as they don't have alpha values.
Next, create a new layer, assign your mask to its contents and set this new layer to your UIView's own layer, like so: you should find that this masks the UIView and all its attached subviews:
UIImage *_maskingImage = [UIImage imageNamed:#"mask"];
CALayer *_maskingLayer = [CALayer layer];
_maskingLayer.frame = theView.bounds;
[_maskingLayer setContents:(id)[_maskingImage CGImage]];
[theView.layer setMask:_maskingLayer];
With this done, you can set the UIView's background colour to whatever you like and the mask will be used to create a coloured filter.
EDIT: As of iOS8 you can now mask a view simply by assigning another view to its maskView property. The general rules stay the same in that the maskView's alpha layer is used to determine the opacity of the view it is applied to.
For apps targeting iOS 8.0+ this worked well (in this case, using a gradient as the mask) It avoids any need to resize or position the mask.
// Add gradient mask to view
func addGradientMask(targetView: UIView)
{
let gradientMask = CAGradientLayer()
gradientMask.frame = targetView.bounds
gradientMask.colors = [UIColor.blackColor().CGColor, UIColor.clearColor().CGColor]
gradientMask.locations = [0.8, 1.0]
let maskView: UIView = UIView()
maskView.layer.addSublayer(gradientMask)
targetView.maskView = maskView
}
In my case, I want to remove the mask once the user starts scrolling. This is done with:
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
exerDetailsTableView.maskView = nil
}
where the view is defined as an #IBOutlet:
#IBOutlet weak var exerDetailsTableView: UITableView!
Result:
I don't know the exact code off the top of my head but the basic idea is to have two UIViews. One UIView would have it's image property set to be the grey scale image and the other UIView would be set as usual the only difference is that you would position the initial UIView directly on top of the UIView containing the "normal" image.
I hope that is enough to push your idea a step further.

Resources