Using drawRect, setNeedsDisplay and layoutSubViews - iphone

Is there any ground rule for using methods like drawRect, setNeedsDisplay and layoutSubViews?
I believe they adversely affect the app performance. Is there any preferred alternatives for these messages?

While this isn't an exact duplicate of this question, I believe my answer there still applies.
In summary, -drawRect: will redraw the content within a view, which is an expensive operation. Avoid redrawing if you can by layering content and moving or hiding those layers as needed.
However, these redraws typically will only have a significant performance impact if there are many of them done in rapid succession, like when trying to animate content. One-off redraws of static UI elements will not perceptively slow things down.

Related

Performance of drawRect

Hey guys just a quick question about the performance of drawRect: as I've noticed a lot of topics with people complaining about performance issues.
In my App, I need to draw ~150 squares overtop some underlaying views. Obviously this many calls isn't going to have a noticeable user difference, but I'm just wondering about the pros and cons going forward. Currently I'm debating two ways:
1) add a transparent UIView subclass on top with a custom drawRect: to facilitate the drawing
2) just add normal square subviews
As I said, using CADisplayLink there won't be any difference to the user, I'm just curious what would be more efficient? (2) is kind of a ridiculous way of doing it, adding instances with useless functionality just for appearance, but all these posts have just scared me into using drawRect. Is drawRect really that inefficient?
Thanks
Perhaps unintuitively, drawRect: isn't called for every frame. In fact, if you do nothing special in your view and your view itself doesn't change, it gets called once.
You can force it to be called by calling [myView setNeedsDisplay], but there usually is no reason to.
If you have a view with overlapping subviews, still for each of those views, including the superview, drawRect: is only called once. Then, the GPU does the compositing (blending).
A custom drawRect: is almost always slower (and harder) than just using UIKit the way it was intended, if UIKit already offers what you want to do.
Also don't fall into one of the biggest traps in software development, premature optimization. Code the most naive version, benchmark it, and if it's fast enough, it's good enough, and you can focus your energy elsewhere. Only start optimizing once you know that something is actually slow, and where and why it is.

UIScrollView - how to draw content on demand?

I want to create a scroll view with a massive contentSize that will display content inside it. The content will be mostly text (a few small images will be drawn for content-boundaries).
So, think like a tiled map application, but tiling labels instead of tiled images.
Performance is critical in this application, so I think I should avoid using UILabels or any UIViews at all inside the scroll view.
How can I draw these labels as the user scrolls around? Some options I've considered:
override drawRect: in the scroll view and draw everything within the window - this seems like it would be really slow. Sometimes drawRect is called with only a 1 pixel difference.
Same, but keep track of which ones I've already drawn
Draw them from the "outside" somehow, like from the scroll view delegate - I can't figure out how to use [#"mystring" drawInRect:] outside of drawRect: (context problems)
Is there something I haven't thought of? I know the scroll views were designed to be able to handle this kind of problem, but I'm not sure what the designed way is. Thanks!
The standard way to achieve this in an iPhone application is to create a normal UIScrollView that is the size you want it to be, and to populate it either directly with a CATiledLayer if you're feeling brave or with a custom UIView subclass that uses a CATiledLayer and implements - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context.
CATiledLayer provides the same experience as Safari — it's good for views that are much larger than the screen and which are expensive to render, but which you don't want to ruin the fluidity of the user experience. It'll request tiles as and when it needs them on a background thread, then fade them in (according to a fade of any length, so you can cause them to appear instantly if you desire) when they're ready. If your program really can always keep up with the scrolling and you've requested an instant appearance then there'll be no evidence that there's an asynchronous step involved.
An example people tend to point to is this one, but if you're new to implementing your own UIView subclasses then it might be worth seeing e.g. this tutorial. Then look into the + layerClass property on UIView and the various properties of CATiledLayer (which I think you'll also possibly need to subclass since + fadeDuration is a class method).

UITableViewController custom cell scrolling performance

