Subclassing an UIView efficiently - iphone

Guys I'm having some troubles subclassing an UIView.
I'm creating an IconView.
Simply it's a container for some other subviews.
In my IconView i have this iVar:
UIImageView _background
UIImageView _icon
UILabel _iconLabel.
When I initialize the IconView I setup this 3 iVar with images, text and some quartz effect like roundCorner and Shadow and then I add them to the self view.
Everything is Ok but if I insert some of this IconView (i.e. 10) inside an empty scrollview the scroll effect is not smooth. I tried before inserting thousand of simple UIViews in a scrollview and the scroll animation works perfectly.
With just 10 of my IconView the scroll animation works really bad.
I could approach differently retaining UIImages instead of UIImageViews and draw it inside drawRect: method but in this case I'm gonna loose Autoresizing property and Quartz effect.
Any suggests?
Thank, Gabriele.

Unfortunately, a UIScrollView gets slow pretty fast. There are a lots of posts and articles on this topic out there, like this Question and this (defect) blogpost along with it's sample code. There are also three sessions about 'Performance optimization in iOS' in the 2010's WWDC videos which I highly recommend to watch. To summarize the conclusions: Use as few subviews as you can and take special care of avoiding transparencies.
Ok, so much for the general 'Performance in ScrollViews' talk, now to your case: Having the same problem, I used all the tips from the articles and videos above and while they improved the performance, it just wasn't enough. I had, like you, used rounded corners one some images and I found out that this absolutely kills performance. Just deactivating them helped more than everything else. It's probably the same with the shadow effects.
Now, most likely, you want to keep those rounded corners. I would suggest that you create a copy of your images (or take the original, if possible) and than manipulate them directly, using those awesome classes. This way, the effects will only be applied once. It works perfectly for me. For you shadows, you can probably just create some in Photoshop and use them in a new ImageView.
If that isn't enough, you should try to cache your IconViews, like TableViewCells are cached, if you don't already do this.

The problem will probably be the Quartz shadows. They can really slow the rendering down if used a lot.
Before you write them off, you might try setting your CALayer's shouldRasterize property to YES. This makes quartz render the shadow only once and store it in a buffer. See how it goes.

Related

In SwiftUI, how to draw shadows with high performance?

I use the .shadow(color:, radius:, x:, y:) to draw shadows in my application. This is the only way I know of drawing apps in SwiftUI. I use the .sheet(isPresented:, content:) method to pop up a view, which contains a lot of shadows, and when I debug view hierarchy, I saw these warnings:
But I don't know how to setting shadowPath, or pre-rendering the shadow into an image and putting it under the layer in SwiftUI, please help me.
This warning is not caused because your code is inherently bad, but as a way of telling you that there are much more performant ways of rendering shadows.
As your UI elements are currently written, SwiftUI is drawing the shadows around your view objects dynamically (at Runtime) based on wherever their positions and bounds are at the time, and that rendering will follow the view throughout it's lifecycle.
It's a math intensive process and involves many draw-calls to the GPU in the best of cases, and CPU bottlenecking as well in the worst of cases.
There are several different ways of rendering shadows in Swift. Most of them utilize frameworks OUTSIDE of SwiftUI (UIKit and CoreGraphics, usually, though Metal, Core Animation, and Core Image have been important in various applications.)
This warning is probably not a big deal if you're not seeing performance problems in the UI layer on target hardware, but if you're very motivated to solve the problem, there are some options:
Option 1
The absolute easiest thing to do if you just want to make the error go away would be to force a GPU call rasterization for the view + shadow by adding
.drawingGroup()
somewhere after the .shadow view. Be advised, this will likely look like crap compared to dynamic shadows. If you're familiar with UIKit, this is similar to the layer.shouldRasterize property on UIView.
Option 2
Speaking of UIKit, an alternative would be to head over there and use either a UIViewRepresentable of your SwiftUI drawing logic, or a completely separate UIView. Either way:
myView = UIView()
myView.layer.shadowPath = UIBezierPath(rect: myView.bounds.cgPath)
should get you started... the other shadow properties and stuff will help.
Option 3
You could render the shadow as an image, either programatically (hard) or in an imaging editing application (annoying) and load is as an image at a lower Z index than your view, and scale them to give the illusion of depth.
This is the kind of hacky work around that game developers used to do when they had crappy hardware but still wanted things to look good.
In the end... for MOST SwiftUI views this warning likely can be ignored. If you load the code in Instruments, you'll likely see that the dynamic rendering of drop shadows under a View is probably not impacting your view rendering performance significantly. This warning is only usually visible inside a UI Debug session.
Hope this helps set you on the path to a solution.
I ran into this this week. And I think I figured out a way that is as performant as the shadowPath option in UIKit. I think that when you use Shape().shadow(...) it draws an efficient shadow based on the path. So IF you know the shape of the thing you're giving a shadow to, you can do it like this:
content
// Use background so the shadow is the same size as the content
.background(
// I'm assuming rectangle but it can be anything like with rounded corners too
Rectangle()
// Add efficient shadow
.shadow()
// Add an inset so you don't see the ugly inner edge of the shadow, it will be under your content
.padding(1)
)
This was much much faster than what I had before!

