View shrink ( not fade out ) using core animation - ios

I have subclassed a UIButton to make the button shape appear as a doughnut ( circle with a transparent hole in the middle )
The drawRect is :
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
if(context==nil)
context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:0 green:1.0 blue:0 alpha:0.5].CGColor);
CGMutablePathRef path = CGPathCreateMutable();
CGContextSetLineWidth(context, self.frame.size.width/2 - innerRadius);
CGPathAddArc(path, NULL, self.frame.size.width/2.0, self.frame.size.height/2.0, (innerRadius+((self.frame.size.width/2 - innerRadius)/2.0)), 0 * 3.142/180, 2.1 *3.14, 0);
CGContextAddPath(context, path);
CGContextStrokePath(context);
CGPathRelease(path);
}
When the button is clicked, I want the inner hole to shrink gradually and finally disappear leaving the circle whole .
innerRadius controls the size of the inner hole so I used the following function to try to achieve what I want :
- (void) animateButton
{
innerRadius = 0;
[UIView transitionWithView:self duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
[self.layer displayIfNeeded];
} completion:nil];
}
This works but not quite like I wanted. The hole just fades out over a 0.5 second period. I want it to gradually shrink and then disappear instead. How can I achieve this using core animation ?

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

Round specific corners of and show shadow on specific sides only

