How can my custom UIView draw a clear background? - ios

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

Related

How to vertical center a `UISlider` thumb image?

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

Using a For Loop To Draw Lines Over a UISlider

I have a UISlider that I use to play a video, and I would like to put red "break indication" lines in my UISlider to visually show the user when the break is coming up, as visualized below. My slider has a set .duration property, and I have an array full of timestamps that contain the times that the video will need to pause for a break. I'm still getting the hang of iOS, so I don't know how to go about drawing the lines over UISlider's. I would like it to appear similar to this:
In my research I've read in the Apple Docs that UISlider luckily provides a method to the sliders coordinates based upon a float value. This is perfect because now I can determine where in the slider I can draw the yellow line based upon the timestamps in the array, right?
So, I created a for loop to call the method and am (attempting to) draw a line.
The drawing the line part is what I'm having issues with. I've been reading through Apple's docs and other questions on this site, but I cannot seem to figure out the drawing logic. It draws just fine, the issue is that it's coordinates are all wrong. It draws and overlaps the majority of its lines in one specific location, the top left of my view. This is what I'm trying to do:
Updated Code In correlation with #Bannings answer (Many thanks to you).
- (void)breakStarted:(NSNotification *)notification {
self.lines = [NSMutableArray new];
for (int i = 0; i < myArray.count; i++) {
long long nanoSeconds = [[myArray.adMarkerTimes objectAtIndex:i] floatValue];
float minutes = nanoSeconds / 60000000000;
[self sliderThumbCenter:self.scrubberSliderView forValue:minutes];
NSLog(#"Minute Readings: %f", minutes);
}
}
- (float)sliderThumbCenter:(UISlider *)slider forValue:(float)value {
CGRect trackRect = [slider trackRectForBounds:slider.bounds];
CGRect thumbRect = [slider thumbRectForBounds:slider.bounds trackRect:trackRect value:value];
CGFloat centerThumb = CGRectGetMidX(thumbRect);
NSLog(#"Center Thumb / Line Placements on slider are: %f", centerThumb);
[self.lines addObject:#(centerThumb)]; // Added the rect values to an array which we will loop through to draw the lines over the slider
[self setNeedsDisplay];
return centerThumb;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 2);
for (NSNumber *x in self.lines) {
CGContextMoveToPoint(context, x.floatValue, 0);
CGContextAddLineToPoint(context, x.floatValue, rect.size.height);
CGContextStrokePath(context);
}
}
I forgot to add: I'm manually converting CMTimeValue to seconds in the loop. That's what myArray.adMarkerTimes. Maybe I did that wrong...
You can override the drawRect of UISlider.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 2);
CGContextMoveToPoint(context, 20, 0);
CGContextAddLineToPoint(context, 20, rect.size.height);
CGContextStrokePath(context);
}
or insert UIView to UISlider.
- (UIView *)lineViewForRect:(CGRect)rect {
UIView *lineView = [[UIView alloc] initWithFrame:rect];
lineView.backgroundColor = [UIColor redColor];
return lineView;
}
- (void)viewDidLoad {
[super viewDidLoad];
[slider addSubview:[self lineViewForRect:CGRectMake(20, 0, 2, slider.bounds.size.height)]];
}
You also can set lineView.layer.zPosition = 1:
before:
after:
EDIT:
You can store lines in an array, and draw each in context.
It seems like this:
// YourSlider.m
- (void)addLineToX:(CGFloat)x {
[self.lines addObject:#(x)];
// This will cause drawRect to be called
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 2);
for (NSNumber *x in self.lines) {
CGContextMoveToPoint(context, x.floatValue, 0);
CGContextAddLineToPoint(context, x.floatValue, rect.size.height);
}
CGContextStrokePath(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

iOS - CGContextStrokePath/CGContextDrawPath remove black background color

I'm trying to draw a straight dashed line. I've gotten it so it draws the dashes, but this black background color remains. I've seem many answers to other questions, but none of them worked.
Apple's documentation seems to point to the fill color and using either CGContextFillPath or CGContextDrawPath, but neither of those work and the background of the dashed line is still black.
- (void)drawRect:(CGRect)rect {
CGContextRef contextRef = UIGraphicsGetCurrentContext();
GLFloat lines[] = {self.frame.size.width, self.frame.size.width*2};
CGFloat grey[4] = {0.6f, 0.6f, 0.6f, 1.0f};
CGContextSetStrokeColor(contextRef, grey);
CGContextSetFillColorWithColor(contextRef, [[UIColor clearColor] CGColor]);
CGContextSetLineDash(contextRef, 0, lines, 2);
CGContextSetLineWidth(contextRef, self.frame.size.width);
CGContextBeginPath(contextRef);
CGContextMoveToPoint(contextRef, 0, 0);
CGContextAddLineToPoint(contextRef, 0, self.frame.size.height);
CGContextDrawPath(contextRef, kCGPathFillStroke);
}
When manually adding this view to your view hierarchy, you just have to make sure you're setting the background color. For example, if you're initializing your view with initWithFrame, then set the background color in that method:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setBackgroundColor:[UIColor clearColor]];
}
return self;
}
Or, alternatively, when adding the view, set the background color there:
CustomView *customView = [[CustomView alloc] initWithFrame:frame];
customView.backgroundColor = [UIColor clearColor];
[self.view addSubview:customView];

iOS: Draw clear circle with UIBezierPath bezierPathWithOvalInRect:

In my UITableViewController's cells, I want to display a circle with a number in it. I am using UIBezierPath's bezierPathWithOvalInRect: to draw the circle.
Unfortunately, while I can set the fill color to be clearColor, the unused portion of the CGRect passed to bezierPathWithOvalInRect: is black.
How do I get rid of the black area created?
Partial screenshot for reference:
(I eventually hope to get that number inside the circle)
Code:
LTTTableViewCell:
- (void)awakeFromNib {
[super awakeFromNib];
// Create a square view using the height of the cell
CGRect positionFrame = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.height);
LTTDrawBallView *drawBallView = [[LTTDrawBallView alloc] initWithFrame:positionFrame];
[self.contentView addSubview:drawBallView];
}
LTTDrawBallView:
- (void)drawRect:(CGRect)rect
{
// Create a new rect with some padding
// + create a circle from this new rect:
CGRect box = CGRectInset(self.bounds, self.bounds.size.width * 0.1f, self.bounds.size.height * 0.1f);
UIBezierPath *ballBezierPath = [UIBezierPath bezierPathWithOvalInRect:box];
[[UIColor whiteColor] setStroke];
[[UIColor greenColor] setFill]; // Green here to show the black area
[ballBezierPath stroke];
[ballBezierPath fill];
[self setBackgroundColor:[UIColor clearColor]]; // Happens with and without this line
}
In the init method of your LTTDrawBallView, include the code:
self.opaque = NO;
self.backgroundColor = [UIColor clearColor];

Resources