slow pattern drawing for a backgroundView - ios

for my tableview I've done a custom background view which I call on viewDidLoad on each tableView on my project:
- (void)viewDidLoad{
[super viewDidLoad];
//other unrelated sutff
self.tableView.backgroundView=[[BlueStyleBackground alloc]init];
}
It was my understanding that doing quartz2D stuff was the best option performance-wise. However this background view takes 0.45 seconds (average) to get drawn. This makes all the naviagation among tableViews a bit slow, not much but enough to notice it.
The background view is a gradient with a pattern overlaped. I've found out that the gradient gets drawn in 0.12 seconds, so the bottle neck appears to be the pattern drawing. What surprises me is that, from my point of view, it doesn't seem like a complicated thing to draw, it's only one horizontal line with a shadow.
Here is how the pattern is called (inside recDraw):
static const CGPatternCallbacks callbacks = { 0, &MyDrawColoredPattern2, NULL };
CGContextSaveGState(context);
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetFillColorSpace(context, patternSpace);
CGColorSpaceRelease(patternSpace);
CGPatternRef pattern = CGPatternCreate(NULL,
rect,//superficie que ha de cobrir CGRectMake(0, 0,self.bounds.size.width,8),
CGAffineTransformIdentity,
self.bounds.size.width, // espai entre patrons (X)
2, // espai entre patrons (Y)
kCGPatternTilingConstantSpacing,
true,
&callbacks);
and this is the pattern code:
void MyDrawColoredPattern2 (void *info, CGContextRef context) {
UIColor* __autoreleasing blauClar = [UIColor colorWithRed: 0.65 green: 0.65 blue: 0.65 alpha: 1];
UIColor* __autoreleasing blauFosc = [UIColor colorWithRed: 0.106 green: 0.345 blue:0.486 alpha:1];
CGColorRef dotColor = blauFosc.CGColor;
CGColorRef shadowColor = blauClar.CGColor;
CGContextSetFillColorWithColor(context, dotColor);
CGContextSetShadowWithColor(context, CGSizeMake(0, 1), 5, shadowColor);
CGContextFillRect(context, CGRectMake(0, 0, 480, 1));
}
Previously I did a pattern which draw two hexagons inside a 24+24 square. It looked more complicated that the code above but it takes only 0.15 seconds to get drawn.
Am I doing something wrong here? My knowledge about quartz drawing is not really that big and I would appreciate some inputs. Thanks in advance!

I hate to answer myself, but after some trial and error I found that changing the pattern code to the following, does the trick:
UIColor *__autoreleasing tileColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"pattern6-65.png"]];
CGColorRef tileCGColor = [tileColor CGColor];
CGColorSpaceRef colorSpace = CGColorGetColorSpace(tileCGColor);
CGContextSetFillColorSpace(context, colorSpace);
if (CGColorSpaceGetModel(colorSpace) == kCGColorSpaceModelPattern)
{
CGFloat alpha = 1.0f;
CGContextSetFillPattern(context, CGColorGetPattern(tileCGColor), &alpha);
}
else
{
CGContextSetFillColor(context, CGColorGetComponents(tileCGColor));
}
CGContextFillRect(context, CGRectMake(0, 0, 10, 10));
What it does is to fill the screen with a pattern generated with a very small png. So I had to draw the png with photoshop and then add it to the project. The bigger the png the faster it gets, but the more difficult is it to draw in photoshop.
The call for the pattern must be modiffied to fit the size of the png (10 pixels here):
CGPatternRef pattern = CGPatternCreate(NULL,
rect,//superficie que ha de cobrir CGRectMake(0, 0,self.bounds.size.width,8),
CGAffineTransformIdentity,
10, // espai entre patrons (X) self.bounds.size.width
10, // espai entre patrons (Y)
kCGPatternTilingConstantSpacing,
true,
&callbacks);
using this 10px png, the whole drawing time has drop to 0.18 seconds (gradient background plus pattern).
I hope it helps somebody with the same problem.

If I understand it right, you are trying to draw gradient pattern onto tableView background.
I would recommend you to create .png image in any graphic editor and put it onto the background.
Hope it helps

Related

CGPatternDrawPatternCallback not called in iOS 12 /Objective C

