How to vertical center a `UISlider` thumb image? - ios

I'm customizing an UISlider and started drawing my own line which I want then to customize more.
A thing I noticed immediately is that the line seemed not to be draw in the middle of the UISlider rectangle. Can someone confirm that this is an intended behavior of UISlider or I'm doing something wrong here?
And the outcome is this:
On the top there are 2 pixel (marked red) and on the bottom there are 4 pixel (also marked red). Why is the line and the thumb image not in the middle?
The code I use is this:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
UIGraphicsBeginImageContextWithOptions(rect.size, TRUE, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
// Line selected side
[self drawLine:context onRect:rect withColor:[[UIColor grayColor] CGColor]];
UIImage *selectedSide = [UIGraphicsGetImageFromCurrentImageContext() resizableImageWithCapInsets:UIEdgeInsetsZero];
// Line unselected side
[self drawLine:context onRect:rect withColor:[[UIColor colorWithRed:0 green:122/255.0 blue:201/255.0 alpha:1.0] CGColor]];
UIImage *unselectedSide = [UIGraphicsGetImageFromCurrentImageContext() resizableImageWithCapInsets:UIEdgeInsetsZero];
UIGraphicsEndImageContext();
[self setMinimumTrackImage:selectedSide forState:UIControlStateNormal];
[self setMaximumTrackImage:unselectedSide forState:UIControlStateNormal];
}
- (void) drawLine:(CGContextRef)context onRect:(CGRect)rect withColor:(CGColorRef)color
{
int halfRectHeight = rect.size.height / 2;
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextMoveToPoint(context, halfRectHeight, halfRectHeight);
CGContextAddLineToPoint(context, rect.size.width - halfRectHeight, halfRectHeight);
CGContextSetStrokeColorWithColor(context, color);
CGContextStrokePath(context);
}

Related

Objective- C: Draw border on an UIImage

I currently have the following image on which I am trying to set a border. It consists of an UIImageView with an image inside (a transparent.png)
When I try to set the border for my image (see code), it gives a border to the UIImage, but it doesn't 'snap' around my image. Is it possible to achieve that effect?
See image current implementation here.
- (UIImage*)imageWithBorderFromImage:(UIImage*)source;
{
CGSize size = [source size];
UIGraphicsBeginImageContext(size);
CGRect rect = CGRectMake(0, 0, size.width, size.height);
[source drawInRect:rect blendMode:kCGBlendModeNormal alpha:1.0];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 1.0, 0.5, 1.0, 1.0);
CGContextStrokeRect(context, rect);
UIImage *testImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return testImg;
}
Try Adding a layer behind UIImageView and add a border to it that will do the trick
#define kBorderWidth 3.0.
#define kCornerRadius 8.0
CALayer *borderLayer = [CALayer layer];
CGRect borderFrame = CGRectMake(0, 0, (imageView.frame.size.width), (imageView.frame.size.height));
[borderLayer setBackgroundColor:[[UIColor clearColor] CGColor]];
[borderLayer setFrame:borderFrame];
[borderLayer setCornerRadius:kCornerRadius];
[borderLayer setBorderWidth:kBorderWidth];
[borderLayer setBorderColor:[[UIColor redColor] CGColor]];
[imageView.layer addSublayer:borderLayer];
And don't forget to import QuartzCore/QuartzCore.h
This example will draw a boarder on the layer, but change it's frame slightly to make the border around the layer.
Depending on your needs, if you don't want it to be as accurate as possible, a quick and dirty solution could be something like this:
- (UIImage *)borderedImageFromImage:(UIImage *)source andColor:(UIColor *)borderColor{
CGFloat scale = 0.95;//this determines how big the border will be, the smaller it is the bigger the border
UIImage *borderImage = [source imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIGraphicsBeginImageContextWithOptions(source.size, NO, source.scale);
[borderColor set];
[borderImage drawInRect:CGRectMake(0, 0, source.size.width, source.size.height)];
[source drawInRect:CGRectMake(source.size.width*(1-scale)/2,
source.size.height*(1-scale)/2,
source.size.width * scale,
source.size.height * scale)];
borderImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return borderImage;
}
and here is how to use it:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.heartImageView.image = [self borderedImageFromImage:[UIImage imageNamed:#"heart"] andColor:[UIColor blackColor]];
}
What this essentially does is draw the image you want twice, once in the colour of the border (slightly scaled) and once with the normal colour. Your mileage may vary depending on the image.

How can my custom UIView draw a clear background?

I need to make my own UITableViewCellAccessoryDisclosureIndicator so I can have custom colors. I need to set the background to clear. No matter what I try, I can't get the clearColor to work. When I change the CGContextSetFillColorWithColor line below to redColor, it's red. I've tried setting opaque and backgroundColor and then calling super drawRect with my fill code removed and with my fill code present, but it's still no good. I've also tried CGContextClearRect in various ways.
Here's the red color version from the simulator. I need the UIView to be clear so that the pretty background image shows through.
I'm not terribly concerned with scrolling performance because this table will have very few rows.
- (instancetype) init {
self = [super init];
if (!self) return nil;
// [self setOpaque:NO];
// [self setBackgroundColor:[UIColor clearColor]];
return self;
}
- (void)drawRect:(CGRect)rect {
// [super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
// my obviously silly/naive attempt at making the background clear
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor); // redColor works
CGContextFillRect(context, rect);
// drawing code for chevron
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
CGContextSetLineWidth(context, 3.f);
CGContextSetLineJoin(context, kCGLineJoinMiter);
CGContextMoveToPoint(context, PADDING, PADDING);
CGContextAddLineToPoint(context, self.frame.size.width - PADDING, self.frame.size.height/2);
CGContextAddLineToPoint(context, PADDING, self.frame.size.height - PADDING);
CGContextStrokePath(context);
CGContextRestoreGState(context);
}
How about this?
self.contentView.opaque = NO;
self.backgroundColor = [UIColor clearColor];
Designated. Flippin. Initializers. What a waste of time.
UIView's designated initializer is initWithFrame. initWithFrame doesn't call init. Notice that I overrided init. I had assumed that initWithFrame would call init.
Here's what works (notice I don't have to paint the background even though I'm not calling super.drawRect):
- (instancetype) initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (!self) return nil;
// assume decent defaults
[self setColor:[UIColor darkGrayColor]];
[self setBackgroundColor:[UIColor clearColor]];
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetStrokeColorWithColor(context, [self color].CGColor);
CGContextSetLineWidth(context, 3.f);
CGContextSetLineJoin(context, kCGLineJoinMiter);
CGContextMoveToPoint(context, PADDING, PADDING);
CGContextAddLineToPoint(context, rect.size.width - PADDING, rect.size.height/2);
CGContextAddLineToPoint(context, PADDING, rect.size.height - PADDING);
CGContextStrokePath(context);
CGContextRestoreGState(context);
}

