So I am doing some custom animations on my navigationcontroller and the way it pushes and pops the viewControllers.
Everything runs smooth. As soon as I add the following code (In a subclass of UINavigationController), I face a huge performance hit. After adding a shadow all animations become very laggy. Is this expected or am I doing something wrong in the code?
// This code gets called once during NavigationController initialization.
[self.view setClipsToBounds:NO];
[self.view.layer setCornerRadius:5];
[self.view.layer setShadowOffset:CGSizeMake(0, 20)];
[self.view.layer setShadowColor:[[UIColor yellowColor] CGColor]];
[self.view.layer setShadowRadius:20.0];
[self.view.layer setShadowOpacity:1];
EDIT:
Changed my shadow radius to 1 and it's still slow
self.view.layer.shouldRasterize = YES;
self.view.layer.rasterizationScale = UIScreen.mainScreen.scale;
I was recently having some issues with slow CALayer shadows, and that simple line of code fixed up everything for me!
You should expect a slowdown from adding a shadow. A shadowRadius of 20 is very high and will be especially slow.
The other key to improve shadow rendering speed: set the shadowPath property. It can help dramatically.
Using shadowPath instead of shadowOffset.
theView.layer.shadowPath = [UIBezierPath bezierPathWithRect:theView.bounds].CGPath;
Check this post: iphone - Animation's performance is very poor when view's shadow is on
Yes, shadow's are very expensive (especially a shadow that big -- play with the radius and you'll notice it makes a huge difference in the degree of slowdown you experience). One way to improve performance is to render it once to a CGImageContext and just display that image instead of having the layer re-render the shadow every time it redraws (but this doesn't work if the shadow needs to animate or something).
Swift 5.3. add this code.
myView -> UIView, collectionViewCell or tableViewCell can be.
myview.layer.shadowPath = UIBezierPath(rect: cell.bounds).cgPath
myview.layer.shouldRasterize = true
myview.layer.rasterizationScale = UIScreen.main.scale
I'm coding app with "Neumorphism" style, so much shadow and app so laggy. But use this code below, app very smooth.
viewHasShadow.layer.shouldRasterize = true
viewHasShadow.layer.rasterizationScale = UIScreen.main.scale
Related
I have some interesting notice with scrolling performance on UITableView with cornerRadius, borderWidth and borderColor enabled.
self.tableView.layer.cornerRadius = 10.f;
self.tableView.layer.borderWidth = 1.0f;
self.tableView.layer.borderColor = [UIColor whiteColor].CGColor;
If I remove borderWidth and borderColor than scrolling gets 60 FPS no problem. As long as I add those two properties to layer FPS drops.
Does anyone have a suggestion or explanation why this strange behavior happens?
Many Thanks!
CALayer having shouldRasterize property for this type of performance issue.
Just set
self.tableView.layer.shouldRasterize = YES;
This single line saved my lot of time and work.
I just found out that cornerRadius with borderWidth sluggish tableView performance by a lot. So instead of 10.f radius I changed to 5.f and performance has increased amazingly. Still not perfect but noticeably better...
I have a UITableView that has three UIImageView views per cell, with three cell displaying on the view at one time (for a total of nine UIImageView views). Think of it as a bookshelf. Sometimes I can have as many as 500 books.
I've added shadow to the UIImageView with code that is this:
UIImageView *itemImageView = [[UIImageView alloc] initWithFrame:CGRectMake(25, 7, 65, 75)];
itemImageView.contentMode = UIViewContentModeScaleAspectFit;
itemImageView.tag = 6;
itemImageView.layer.shadowColor = [UIColor blackColor].CGColor;
itemImageView.layer.shadowOffset = CGSizeMake(3, -1);
itemImageView.layer.shadowOpacity = 0.7;
itemImageView.layer.shadowRadius = 3.0;
itemImageView.clipsToBounds = NO;
[cell.contentView addSubview:itemImageView];
When I add the shadow code, as seen above, scrolling performance is just totally killed and becomes choppy. Each image has a different Rect so the shadow has to be created for each item as it scrolls. Anyone have any tips on how to add shadows to my images on a UITableView without having this issue?
You may see a performance improvement if you add
itemImageView.layer.shadowPath =
[UIBezierPath bezierPathWithRect:itemImageView.layer.bounds].CGPath;
But in general, layer operations like this will kill performance in a table view. I experienced exactly the same issue, and we just eliminated the shadow effect. It wasn't worth it.
You should review the WWDC videos on CoreAnimation best practices. You can request that the rasterized copy of the shadow be cached in memory. Cocoa is definitely fast enough to render these shadows on the fly without falling back to a pre-rendered image.
Example:
itemImageView.layer.shouldRasterize = YES;
Also I up-voted the answer regarding UIBezierPath. This is also mentioned in the best practices, but setting the shadow path of a CALayer is a huge performance boost. You can also create some interesting special effects by manipulating the shadow path.
Shadows are expensive and will kill your performance.
A better approach is to render the shadowed image in the background, cache/save it and display it on the view when its ready.
Edit: You way wish to look at Core Graphics / CGImage routines. Specifically CGContextSetShadowWithColor will draw you a shadow.
http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Reference/CGContext/Reference/reference.html
I have the same problem with adding shadow to labels inside a tableviewcell. I tried to layout the elements inside cellForRowAtIndexPath when the cell will be created:
if(cell == nil){
//layout cell
These optimized the scrolling a little bit, but its quite choppy.
For optimizing your pictures quality you should add also the rasterizationScale if you activated "shouldRasterize":
aLabel.layer.shouldRasterize = YES;
aLabel.layer.rasterizationScale = [[UIScreen mainScreen] scale];
Maybe somebody has some ideas how to optimize the code to get the normal iOS scrolling. thx
Do you activate reusing? If not, the cells will redone each time the view changes (e.g., during scrolling). That would eat a lot of performance for sure.
Check out my same question here: App running slowly because of UIImageViews
I would use this code:
imageView.layer.masksToBounds = NO;
UIBezierPath *path = [UIBezierPath imageView.bounds];
imageView.layer.shadowPath = path.CGPath;
I'm sure I'm missing something basic here. I'm trying out the CALayers 'hello world' code from:
http://www.raywenderlich.com/2502/introduction-to-calayers-tutorial
Doing the very first example. New single view project in xcode 4.2. No change to the nib/storyboard. Import QuartzCore. Add the following code to ViewDidLoad in the ViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.layer.backgroundColor = [UIColor blueColor].CGColor;
self.view.layer.cornerRadius = 30.0;
self.view.layer.frame = CGRectMake(20, 20, 20, 20);
}
I run this (ipad 2 or ipad simulator) and get a full screen blue rectangle with rounded corners. What I hoped to get was a 20x20 blue rectangle offset by 20/20.
I'm clearly getting control over the views layer (as shown by the color and rounded corners). However, adjusting the frame seems to have no impact. I've NSLog'ed the frame before/after setting it, and it has changed. Is the frame of the root layer locked to the uiview frame?
I don't have a strong reason to change the views layers frame, I'm just trying to reason through what is going on. Hopefully this is an easy question...
Thanks!
Paul
Actually, the previous answer (you can't set uiview.layer.frame as it always fills the uiview) is close, but not quite complete. After reading the answer, I registered for the original site and to comment that the tutorial had issues. In doing so, I found that there were already comments that I hadn't seen in my first pass that addressed this. Using those, I started doing some testing.
The bottom line, if you move the self.view.layer.frame setting code from viewDidLoad to viewWillAppear, it works fine. That means that you can change the frame of the root layer of a view. However, if you do it in viewDidLoad it will be undone later.
However, the previous answer is still pretty close. I NSLog'ed the frame of the root layer and the frame of the view. Changing the root layer frame changes the view frame. So, the answer that the view.layer.frame always fills the view.frame is correct. However, setting the layer frame resets the view frame to match. (I'm guessing that uiview.frame property simply returns uiview.layer.frame...)
So, at some point in time between 2010 and today, something in the environment changed. Specifically, after viewDidLoad and before viewWillAppear the uiview/layer frame appears to be reset to the nib specified value. This overrides any changes in viewDidLoad. Changes made in viewWillAppear appear to stick.
Robin's answer got me on the right track, but I wanted to spell out the full answer.
The tutorial is wrong. Setting the frame of the view's main layer has no effect. The main layer is 'special' and will always fill the view's bounds. What you need to do is create a sublayer of the main layer like this:
- (void)viewDidLoad
{
[super viewDidLoad];
CALayer *newLayer = [[CALayer alloc] init];
newLayer.backgroundColor = [UIColor orangeColor].CGColor;
newLayer.cornerRadius = 20.0;
newLayer.frame = CGRectMake(100.0f, 100.0f, 200.0f, 200.0f);
[self.view.layer addSublayer:newLayer];
[newLayer release]; // Assuming you're not using ARC
}
Also, in your code a layer with width 20pt and height 20pt is too small to have rounded corners of 30pt anyway.
Anyone ever seen the problem of [UIColor initWithPatternImage] ignoring the alpha values of the PNG? If so, what's the best fix?
I am using a png as a background layer to my array of button objects and it contains several pre-set alpha values per pixel in the image that is to be used as a texture background. It loads fine as a pattern/texture-color, but it comes up with all key transparent area as opaque black.
It is important that I get the right alpha values so that the button images shows correctly. The button frames do not include the alpha shadows from the background as that is not the "clickable" portion of the button. Additionally, my button object's images and background images also makes use of transparency, so it really needs to have a clear background directly behind each button to let the proper true current color settings come through (lowest layer UIView will have its background color set to the current user's selected color). Setting just the single alpha value for the UIView layer containing this texture does not work for my needs either.
Any help would be appreciated. My current workaround would be to use fully-blown, repeatedly-programmed layout of several UIImageView using the png, instead of a single UIView with the pattern fill.
Here is a snippet of code, but it's pretty standard for turning a UIImage into a UIColor for use as a pattern/texture color:
UIView *selectorView = [[UIView alloc] initWithFrame:CGRectMake(0,0,320,320)];
UIColor *background = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"SelectorViewBackground.png"]];
selectorView.backgroundColor = background;
[mainView addSubview:selectorView]; // pattern background layer. Add UIButtons on top of this selectorView layer
[self addSubview:mainView]; // current user selected color set in mainView.
[selectorView release];
[background release];
I had the same problem with setting a background on a UIView with some transparancy,
this is how I solved it:
theView.backgroundColor = [UIColor clearColor];
theView.layer.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"the_image_with_transparancy.png"]].CGColor;
This is probably related to:
Tiled Background Image: Can I do that easily with UIImageView?
Basically, try setting:
[view setOpaque:NO];
[[view layer] setOpaque:NO];
No I've never had an issue with this. I use code like the above all the time for apps (though often I use it in conjunction with a layer instead of a view but that shouldn't make a difference with transparency being recognized) and always have had transparency work fine.
I'd look into your PNG file. I've noticed iOS sometimes being finicky with certain PNG options/types (like an 8 bit PNG with 8 bit transparency). Make sure your PNG is saved as 24 bit with 8 bit transparency (32 bit total).
Also, stupid question, but have you verified there isn't anything black in the view/layer hierarchy behind your PNG? Sometimes it's the stupid things like that
For those who might need the work-around code where the background patterns can be laid out as rows in a UIScrollView, here it is (adjusted to remove confidentiality concerns, should work if variables properly set prior to call).
Note that there should be ways to reuse just the one allocated instance of UIImageView multiple times to either save memory or load times but time-to-market is my No. 1 driver right now. Enjoy the journey :)
UIImageView *selectorView;
for (int i = 0; i < numRows; ++i) {
selectorView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"SelectorViewBackground.png"]];
selectorView.frame = CGRectMake(0, i * patternHeight, patternWidth, patternHeight);
[mainView addSubview:selectorView];
[selectorView release];
}
I was pretty excited when I found out just how easy it is to add shadows to my UIViews on the iPhone/iPad.
Just add the framework in Xcode, add the import to the top of the file:
#import <QuartzCore/QuartzCore.h>
Then later:
self.contentView.layer.shadowRadius = 3.0;
self.contentView.layer.shadowOffset = CGSizeMake(-2.0, -3.0);
self.contentView.layer.shadowOpacity = 0.5;
self.contentView.layer.shadowColor = [UIColor blackColor].CGColor;
While this does create a beautiful shadow in my app, it also lags it to death now when the view is shown... even when launched outside of the debugger. Is there something I'm forgetting or is this method just not practical for larger views?
For reference, I posted a screenshot here.
You should set the shadowPath property. It is how CoreGraphics is able to optimize shadows.
For example, if your view is an opaque rectangle:
self.contentView.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.contentView.bounds].CGPath;
Thought I should make an answer because I didn't want this gem to get buried in the comments.
In addition to cobbal's answer above, which helped a lot, occulus also mentioned the following optimization for non-rectangular shadows, such as text shadows.
self.contentView.layer.shouldRasterize = YES;
// Fix visual degradation when rasterizing on high-density screens:
self.contentView.layer.rasterizationScale = [[UIScreen mainScreen] scale];