The following Objective-C code has worked correctly in iOS 9 - 11. It draws a checkerboard with colored squares. For some reason the callback that adds the colors is not being called in iOS 12 and Xcode 10.0. I've tried a variety of fixes but nothing obvious has worked. Something seems to have changed in iOS 12 but nothing I tried has fixed the problem.
-(id)initWithFrame:(CGRect)frame checkerSize:(CGSize)checkerSize darkColor:(UIColor *)darkShade lightColor:(UIColor *)lightShade {
self = [super initWithFrame:frame];
if(self != nil)
{
// Initialize the property values
checkerHeight = checkerSize.height;
checkerWidth = checkerSize.width;
self.darkColor = darkShade;
self.lightColor = lightShade;
// Colored Pattern setup
CGPatternCallbacks coloredPatternCallbacks = {0, ColoredPatternCallback, NULL};
CGRect clippingRectangle = CGRectMake(0.0, 0.0, 2.0*checkerWidth, 2.0*checkerHeight);
// First we need to create a CGPatternRef that specifies the qualities of our pattern.
CGPatternRef coloredPattern = CGPatternCreate(
(__bridge_retained void *)self, // 'info' pointer for our callback
clippingRectangle, // the pattern coordinate space, drawing is clipped to this rectangle
CGAffineTransformIdentity, // a transform on the pattern coordinate space used before it is drawn.
2.0*checkerWidth, 2.0*checkerHeight, // the spacing (horizontal, vertical) of the pattern - how far to move after drawing each cell
kCGPatternTilingNoDistortion,
true, // this is a colored pattern, which means that you only specify an alpha value when drawing it
&coloredPatternCallbacks); // the callbacks for this pattern.
// To draw a pattern, you need a pattern colorspace.
// Since this is an colored pattern, the parent colorspace is NULL, indicating that it only has an alpha value.
CGColorSpaceRef coloredPatternColorSpace = CGColorSpaceCreatePattern(NULL);
CGFloat alpha = 1.0;
// Since this pattern is colored, we'll create a CGColorRef for it to make drawing it easier and more efficient.
// From here on, the colored pattern is referenced entirely via the associated CGColorRef rather than the
// originally created CGPatternRef.
coloredPatternColor = CGColorCreateWithPattern(coloredPatternColorSpace, coloredPattern, &alpha);
CGColorSpaceRelease(coloredPatternColorSpace);
CGPatternRelease(coloredPattern);
}
return self;
}
void ColoredPatternCallback(void *info, CGContextRef context) {
HS_QuartzPatternView *self = (__bridge_transfer id)info; // needed to access the Obj-C properties from the C function
CGFloat checkerHeight = [self checkerHeight];
CGFloat checkerWidth = [self checkerWidth];
// "Dark" Color
UIColor *dark = [self darkColor];
CGContextSetFillColorWithColor(context, dark.CGColor);
CGContextFillRect(context, CGRectMake(0.0, 0.0, checkerWidth, checkerHeight));
CGContextFillRect(context, CGRectMake(checkerWidth, checkerHeight, checkerWidth, checkerHeight));
// "Light" Color
UIColor *light = [self lightColor];
CGContextSetFillColorWithColor(context, light.CGColor);
CGContextFillRect(context, CGRectMake(checkerWidth, 0.0, checkerWidth, checkerHeight));
CGContextFillRect(context, CGRectMake(0.0, checkerHeight, checkerWidth, checkerHeight));
}

Custom UIProgressView with image

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];
}

iOS, How to draw a rough line, for fun apps?

I'm trying to draw a rough line, a normal straight line just looks odd in my app. I'm using a fun font etc and a straight line just doesn't work.
When I say rough, I mean I want to add some noise or texture to it.
I've tried adding an image as a texture but the options I've tried use a uniform tiled approach which looks too georometricly sound.
I need to either add a scatter effect to draw my texture image roughly in the shape of a line or some sort of random approach to add noise.
I know I could just draw a rough line in a imaging app and use that, but I'm not sure about colouring and size at this stage and I'd rather use something dynamic.
I've spend far too long on this issue and a lot if time googling and can't find anything I can use, so if you have a solution please provide or point me at some example code.
Thanks.
This is the best solution I've found, but it's too uniform...
#import "DrawTexturedLine.h"
#implementation DrawTexturedLine
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[self patternMake2:rect context:context];
}
void pattern2Callback (void *info, CGContextRef context) {
UIImage *image = [UIImage imageNamed:#"particle.png"];
CGImageRef imageRef = [image CGImage];
CGContextDrawImage(context, CGRectMake(0, 0, 50, 25), imageRef);
}
- (void)patternMake2:(CGRect)rect context:(CGContextRef)context
{
static const CGPatternCallbacks callbacks = { 0, &pattern2Callback, NULL };
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetFillColorSpace(context, patternSpace);
CGColorSpaceRelease(patternSpace);
CGSize patternSize = CGSizeMake(25/2.0, 25);
CGPatternRef pattern = CGPatternCreate(NULL, self.bounds,
CGAffineTransformIdentity, patternSize.width, patternSize.height,
kCGPathFillStroke, true, &callbacks);
CGFloat alpha = 1;
CGContextSetFillPattern(context, pattern, &alpha);
CGPatternRelease(pattern);
CGContextFillRect(context, rect);
//CGContextRestoreGState(context);
}
#end
Not quite answering your question but you could draw the lines in a paint package and include those with your app and then use code like this to set the colour of the lines.
https://coffeeshopped.com/2010/09/iphone-how-to-dynamically-color-a-uiimage
If you were to use Sprite Kit it becomes much easier to change colors of images
[SKAction colorizeWithColor:[self randomColor]
colorBlendFactor:1.0
duration:0]];
I'm just returning to this issue and I've spent far too much time on this, looking at various tinting solutions. In the end I had to use a paint package to do most of the work. If I had been better with it I could have done the whole thing in a paint package.
I ended up drawing some lines with a black marker scanning them into gray scale then colorizing it.

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