iOS make hole in UIView other shape than square

I have a UIView that draws different shapes. I can make a hole in my image to have it transparent, working fine, but the hole is only square,
//hole
CGRect holeRectValue = CGRectMake(300, 40, 80, 100);
CGRect holeRectIntersection = CGRectIntersection( holeRectValue, rect );
[[UIColor clearColor] setFill];
UIRectFill(holeRectIntersection);
Now I need to make the hole in the image but not as a rect, as the shape of my drawn figure,
Here is the code:
- (id)initWithFrame:(CGRect)frame forNode0:(CGPoint)node0 forNode1:(CGPoint)node1 fornode2:(CGPoint)node2 fornode3:(CGPoint)node3 fornode4:(CGPoint)node4 fornode5:(CGPoint)node5 fornode6:(CGPoint)node6 fornode7:(CGPoint)node7 fornode8:(CGPoint)node8 fornode9:(CGPoint)node9
{
self = [super initWithFrame:frame];
if (self) {
self.opaque = NO;
self.node0Pos = node0;
self.node1Pos = node1;
self.node2Pos = node2;
self.node3Pos = node3;
self.node4Pos = node4;
self.node5Pos = node5;
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
//bgnd
UIGraphicsBeginImageContext(self.frame.size);
[[UIImage imageNamed:#"cat.jpeg"] drawInRect:self.bounds];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[[UIColor colorWithPatternImage:image] setFill];
// Drawing code
UIRectFill(rect);
// Drawing code
//1. begin new path
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath(context);
//2. move to initial starting point
CGContextMoveToPoint(context, self.node0Pos.x, self.node0Pos.y);
//3. add lines defining shape
CGContextAddLineToPoint(context, self.node1Pos.x, self.node1Pos.y);
CGContextAddLineToPoint(context, self.node2Pos.x, self.node2Pos.y);
CGContextAddLineToPoint(context, self.node3Pos.x, self.node3Pos.y);
CGContextAddLineToPoint(context, self.node4Pos.x, self.node4Pos.y);
CGContextAddLineToPoint(context, self.node5Pos.x, self.node5Pos.y);
//4. optionally close path
CGContextClosePath(context);
CGColorRef color;
//5. draw path
color = [UIColor colorWithRed:1 green:0 blue:228/255.0f alpha:0.5].CGColor;
//CGContextSetFillColor(context, color);
CGContextSetFillColorWithColor(context, color);
CGContextDrawPath(context, kCGPathFill);
//
//hole
CGRect holeRectValue = CGRectMake(300, 40, 80, 100);
CGRect holeRectIntersection = CGRectIntersection( holeRectValue, rect );
[[UIColor clearColor] setFill];
UIRectFill(holeRectIntersection);
}
So how do I make the "hole" with my actual context path?
P.S. I was doing some masking, but it leaves a white line around the hole, so I need to avoid this.
[[UIColor clearColor] setFill];
UIRectFill(holeRectIntersection);
This doesn’t do anything—drawing with a clear color is effectively a no-op. What you most likely want to do is add the rectangle you’re trying to cut out as part of the path you’re creating, i.e. inserting a call to CGContextAddRect before your CGContextClosePath. See Filling a Path in the Quartz 2D Programming Guide.
I believe what you're looking for is the CALayer.mask property. To create a "hole", you would generate a CALayer object with an alpha channel in the shape of the hole you want to make, and then apply it to the view you want to punch the hole in.
In semi-pseudocode:
CALayer *holeMask;
UIView *myView;
//
// Build the holeMask object via whatever means,
// and set myView to the target view that you want
// to punch the hole in...
//
myView.layer.mask = holeMask

What is wrong with my CGContextclip.

I am beginner with Core Garphics and I am try to get around with my understanding of CGContextclip. I am doing a simple program with custom UIButton drawrect as below
Rect dimension passed to drawRect method is CustomButton *button = [[CustomButton alloc]initWithFrame:CGRectMake(0, 0, 100, 50)];
#implementation CustomButton
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self setBackgroundColor:[UIColor clearColor]];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef colorRef = [[UIColor blueColor] CGColor];
CGContextSetStrokeColorWithColor(context, colorRef);
CGContextSetFillColorWithColor(context, [[UIColor blueColor] CGColor]);
CGContextSetLineWidth(context, 3.0f);
CGContextAddRect(context, rect);
CGContextStrokeRect(context, rect);
//CGContextFillRect(context, rect);
CGContextSaveGState(context);
CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor]);
CGContextSetFillColorWithColor(context, [[UIColor redColor]CGColor]);
CGMutablePathRef rectPath = CGPathCreateMutable();
CGPathMoveToPoint(rectPath, NULL, CGRectGetMinX(rect), CGRectGetMidY(rect));
CGPathAddLineToPoint(rectPath, NULL, CGRectGetMaxX(rect), CGRectGetMidY(rect));
CGPathAddLineToPoint(rectPath, NULL, CGRectGetMaxX(rect), CGRectGetMaxY(rect));
CGPathAddLineToPoint(rectPath, NULL, CGRectGetMinX(rect), CGRectGetMaxY(rect));
CGPathCloseSubpath(rectPath);
CGContextAddPath(context, rectPath);
CGContextClip(context);
CGContextStrokePath(context);
//CGContextFillPath(context);
CGContextRestoreGState(context);
CGColorRelease(colorRef);
}
#end
I am expecting a full rect with dimension (0,0,100,50) colored in blue and a half rect inside with red coloured. I am getting the blue colored rect but red colored rect is not visible in my context.
I have gone through some solution here and also at some blogs but to my faulty eyes the code looks simple and fine. What is wrong with my clipping.
Thank you for your time and response.
I think there's no path in the context anymore after you clipped it.Just fill another rectangle:
//CGContextFillPath(context);
CGContextFillRect(context, rect);

