I have a UIScrollView that pretty much functions like a Facebook news feed. I thought my elements were slowing the scroll fps down. By process of elimination, I found out that the shadows slow down my app.
The UIView boxes inside the scroll view have such configuration:
self.layer.shadowColor = UIColor.blackColor().CGColor
self.layer.shadowOffset = CGSizeMake(0, 2)
self.layer.shadowRadius = 2
self.layer.shadowOpacity = 0.15
Like a news feed, my scroll view has many boxes, therefore having UIViews with their own shadows. How do I go with this without slowing down my app?
There's a bunch of stuff on speeding up UIScrollViews:
CALayer - Shadow causes a performance hit?
https://markpospesel.wordpress.com/2012/04/03/on-the-importance-of-setting-shadowpath/
If you use custom CALayer instances -- especially with shadows -- stuff that requires processing power, you should use
scrollView.layer.shouldRasterize = YES;
scrollView.layer.rasterizationScale = [UIScreen mainScreen].scale;
Also a shadowPath could speed up your scrollview as well something like this:
[scrollView.layer setShadowPath:[[UIBezierPath bezierPathWithRect:myView.bounds] CGPath]];
Setting a shadowPath allows iOS to not recalculate how it should draw the shadow every time it draws the view, thus improving performance.
In swift, something like this:
let shadowPath = UIBezierPath(rect: view.bounds);
view.layer.masksToBounds = false;
view.layer.shadowColor = UIColor.blackColor().CGColor;
view.layer.shadowOffset = CGSizeMake(0, 0.5);
view.layer.shadowOpacity = 0.2;
view.layer.shadowPath = shadowPath.CGPath;
Setting the shadowPath will improve performance and look the same so long as your views are opaque. You could just set it to the bounds of the view or layer like so:
CGPathRef shadowPath = CGPathCreateWithRect(self.bounds, NULL);
self.layer.shadowPath = shadowPath;
CGPathRelease(shadowPath);
In Swift:
layer.shadowPath = CGPathCreateWithRect(bounds, nil)
The memory management is handled for you (discussed here).
Related
Question I'm struggling on. I've searched SO for a fair good time by now, but couldn't find an answer.
I have a UIView which contains alpha value of 0.5, means - it transparent.
If I'm applying the usual code for UIView shadowing -
class func applyShadow(view : UIView)
{
view.layer.shadowColor = UIColor.blackColor().colorWithAlphaComponent(0.15).CGColor
view.layer.shadowOpacity = 1
view.layer.shadowOffset = CGSizeMake(0, 0.5)
view.layer.shadowRadius = 1.3
view.layer.masksToBounds = false
view.layer.shouldRasterize = true
view.layer.rasterizationScale = UIScreen.mainScreen().scale
}
The "fill" of the my UIView get shadowed as well.
How can I draw the shadow only on the "border" path of my UIView, excluding the UIView fill?
For 2022 this is now possible.
Essentially you do this:
shadowHole.fillRule = .evenOdd
shadowHole.path = p.cgPath
full details ...
https://stackoverflow.com/a/59092828/294884
cell.layer.shadowOpacity = 1.0;
cell.layer.shadowRadius = 5;
cell.layer.shadowColor = [UIColor blackColor].CGColor;
cell.layer.shadowOffset = CGSizeMake(0.0, -2.0);
cell.layer.shadowPath = [UIBezierPath bezierPathWithRect:cell.layer.bounds].CGPath;
I have been trying to get a shadow to appear around my cells, however I haven't had any luck. I've even tried ordering the zPosition of my cells to see if it works, and nothing. What am I missing?
I want to be able to draw my shadows so the cell with index.row = x appears to be above the cell with index.row = x-1
You might get this to work to some extent by setting the layer's masksToBounds to NO. But don't. Don't do any of what you're doing. What you're trying to do is a really bad idea, because you are forcing the render tree to constantly rerender the shadow while the table scrolls, which will cause a low frame rate and stuttering of the scroll.
I'm trying to see if there's a way to create a UIView with a shadow behavior compliant with material design. My understanding is that the shadow gets more intense as the object is further removed from the background surface.
I can manually add a shadow like this, however this shadow does not calculate how intense the shadow should be (based on Z order).
button.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:button.layer.bounds cornerRadius:11].CGPath;
button.layer.shadowOpacity = 1.0;
button.layer.shadowOffset = CGSizeMake(1,1);
button.layer.shadowColor = [UIColor blackColor].CGColor;
How do I create a UIView which will behave like Google's material design? I found this example, called Material Kit, however it simply hides shadow on touch
One way in which Google does this is by emphasizing the use of a
‘z-axis’ to create a sense of depth in which elements can occupy
different levels. In order to show that an element is occupying a
higher level on the z-axis, shadows are used around its border, being
cast on to the level below. What this creates is the illusion of an
interactive layer/element that exists above a different, lower
interactive layer/element.
You could subclass UIButton, and first set the shadow properties to your liking:
button.layer.shadowOffset = CGSizeMake(0, 1);
button.layer.shadowRadius = 5;
button.layer.shadowOpacity = 0;
Then change the shadowOpacity property when the highlighted state changes:
- (void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
self.layer.shadowOpacity = (highlighted ? 0.85 : 0);
}
In order to apply both a shadow and a rounded corner, you need to use 2 nested views. This is because the two effects require competing masksToBounds properties.
Nest 2 UIViews. Apply the shadow to the outer view, setting masksToBounds = false. Then apply the rounded corner to the inner view, which requires masksToBounds = true.
Note that, according to Apple docs, masksToBounds is equivalent to clipsToBounds.
I want to make a circular progress bar such as that, how to animate its resizing and properties (shape, colour, width, etc.) as well.
I am trying to make it around a circular transparent view.
Where to start?
Does anyone has a sample code?
There's no substitute for learning, however a little help never goes a miss, so here are snippets of code that will accomplish the task for you.
The concept is to use a CAShapeLayer and a UIBezierPath and progress is simply setting the strokeEnd propertie of the UIBezierPath. You'll need to declare a CAShapeLayer and set its properties. We'll call this our progressLayer. (i'm not going to provide complete code, simply direction and samples for you to put together.)
// setup progress layer
// N.B borderWidth is a float representing a value used as a margin.
// pathwidth is the width of the progress path
// obviously progressBounds is a CGRect specifying the Layer's Bounds
[self.progressLayer setFrame:progressBounds];
UIBezierPath *progressPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)) radius:(bounds.size.height - self.borderWidth - self.pathWidth ) / 2 startAngle: (5* -M_PI / 12) endAngle: (2.0 * M_PI - 7 * M_PI /12) clockwise:YES];
self.progressLayer.strokeColor = [UIColor whiteColor].CGColor;
self.progressLayer.lineWidth = 6.0f ;
self.progressLayer.path = progressPath.CGPath;
self.progressLayer.anchorPoint = CGPointMake(0.5f, 0.5f);
self.progressLayer.fillColor = [UIColor clearColor].CGColor;
self.progressLayer.position = CGPointMake(self.layer.frame.size.width / 2 - self.borderWidth / 2, self.layer.frame.size.height / 2 - self.borderWidth/2);
[self.progressLayer setStrokeEnd:0.0f];
You will obviously need to add progressLayer to your view hierarchie
Then you will need a simple animation to progress the bar;
// updateInterval is a float specifying the duration of the animation.
// progress is a float storing the, well, progress.
// newProgress is a float
[self.progressLayer setStrokeEnd:newProgress];
CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
strokeEndAnimation.duration = updateInterval;
[strokeEndAnimation setFillMode:kCAFillModeForwards];
strokeEndAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
strokeEndAnimation.removedOnCompletion = NO;
strokeEndAnimation.fromValue = [NSNumber numberWithFloat:self.progress];
strokeEndAnimation.toValue = [NSNumber numberWithFloat:newProgress];
self.progress = newProgress;
[self.progressLayer addAnimation:strokeEndAnimation forKey:#"progressStatus"];
in your image above, the un-progressed path is nothing more than a second fully stroked layer behind the progressLayer
oh, and one final point, you'll find that the Circle progresses Clockwise. If you take the time to learn what's happening here, you'll figure out how to set progress Anti Clockwise.
Good Luck ...
You can also check out my lib MBCircularProgressBar, it also shows how to animate with and how to make it customizable from the Interface Builder. You can make it transparent by using transparent colors.
http://raw.github.com/matibot/MBCircularProgressBar/master/Readme/example.jpg
Here's a great, short tutorial on how to make this: http://www.tommymaxhanks.com/circular-progress-view/
You will need to learn a bit of Core Graphics in order to accomplish this in the way you want (the author used a gradient in the implementation, you may want to change that.), but it's worth it if you want to do cool stuff like this on the iPhone!
I think even more helpful is the example code, which is hosted here: https://github.com/mweyamutsvene/THControls/tree/master/CircularProgressView
Does any one know how to achieve the shadow effect with no gradient? Like the screenshot show below
Another concern is the sequence of subviews, i.e the view in front may hide the effect of the view in behind. How to overcome this?
For the first problem you can change the shadowRadius of the shadow, for example:
//You must include QuartzCore framework (#import <QuartzCore/QuartzCore.h>)
view.layer.cornerRadius = 5;
view.layer.shadowRadius = 0; //The shadow should be rendered as a solid shape
view.layer.shadowOffset = CGSizeMake(0, 2);
view.layer.shadowOpacity = 0.5;
view.layer.shadowColor = [UIColor blackColor].CGColor;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:view.bounds];
view.layer.shadowPath = path.CGPath; //This is very important!
Remember to always set the shadowPath! If you don't the performance of rendering the shadow will decrease a lot.
For the second problem, sorry but I don't think there's a way to let the shadow of an object appear over another view that is over the original one.