I have the following piece of code to round only specific corners of a view:
- (void)roundOnlySpecifiedCornersInView:(UIView *)view corners:(UIRectCorner)corners
{
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:(corners) cornerRadii:CGSizeMake(4.0, 4.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.path = maskPath.CGPath;
view.layer.mask = maskLayer;
}
This works perfectly in isolation. Now I also want shadow in my view, but I specifically want to apply shadow in different cases:
on all sides
all sides except bottom
all sides except top
left/right sides only
All techniques I encountered work by creating an inset of the view. The problem with this, is that, say you want to only keep shadow on left/right sides, you offset bottom and top. Since the Rect is now less high, the shadow at the left and right does not cover the full height of the view. Also, the mask layer used for rounding corners causes the shadow to no longer appear.
Example code for this:
innerView.layer.shadowColor = [[UIColor colorWithWhite:0.0f alpha:0.1f] CGColor];
innerView.layer.shadowOpacity = 1.0f;
innerView.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
innerView.layer.shadowRadius = 6.0f;
CGRect shadowFrame = UIEdgeInsetsInsetRect(innerView.bounds, UIEdgeInsetsMake(9, 0, 9, 0));
CGPathRef shadowPath = [UIBezierPath bezierPathWithRect:shadowFrame].CGPath;
innerView.layer.shadowPath = shadowPath;
How can I round specific corners in a view and at the same time show shadow only at specified sides?
Answers in Swift are appreciated too!
Screenshot of what I want (this one is easy since all corners need to be rounded so I can use .layer.cornerRadius and it has shadow at all sides):
Now I just want to round only 2 of the corners (top left and top right, bottom left and bottom right) and add shadow to only some sides.
I'm not sure if it meet your demand. The code create an image with top and bottom shadow, and all rounding corner, you can modify the code to achieve what you need. You can use the image as the background of your cell(It's seems that it is UITableViewCell)
Let me know if it don't work for you.
The image:
// create a shadow image
CGSize size = CGSizeMake(ScreenWidth, ScreenWidth);
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *backgroundColor = [UIColor colorWithRed:246.0/255.0 green:246.0/255.0 blue:246.0/255.0 alpha:1.0];
UIColor *fillColor = [UIColor whiteColor];
CGRect rect = CGRectMake(10, 10, 100, 44);
// re-draw the background
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
// set top and bottom shadow
CGRect rectTop = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 5);
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0, -5), 5, [UIColor colorWithRed:0 green:0 blue:0 alpha:0.1].CGColor);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rectTop);
CGContextRestoreGState(context);
CGRect rectBottom = CGRectMake(rect.origin.x, rect.origin.y+rect.size.height-5, rect.size.width, 5);
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0, 5), 5, [UIColor colorWithRed:0 green:0 blue:0 alpha:0.1].CGColor);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rectBottom);
CGContextRestoreGState(context);
// re-draw the background
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, rect);
CGContextSaveGState(context);
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(4.0, 4.0)];
[maskPath addClip];
CGContextSetFillColorWithColor(context, fillColor.CGColor);
CGContextFillRect(context, rect);
CGContextRestoreGState(context);
You can modify the code to get a top left shadow:
// create a shadow image
CGSize size = CGSizeMake(ScreenWidth, ScreenWidth);
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *backgroundColor = [UIColor colorWithRed:246.0/255.0 green:246.0/255.0 blue:246.0/255.0 alpha:1.0];
UIColor *fillColor = [UIColor whiteColor];
CGRect rect = CGRectMake(10, 10, 100, 44);
// re-draw the background
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
// set top and left shadow
CGRect rectTop = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 5);
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0, -5), 5, [UIColor colorWithRed:0 green:0 blue:0 alpha:0.1].CGColor);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rectTop);
CGContextRestoreGState(context);
CGRect rectLeft = CGRectMake(rect.origin.x, rect.origin.y, 5, rect.size.height);
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(-5, 0), 5, [UIColor colorWithRed:0 green:0 blue:0 alpha:0.1].CGColor);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rectLeft);
CGContextRestoreGState(context);
// re-draw the background
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, rect);
CGContextSaveGState(context);
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(4.0, 4.0)];
[maskPath addClip];
CGContextSetFillColorWithColor(context, fillColor.CGColor);
CGContextFillRect(context, rect);
CGContextRestoreGState(context);
HTH
In many, many cases you would want to achieve this with drawing the shadow, of course. However, you may consider the following approach: using a stretchable image with the shadow just on the sides that you need, e.g. something similar to (this one has shadow on all sides though):
Just before you say: "Ewwwww! He's using an image for a simple shadow!" let me emphasise that this will work much better in terms of performance. For instance, if you have a lot of cells in a UICollectionView and each one is re-drawing its shadow it could significantly slow down your app during scrolling, whereas with an image-based shadow it's going to be essentially the same.
I would go even further and suggest that you could actually use a stretchable image for masking the view with rounded corners, too! Now, this may look like going a bit too far, but if you notice a drastic decrease in performance I would give it a shot. What you essentially need to do is prepare another stretchable image with a transparent "middle" part and rounded corners of the same colour as your background. Something like:
And again, before you downvote this bizarre way of doing something that can be easily achieved with two lines of code...
view.layer.cornerRadius = 20
view.layer.masksToBounds = true
...Let me point out that this will work ridiculously faster in cases when you have to display a bunch of those masked views simultaneously: for instance, masking UICollectionViewCells. Basically, if you set a mask on a UIView's layer, this mask is going to be re-calculated and applied in real time, which would cost you a lot in terms of performance during frequent redrawing of the UIView contents — when scrolling a UITableView or UICollectionView, for example.
Obviously this wouldn't work if your background is not of a solid colour, and has a gradient for instance. However, in many other cases this may help you achieve much smoother UI performance. Another advantage in your particular case is that you can easily control which corners to mask and where the shadow should drop.
Again, I'm not implying that this is the way to go in each and every case. What I'm saying is that there are a lot of cases when this could help increase the performance significantly: for example, if you are masking far too many views that are rendered on the screen at the same time, and the layout is expected to be redrawn frequently.
for specific corner set corner radius
refer from : how to set cornerRadius for only top-left and top-right corner of a UIView?
UIBezierPath *maskPath;
maskPath = [UIBezierPathbezierPathWithRoundedRect:_backgroundImageView.bounds
byRoundingCorners:(UIRectCornerBottomLeft|UIRectCornerBottomRight)
cornerRadii:CGSizeMake(3.0, 3.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
_imageView.layer.mask = maskLayer;
[maskLayer release];

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

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 scaling with CGAffineTransformMakeScale on drawRect content

float scaleFactor = 0.5;
self.bubble.transform = CGAffineTransformMakeScale(scaleFactor, scaleFactor);
[self.bubble setNeedsDisplay];
[UIView animateWithDuration:2.0 animations:^{
self.frame = _rectToMoveTo;
} completion:^(BOOL finished) {
[UIView animateWithDuration:2.0f animations:^{
self.bubble.transform = CGAffineTransformMakeScale(1.0, 1.0);
}];
}];
The above code performs the animation mostly correct. As you can see, the self.bubbles are scaled down to ½ then animated back to normal. The problem is that self.bubble has a drawRect method for drawing a circle. The problem is that this circle gets scaled down to ¼ from the start! When the second animation runs, the subviews of self.bubble get scaled to normal, but the circle scales up to only ½. I've tried using setNeedsDisplay to redraw the circle but it will only work after the animations have completed so it's no good. how do you fix this?
Edit: here is _bubble drawRect method.
CGContextRef c = UIGraphicsGetCurrentContext();
if (!_colour) _colour = [UIColor darkGrayColor];
[_colour setFill];
[[UIColor clearColor] setStroke];
CGContextAddEllipseInRect(c, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height));
CGContextFillPath(c);
[self addSubview:_title];
[self addSubview:_icon];
self.frame is in the superview's coordinate system, and is thus affected by your transform, which you don't want.
Inside drawRect:, you should only use self.bounds, not self.frame.
Also, you should not add subviews in drawRect:.

Resources