Make a line drawn with CoreGraphics dynamic

I am drawing a line using the following method:
- (void)drawLineWithColor:(UIColor *)color andRect: (CGRect)rect{
if (uploadButtonHidden == 2) {
uploadPhotoButton.hidden = NO;
}
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 15, 110);
// set the stroke color and width
CGContextSetStrokeColorWithColor(context, [color CGColor]);
CGContextSetLineWidth(context, 6.0);
// move to your first point
CGContextMoveToPoint(context, 455, coords.y - 140);
// add a line to your second point
CGContextAddLineToPoint(context, coordsFinal.x, coordsFinal.y);
// tell the context to draw the stroked line
CGContextStrokePath(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Draw image on view
[image drawAtPoint:CGPointZero];
}
As you can see coords.y sets the starting point of my line. Is there any way when changing the coords.y point to update my line? If for example I have a method which adds 50 to coords.y every 0.5 seconds, how can I update the line without redrawing it (to prevent a memory crash)??
EDIT:
this method is called like this:
UIGraphicsBeginImageContext(self.view.frame.size);
[self drawLineWithColor:[UIColor blackColor] andRect:CGRectMake(20, 30, 1476, 1965)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
imageViewForArrows = [[UIImageView alloc] initWithImage:image];
[imageArrowsArray addObject:imageViewForArrows];
Don't draw into an image, draw straight into the context instead. I'm assuming this method is called from within your drawRect method, in which case there will already be a value CGContextRef. You just have to get it and draw into it. Be sure to use CGContextSaveGState and CGContextRestoreGState when you apply a transform or clipping.
- (void)drawLineWithColor:(UIColor *)color andRect: (CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, 15, 110);
// set the stroke color and width
CGContextSetStrokeColorWithColor(context, [color CGColor]);
CGContextSetLineWidth(context, 6.0);
// move to your first point
CGContextMoveToPoint(context, 455, coords.y - 140);
// add a line to your second point
CGContextAddLineToPoint(context, coordsFinal.x, coordsFinal.y);
// tell the context to draw the stroked line
CGContextStrokePath(context);
CGContextRestoreGState(context);
}

Resources