So I have a UITableViewController. The cells in this tableview have the following UIControls:
2 UILabels, one of which has a shadow and a clearColor background color.
1 Custom Progress view resized to be larger and with a different color
3 UIButtons
Functionally, they do exactly what they are supposed to do. However, I've noticed when looking at it on device that scrolling performance quickly tanks and has dropped frames all over the place, even with other interactions like pushing one of the buttons.
So I was reading around today and found http://blog.atebits.com/2008/12/fast-scrolling-in-tweetie-with-uitableview/ this article by the Tweetie guy about how to achieve fast scrolling performance by subclassing UITableViewCell and doing the drawing yourself.
The example works extremely well, but when I tried to adapt it to work with my desired configuration I realized that he isn't using any predefined UI Controls, he's mapping out everything by hand.
While I can see how this would be an extremely efficient way to do things, it strikes me as problematic for things like the progress view and the buttons, and even one of my labels to a certain extent.
So my question is this: Do I need to completely write my controls from scratch if I want my scrolling performance to be good, or is there a way to use the standard UI Controls and get good scrolling performance?
If you're adding custom controls to your cell, you should still be subclassing UITableViewCell, adding your controls in the init function, laying them out in layoutSubviews, etc. - just like any other view. As VdesmedT says, make sure you're re-using cells via the dequeue mechanism, so that you aren't allocating new cells with each scrolling operation.
OK, I will propose something obvious but to achieve UIScrollView performance, you need to be sure that the dequeue mechanism works well. I often see developers not properly set the identifier in IB and therefore missing the UITableViewCell cache benefit

Dealing with drawRect multiple calls

I discovered that my drawRect is being called more than once. Unfortunately this had the unfortunate side-effect of double-drawing everything because all my subviews are drawn in drawRect (I'm s strict atheist w.r.t. Interface Builder).
What is the best way to deal with a multiple calls to drawrect? A flag to check if it's being called again? Or clear the whole view and redraw from scratch (as I have done?)
What do you mean by "your subviews are drawn in drawRect"? If you mean that you're putting calls to -addSubview: in your drawRect, don't. Move those to a more appropriate place that gets called only when it needs to (probably the -initWithFrame: method for your view if the subviews are always there), and use drawRect only to do custom drawing.
Strictly speaking, being called multiple times is the entire point of -drawRect. It's called to update small parts of your view when needed. It's actually called quite often if you're updating your view (moving it around, updating the superview, etc.), so it should be as simple and fast as possible to avoid performance problems.
When UIKit calls drawRect:, the graphics context set up to draw into should already be cleared for you (unless you have set the clearsContextBeforeDrawing property to NO). Are you perhaps calling drawRect: manually instead of calling setNeedsDisplay/setNeedsDisplayInRect:?
Also, each view is responsible for drawing only its own content and not the content of its subviews.

Overriding selectable area of UITableViewCell?

I'm trying to avoid putting a button in my UITableViewCell subclass so that I don't unnecessarily lag up the scrolling speed.
The button would push another view onto the navigation stack.
I figured since UITableView already has built-in and optimized methods for managing this, that simply limiting the touchable area of my cells would be the easiest and most effective way of achieving my goal.
I really have no idea how I would implement something like this. I have a feeling I would have to override pointInside:withEvent: or hitTest:withEvent:, but I'm not sure how. Managing touchEvents and the UIResponder stuff still escapes me.
So my question is ultimately, in my rootViewController, how would I implement that selecting a row will only work at lets say 220,10,40,40 ?
Thanks!
A UITableViewCell should have a normal selection style(i.e. not UITableViewCellSelectionStyleNone), generally speaking. You shouldn't have buttons on cells that you are reusing an arbitrary number of times.
If you're using a lot of UIButtons in your UITableView, you may want to rethink how your UI is designed.
You can't easily (to my knowledge) change "selectable" parts of a cell - the whole cell must be selectable no matter what(you can set the style to no selection, but you'll still recieve touch updates).
The behavior you're describing sounds like a job for UITableViewCellAccessoryDetailDisclosureButton, actually. If you're going to be pushing another view onto the stack but don't want to do that when the user selects the cell proper, use a detail disclosure button to maximize your consistency with existing UI convention. It's hard to say without more information.
As for doing the work necessary to detect touches in a sub-region, that may be more trouble than it's worth. True, adding subviews to a cell incurs a compositing cost but if you're only talking about one button, and a button whose background you can set to opaque, you'll probably be just fine. The alternative is reinventing the wheel to recreate button behavior in a subregion of the cell and that doesn't sound like functionality that will be much fun to maintain as the SDK matures.
That said, adding views to a cell doesn't incur a compositing cost per se, it's drawing those views that's the trouble. So if you really wanted to go nuts on the optimization, you could create a pre-rendered cell background image that includes the appearance of a button you want and then place a custom, image-free, see-through UIButton instance right over top of it. Nothing to draw, so no additional compositing cost. Worth a shot.
You didn't come here for a premature optimization speech, so I won't bother with one, but I say just do the cell with a normal button for now, make sure you like how the functionality feels to use then optimize toward the end if you're looking at performance that you're not happy with.