Custom UIProgressView with image - ios

I’m trying to make a custom UIProgressView where the image that gets filled up is the Nike Swoosh. I’ve tried to follow some tutorials but am getting nowhere.
My current approach:
Make the inside of swoosh transparent and surroundings black.
Then put a big UIProgressView behind that.
Since the middle of the swoosh is transparent, it looks like the swoosh is filling up.
But, modifying the height of the progress bar has proven to be a pain since it messes with the width in a weird way…and it’s hard to align the swoosh with the progress bar for responsiveness.
Are there any other ideas or libraries out there?
Thanks

My suggestion:
draw a second image programatically to apply as a mask against your 'swoosh' image, and then repeat this cyclically.
example, fill image from left (mask it from right)
{
//existing variables
IBOutlet UIImageView *swooshView;
}
-(UIImage *)maskImageOfSize:(CGSize )size filledTo:(CGFloat )percentage{
UIGraphicsBeginImageContextWithOptions ( size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColourWithColour (context, [UIColor blackColor].CGColor);
CGRect fillRect = CGRectZero;
fillRect.size.height = size.height;
fillRect.size.width = size.width * percentage / 100.0;
fillRect.origin.x = (size.width - fillRect.size.width);
CGContextFillRect(context, fillRect);
UIImage *result = UIGraphicsGetImageFromImageContext();
UIGraphicsEndImageContext();
return result;
}
-(void)fillSwooshToPercentage:(CGFloat )percentage{
percentage = ((CGFloat ) fmaxf ( 0.0 , (fminf (100.0, (float) percentage ) ) );
// just policing a 'floor' and 'ceiling'...
swooshView.layer.mask = [self maskImageOfSize:self.swoosh.bounds.size filledTo:percentage];
}

Related

UINavigationBar Custom Color Hairline Border

First of all, I did search this before posting this question but if there is an answer out there, it is buried under the millions of questions about how to remove the default bottom border of a navigation bar.
I do NOT want to remove the bottom border ("shadow") of the navigation bar.
I am trying to "theme" my app by the usual method of using appearance proxies.
I can globaly change most visual attributes of UINavigationBar with code like the following:
let navigationBarProxy = UINavigationBar.appearance()
navigationBarProxy.isTranslucent = false
navigationBarProxy.barTintColor = myBarBackgroundColor
navigationBarProxy.tintColor = myBarTextColor
Regarding the 'hairline' bottom border of the bar (or as it is known, the "shadow"), I can either set the default one by doing nothing or specifying nil:
navigationBarProxy.shadowImage = nil
...or I can specify a custom color by assigning a solid image of the color I'm after:
navigationBarProxy.shadowImage = UIImage.withColor(myBorderColor)
(uses helper extension:)
extension UIImage {
public static func withColor(_ color: UIColor?, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage? {
let rect = CGRect(origin: CGPoint.zero, size: size)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
let actualColor = color ?? .clear
context?.setFillColor(actualColor.cgColor)
context?.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
However, the approach above gives me (on retina devices) a 1pt, 2px border, whereas the default, light gray one is actually 0.5pt, 1px (a.k.a. "hairline").
Is there any way to achieve a 0.5 pt (1px), custom-colored bottom border (shadow) for UINavigationBar?
I guess I could use a runtime generated, background image that is for the most part solid, but has a 1px border of my choice color "baked in" at the bottom. But this seems inelegant at best, and I'm not sure how it would work when that navigation bar height changes: is the image sliced, or simply stretched, or what?
Based on the chosen answer found here (with small changes because it was old):
How to change the border color below the navigation bar?
// in viewDidLoad
UIView * navBorder = [[UIView alloc] initWithFrame:CGRectMake(0,
self.navigationController.navigationBar.frame.size.height, // <-- same height, not - 1
self.navigationController.navigationBar.frame.size.width,
1/[UIScreen mainScreen].scale)]; // <-- 5/5S/SE/6 will be 0.5, 6+/X will be 0.33
// custom color here
[navBorder setBackgroundColor:customColor];
[self.navigationController.navigationBar addSubview:navBorder];
Credit here for finding scale programmatically:
Get device image scale (e.g. #1x, #2x and #3x)
*NOTE:
iPhone 6+/X are x3, so 1px height will be 0.33pt
iPhone 5/5S/SE/6 are x2, so 1px height will be 0.5pt
Tested in simulator, may need to verify on actual devices.
Visually same as default nav bar with a custom color.
I believe you want to remove the shadow. This should help with that.
[[UINavigationBar appearance] setShadowImage:[UIImage new]];
If you want to have different coloured shadow then you can create a image with your desired colour and use it instead of
[UIImage new]
You can use something like this for generating an image yourself
+ (UIImage *)imageWithColor:(UIColor *)color {
CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
const CGFloat alpha = CGColorGetAlpha(color.CGColor);
const BOOL opaque = alpha == 1;
UIGraphicsBeginImageContextWithOptions(rect.size, opaque, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}

Fill UINavigationBar background with CGGradientRef

I would like to fill my UINavigationBar background with CGGradientRef instead of a picture file (e.g. myBackground.png). This practice will avoid having to create a PNG file for each screen size and also save storage space.
I've seen that it's possible to create an UIImage drawing a gradient from scratch and using:
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
Also, I've seen that I can assign an UIImage to a UINavigationBar using:
myNavigationController.navigationBar.barTintColor = [UIColor colorWithPatternImage:image]];
However, I have not been able to put these two together. Some help will be appreciated. Thank you.
Here is a overloaded UINavigationBar class using a two point gradient, although it could easily be improved to cover multi-point gradients.
To enable this bar style, select the navigation bar object in the navigation scene of the storyboard, and set its custom class to GradientNavigationBar.
In this case the awakeFromNib call is used to change the background, (assuming that the Navigation bar class has been changed in the storyboard), in the case that the Navigation bar is instantiated programatically, the customization call should be made in the appropriate position in the code.
The solution works by converting the colors passed to it to an array of CGFloat, then generating a CGGradientRef object, using those colors, creating an image and then using the setBackgroundImage:forBarMetrics call to set the background as required.
#interface GradientNavigationBar
#end
#implementation GradientNavigationBar
-(void) awakeFromNib {
[self setGradientBackground:[UIColor redColor]
endColor:[UIColor yellowColor]];
}
-(void) setGradientBackground:(UIColor *) startColor endColor:(UIColor *) endColor {
// Convert the colors into a format where they can be used with
// core graphics
CGFloat rs, gs, bs, as, re, ge, be, ae;
[startColor getRed:&rs green:&gs blue:&bs alpha:&as];
[endColor getRed:&re green:&ge blue:&be alpha:&ae];
CGFloat colors [] = {
rs, gs, bs, as,
re, ge, be, ae
};
// Generate an Image context with the appropriate options, it may
// be enhanced to take into account that Navbar heights differ
// eg between landscape and portrait in the iPhone
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
CGContextRef gc = UIGraphicsGetCurrentContext();
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
// The gradient element indicates the colors to be used and
// the color space
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;
// Draw the gradient
CGContextDrawLinearGradient(gc, gradient, CGPointMake(0, 0),CGPointMake(0, self.bounds.size.height),0);
// Capture the image
UIImage * backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
// The critical call for setting the background image
// Note that separate calls can be made e.g. for the compact
// bar metric.
[self setBackgroundImage:backgroundImage forBarMetrics:UIBarMetricsDefault];
CGGradientRelease(gradient), gradient = NULL;
UIGraphicsEndImageContext();
}
#end

Draw on UIImage to erase and see through, revealing the UIImage layer underneath [duplicate]

This question already has an answer here:
How to erase piece of UIImageView with png-brush and UIBezierPath
(1 answer)
Closed 9 years ago.
I spent the last few days trying to find a way, but I'm stuck.
At a certain point of my application I would like two UIImages overlaid, and when I start "drawing" on the first layer it would erase the image and let us see through, to be able to reveal the content underneath.
I don't know my ways in core graphics, and after spending days on the net I'm wondering if it is possible.
Is there anyone who could help me, or point me to the direction I should follow ?
Any help would be very appreciated.
Have a nice day.
Create one ScratchView class and put your Scratch image in initMethod like:-
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
scratchable = [UIImage imageNamed:#"scratchable.jpg"].CGImage;
width = CGImageGetWidth(scratchable);
height = CGImageGetHeight(scratchable);
self.opaque = NO;
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceGray();
CFMutableDataRef pixels = CFDataCreateMutable( NULL , width * height );
alphaPixels = CGBitmapContextCreate( CFDataGetMutableBytePtr( pixels ) , width , height , 8 , width , colorspace , kCGImageAlphaNone );
provider = CGDataProviderCreateWithCFData(pixels);
CGContextSetFillColorWithColor(alphaPixels, [UIColor blackColor].CGColor);
CGContextFillRect(alphaPixels, frame);
CGContextSetStrokeColorWithColor(alphaPixels, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(alphaPixels, 20.0);
CGContextSetLineCap(alphaPixels, kCGLineCapRound);
CGImageRef mask = CGImageMaskCreate(width, height, 8, 8, width, provider, nil, NO);
scratched = CGImageCreateWithMask(scratchable, mask);
CGImageRelease(mask);
CGColorSpaceRelease(colorspace);
}
return self;
}
and also create second view class for display image of background after Scratch
Bellow this example is much useful to you try with this:-
https://github.com/oyiptong/CGScratch

Tinting UIImage to a different color, OR, generating UIImage from vector

I have a circle with a black outline, and a white fill that I need to programmatically make the white into another color (via a UIColor). I've tried a handful of other stackoverflow solutions but none of them seem to work correctly, either filling just the outside or an outline.
I have two ways I could do this but I am unsure of how I would get the right results:
Tint just the white color to whatever the UIColor should be,
or,
Make a UIImage from two circles, one being filled and one overlapping that with black.
If you decide to use two circles, one white and one black, then you may find this helpful. This method will tint a uiimage one for you but it addresses the problem of only tinting the opaque part, meaning it will only tint the circle if you provide a png with transparency around the circle. So instead of filling the entire 24x24 frame of the image it fills only the opaque parts. This isn't exactly your question but you'll probably come across this problem if you go with the second option you listed.
-(UIImage*)colorAnImage:(UIColor*)color :(UIImage*)image{
CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, image.scale);
CGContextRef c = UIGraphicsGetCurrentContext();
[image drawInRect:rect];
CGContextSetFillColorWithColor(c, [color CGColor]);
CGContextSetBlendMode(c, kCGBlendModeSourceAtop);
CGContextFillRect(c, rect);
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
Extend a UIView and just implement the drawRect method. For example, this will draw a green circle with a black outline.
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef gc = UIGraphicsGetCurrentContext();
[[UIColor greenColor] setFill];
CGContextFillEllipseInRect(gc, CGRectMake(0,0,24,24));
[[UIColor blackColor] set];
CGContextStrokeEllipseInRect(gc, CGRectMake(0,0,24,24));
}
For such simple shapes, just use CoreGraphics to draw a square and a circle -- adding the ability to set the fill color in your implementation.
If it's just black and white - then altering the white to another color is not so difficult when you know the color representations. Unfortunately, this is more complex to write and execute so… my recommendation is to just go straight to CoreGraphics for the simple task you outlined (bad pun, sorry).
here's a quick demo:
static void InsetRect(CGRect* const pRect, const CGFloat pAmount) {
const CGFloat halfAmount = pAmount * 0.5f;
*pRect = CGRectMake(pRect->origin.x + halfAmount, pRect->origin.y + halfAmount, pRect->size.width - pAmount, pRect->size.height - pAmount);
}
static void DrawBorderedCircleWithWidthInContext(const CGRect pRect, const CGFloat pWidth, CGContextRef pContext) {
CGContextSetLineWidth(pContext, pWidth);
CGContextSetShouldAntialias(pContext, true);
CGRect r = pRect;
/* draw circle's border */
CGContextSetRGBStrokeColor(pContext, 0.8f, 0.7f, 0, 1);
InsetRect(&r, pWidth);
CGContextStrokeEllipseInRect(pContext, r);
/* draw circle's fill */
CGContextSetRGBFillColor(pContext, 0, 0, 0.3f, 1);
InsetRect(&r, pWidth);
CGContextFillEllipseInRect(pContext, r);
}

iOS: Applying a RGB filter to a greyscale PNG

I have a greyscale gem top view.
(PNG format, so has alpha component)
I would like to create 12 small size buttons, each in a different colour, from this image.
For the sake of tidiness, I would like to do this within the code rather than externally in some art package.
Can anyone provide a method (or even some code) for doing this?
PS I am aware of how to do it in GL using a ridiculous amount of code, I'm hoping there is a simpler way using core graphics / core animation
EDIT: Working solution, thanks to awesomeness from below answer
CGSize targetSize = (CGSize){100,100};
UIImage* image;
{
CGRect rect = (CGRect){ .size = targetSize };
UIGraphicsBeginImageContext( targetSize );
{
CGContextRef X = UIGraphicsGetCurrentContext();
UIImage* uiGem = [UIImage imageNamed: #"GemTop_Dull.png"];
// draw gem
[uiGem drawInRect: rect];
// overlay a red rectangle
CGContextSetBlendMode( X, kCGBlendModeColor ) ;
CGContextSetRGBFillColor ( X, 0.9, 0, 0, 1 );
CGContextFillRect ( X, rect );
// redraw gem
[uiGem drawInRect: rect
blendMode: kCGBlendModeDestinationIn
alpha: 1. ];
image = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
}
The easiest way to do it is to draw the image into an RGB-colorspaced CGBitmapContext, use CGContextSetBlendMode to set kCGBlendModeColor, and then draw over it with a solid color (e.g. with CGContextFillRect).
The best looking results are going to come from using the gray value to index into a gradient that goes from the darkest to the lightest colors of the desired result. Unfortunately I don't know the specifics of doing that with core graphics.
This is an improvement upon the answer in the question and an implementation of #Anomie
First, put this at the beginning of your UIButton class, or your view controller. It translates from UIColor to an RGBA value, which you will need later.
typedef enum { R, G, B, A } UIColorComponentIndices;
#implementation UIColor (EPPZKit)
- (CGFloat)redRGBAValue {
return CGColorGetComponents(self.CGColor)[R];
}
- (CGFloat)greenRGBAValue {
return CGColorGetComponents(self.CGColor)[G];
}
- (CGFloat)blueRGBAValue {
return CGColorGetComponents(self.CGColor)[B];
}
- (CGFloat)alphaRGBAValue {
return CGColorGetComponents(self.CGColor)[A];
}
#end
Now, make sure that you have your custom image button in IB, with a grayscale image and the right frame. This is considerably better and easier then creating the custom image button programmatically, because:
you can let IB load the image, instead of having to load it manually
you can adjust the button and see it visually in IB
your IB will look more like your app at runtime
you don't have to manually set frames
Assuming you are having the button be in IB (near the bottom will be support for having it programmatically created), add this method to your view controller or button cub class:
- (UIImage*)customImageColoringFromButton:(UIButton*)customImageButton fromColor:(UIColor*)color {
UIImage *customImage = [customImageButton.imageView.image copy];
UIGraphicsBeginImageContext(customImageButton.imageView.frame.size); {
CGContextRef X = UIGraphicsGetCurrentContext();
[customImage drawInRect: customImageButton.imageView.frame];
// Overlay a colored rectangle
CGContextSetBlendMode( X, kCGBlendModeColor) ;
CGContextSetRGBFillColor ( X, color.redRGBAValue, color.greenRGBAValue, color.blueRGBAValue, color.alphaRGBAValue);
CGContextFillRect ( X, customImageButton.imageView.frame);
// Redraw
[customImage drawInRect:customImageButton.imageView.frame blendMode: kCGBlendModeDestinationIn alpha: 1.0];
customImage = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
return customImage;
}
You then will need to call it in a setup method in your view controller or button subclass, and set the imageView of the button to it:
[myButton.imageView setImage:[self customImageColoringFromButton:myButton fromColor:desiredColor]];
If you are not using IB to create the button, use this method:
- (UIImage*)customImageColoringFromImage:(UIImage*)image fromColor:(UIColor*)color fromFrame:(CGRect)frame {
UIImage *customImage = [image copy];
UIGraphicsBeginImageContext(frame.size); {
CGContextRef X = UIGraphicsGetCurrentContext();
[customImage drawInRect: frame];
// Overlay a colored rectangle
CGContextSetBlendMode( X, kCGBlendModeColor) ;
CGContextSetRGBFillColor ( X, color.redRGBAValue, color.greenRGBAValue, color.blueRGBAValue, color.alphaRGBAValue);
CGContextFillRect ( X, frame);
// Redraw
[customImage drawInRect:frame blendMode: kCGBlendModeDestinationIn alpha: 1.0];
customImage = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
return customImage;
}
And call it with:
[self.disclosureButton.imageView setImage:[self customImageColoringFromImage:[UIImage imageNamed:#"GemTop_Dull.png"] fromColor:desiredColor fromFrame:desiredFrame]];

Resources