Performance of drawRect - iphone

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.

Related

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).

Using drawRect, setNeedsDisplay and layoutSubViews

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.

Complex nib is slow to load

I'm looking for advice about a nib that's very slow to load. It's big and complex, with lots of subviews and doodads. When I fire my UINavController to push it, it's noticeably laggy (maybe almost a second) on my 3G. It sits there with the table cell selected and nothing else happening for long enough to make you wonder if it's broken.
I wonder about pre-loading it in another thread while the user is on the previous view. I could probably fire the selector in the background with a delay in the previous view's viewDidAppear, and then keep it in a property until push time comes.
Thoughts?
Can you split the subviews and doodads into their own, smaller nibs? Then you don't have to load everything at the same time, just what you need for when you need it.
I am working on a project, which is so full of the same, it would be unusable in NIB form. I choose to start creating my own sub-views in code, and then in performance concern areas, did my own drawing (like you would in a complex/custom UITableViewCell implementation).
It isn't really that hard to create some or all of your elements in code, or roll areas of them up to something that can be drawn by hand.
Just a thought.
I would be careful about assuming that it is the loading of the NIB file which is slowing you down. Run Instruments and / or Shark against your application, focusing on sampling what happens when you tap on the table to bring up your new view. You might be surprised at where the bottleneck is. In particular, it might not be loading the NIB itself, but something in the initialization methods of one or more of the 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.

Cocoa-Touch: When implementing drawRect, how do you redraw the transparent background?

I'm implementing a UIView's (UITableViewCell to be more exact) drawRect method.
My view has a transparent background, so when something is changed in the view, the older drawn version still remains there. So if on the first drawRect draw an "A", then a "B" on the same point, I get both of them drawn on top of each other.
How can I tell the framework to redraw the background?
(which I suppose it doesn't do because is not always needed, but in this case it badly is)
I guess what I need is the equivalent of win32's invalidateRect, however I went thru UIViews members and didn't find anything.
Btw, i've tried setNeedsDisplay, it didn't help.
I think I've used CGContextClearRect(CGContextRef context, CGRect rect) for this before.
you should set clearsContextBeforeDrawing
I wonder if, since it is your own view that is retaining the drawing, whether you yourself should erase the rect being passed in?
What happens if your background is not transparent?
It's possible having come from the Windows world that you're thinking about the problem wrong. Having also come from the Windows world, I've done the same many times myself. Why do you need to override drawRect? Whenever you do that, you are responsible to take care of everything. Is it possible to do what you are wanting another way? What are you drawing in drawRect? Can you just add sub views or sub layers to your cell instead?
BTW, are you calling [super drawRect:rect] at the beginning of your drawRect override?
I'm no longer reusing cells.
This is working for me because I always have < 20 cells in my table. I guess this is not a solution for everyone, but it's the way I'm planning to go.
Rather than have the framework create and reuse 10ish cells, I'm creating my 20 while fetching the data for them, and later at display time things go smoother because they don't need to be re-customized every time.