App has slugging behaviour

My app is behaving sluggishly. If i pop up a UIActionSheet, for example, instead of rolling in smoothly, it stutters in over about 5 frames. I know ideally you should have as little amount of views on screen as possible, but that's what I've got anyway.
Any suggestions for speeding it up?
EDIT:
On my view i have:
Custom navigation bar in place of the regular one. It's a UIImageView, using an image file. It has a quartzcore shadow. It contains 3 buttons. 2 of these buttons have 2 UIImages each, for normal and highlighted, generated from code when the view is shown. The other button uses an image file for normal and for highlighted.
An image file for a background lies under that. On top of the background is a UITableView. By default, it doesn't have any cells (the user adds them). We'll ignore the cell, since it's slugging regardless of their being there or not.
The header of the tableview contains some labels, and an editable uitextview. The size of the header changes as more lines are added to the textview. It also has a background image, which is transparent to allow you to see the view's background image behind it. It's loaded from a file, and a texture image on top of that is also loaded from a file.
The footer is simply a background image loaded from a file with the same texture on top.
Andrew, I'm afraid you haven't been quite specific enough to isolate the exact problem. However there are a couple of things I have picked out. Firstly, check your table view is set to be opaque. Also try to design your app so your table cells can be opaque. I'm assuming your design will allow this. You need to really know how to optimise view rendering performance if you want your table and it's cells to appear translucent over other content and it may be you would need to develop your own custom specialised alternative to UITableView if that is something you really need to know (can be done but quite advanced stuff).
Also you mention using Quartz shadow. You should be able to use UIKit for drawing shadows around images, unless you have some specialist requirement. Are you sure you need to use Quartz for what you want to do? Apologies if you already know this, but if you are fairly new to iOS development and have been looking up how to do shadows, you may have found the Quartz API's for doing that and assumed that is the solution, when (depending on what you need) you will probably be better off staying with UIKit. As a general rule of thumb, only use Quartz if you are sure you can't do what you want to do with just the UIKit API's.
Another thing to check. If you are using Quartz, then you are probably getting getting the graphics context for the UIImage view and drawing on the views context in drawRect: depending on how your view hierarchy is configured, and if you have your navigation bar view set to be transparent over the top of the UITableView, then your custom drawRect implementation may be getting called unnecessarily with every animation frame and this would be a big drain on performance.
Given the level of information you have given I'm having to guess a bit and can't give a precise answer. However for a definitive understanding of how to optimise UIView performance I recommend checking out this video (though you will need an Apple Developer account to be able to access it):
https://developer.apple.com/videos/wwdc/2011/
Session 121 – Understanding UIKit Rendering
Hope this helps. Paul.

Definitive way to do rounded corners inside UITableViewCells?

So I've been all over the web, trying to figure out a definitive way to do this, and I can't seem to get it. Each of my table view cells has 3 dynamically-loaded images that I need to add rounded corners to. Right now I'm using Quartz and the layer.cornerRadius property of the ImageViews to round the corners, but of course it kills the scrolling performance of the table view.
Browsing online, I can't seem to find an answer to the performance problems. Most threads either end in suggesting the use of pre-rendered static images (not an option for me since the images are loaded dynamically, from the web) or some drawing trick that Loren Brichter came up with for Tweetie- however, the example project he posted on his blog is now a dead link, and I can't find it anywhere.
So basically, my question is how exactly should I go about accomplishing the rounded corners on elements inside a table view cell, without sacrificing performance? Thanks for any help!
Looking at the other answers it looks like you are using a lot of alpha transparency that is going to cost you a lot performance wise. You may consider revisiting that to get additional frames. One idea you may try doing is rendering the rounded image in a graphics context and using that as your cell image instead of the QuartzCore rounding.
Load the image in the imageview
Round the corners
Render the image into a graphics context
Set the imageview with the new image
Turn rounding off
Do all of this in the cellForRowAtIndexPath: method. There may be more efficiencies to be had (like caching the rendered image), but this is a way to at least test out the performance of a pre-rendered rounded image.
What about using a mask concept, and overlaying a mask UIImageView with transparent background over your dynamic images. Havent tried that, my self but it's an option.

UITableView smooth scrolling performance

What makes scrolling so choppy on the UITableView when images are loaded? Is it because they need to be added as subviews? Or is it because images need to be cached? Both?
Because they're added as subviews, and it's a lot of work to draw subviews as they have to be composited. Even more so when Core Graphics or Core Animation is drawing multiple cells with these subviews.
Loren Brichter (atebits) explains it better:
Much like on Mac OS X, there are two drawing systems on the iPhone. One is CoreGraphics, the other is LayerKit CoreAnimation. CoreGraphics does drawing on the CPU, CoreAnimation does drawing on whatever it thinks is fastest - most likely the GPU.
The GPU on the iPhone hates blending, that’s why Apple recommends that you keep as many of your views opaque as possible. Sometimes you have no choice - if you have a label over an image you are forced to make the label transparent otherwise you get a big ugly block around your text.
What’s a developer to do? Pre-blend of course… with CoreGraphics into your own view. If you blend your stuff together into a single static view on demand (e.g. when a table view moves a cell onscreen), it’s a little bit more expensive for the first frame, but every frame after that CoreAnimation is just dealing with one big, opaque texture… which it loves. It’s more than just the blending too. If you think about what is happening in terms of overdraw, having one big view per table cell is a big win because CoreAnimation will only touch a single given pixel on the screen once rather than multiple times (potentially, depending on how much overlap your old view hierarchy had).
Honestly, I'm not sure what to answer.
What I know, is that when I have a really "heavy" UITableView, and need it to scroll fast, I use the method from the creator of Tweetie:
http://blog.atebits.com/2008/12/fast-scrolling-in-tweetie-with-uitableview/
Never found a better way than this one.

How do I use CALayer with the iPhone?

Currently, I have a UIView subclass that "stamps" a single 2px by 2px CGLayerRef across the screen, up to 160 x 240 times.
I currently animate this by moving the UIView "up" the screen 2 pixels (actually, a UIImageView) and then drawing the next "row".
Would using multiple CALayer layers speed up performance of rendering this animation?
Are there tutorials, sample applications or code snippets for use of CALayer with the iPhone SDK?
The reason I ask is that most of the code snippets I find that demonstrate simple examples of CALayer employ method calls that do not work with the iPhone SDK. I appreciate any advice or pointers.
Okay, well, if you want something that has some good examples of CA good that draws things like that and works on the phone, I recommend the GeekGameBoard code that Jens Aflke published (it is an improved version of some Apple demo code).
Based on what you are describing I think you are doing somthing way more complicated than it needs be. My impression is you want basically a static view that you are animating by shifting its position so that it is partially off screen. If you just need to set some static content in your drawRect going through layers is not going to be faster than just calling CGFillRect() with your color. After that you could just use implicit animations and the animator proxy on UIView to move the view. I suspect you could even get rid of the custom drawRect: implementation with a patterned UIColor, but I honestly have not benchmarked the difference between the two.
What CALayer methods are you seeing that don't work on iPhone? Aside from animation features tied to CoreImage I have not noticed much that is missing. The big thing you are likely to notice is that all views are layer backed (so you do not need to do anything special to use layers, you can just grab a UIView's layer through the layer accessors methos), and the coordinate system has a top left origin.
In any event, generally having more things is slower than having fewer things. If you are just repeating the same pattern over and over again you are likely to find the best performance is implementing a custom UIView/CALayer/UIColor that knows how to draw what you want, rather than placing visually identical layers or views next to each other.
Having said that, generally layers are lighter weight than views, so if you have a lot of separate elements that you need to keep logically separated you will find that moving to layers can be a win over using views.
You might want to look at -[UIColor initWithPatternImage:] depending on exactly what you are trying to do. If you are using this two pixel pattern as a background color you could just make a UIColor that draws it and set the background.
What CALayer methods are you seeing that don't work on iPhone?
As one example, I tried implementing the grid demo here, without much luck. It looks like CAConstraintLayoutManager and CAConstraint are not available in QuartzCore.h.
In another attempt, I tried a very simple, small 20x20 CALayer object as a sublayer of my UIView's layer property, but that didn't show up.
Right now, I have a custom UIView of which I override the drawRect method. In drawRect I grab a context and render two types of CGLayerRefs:
At "off" cells I draw the background color across the entire 320x480 canvas.
At "on" cells, I either draw a single CGLayerRef across a grid of 320x480 pixels (initialization) or across a 320x2 row (animation).
During animation, I make a UIImageView clip view from 320x478 pixels, and draw a single row. This "pushes" my bitmap up the screen two pixels at a time.
Basically, I'd like to test whether or not using CALayer will accomplish two things:
Make my rendering faster, if CALayer has less overhead than what I'm doing now
Make my animation smoother, by letting me transition a layer up the screen smoothly
Unfortunately, I can't seem to get a basic CALayer working at the moment, and haven't found a good chunk of sample code to look at and play with.