Questions about drawRect in iOS - ios

Here is the thing: I have a view, which contains complicated content that need be redraw after related data updated. I use multiple views to implement this, more likely that one large view contains several subviews, and every subview may also have several subviews. All these views override their own drawRect: method.
The question is that the complicated view takes large memory usage, and I want to know why drawRect: cause a large memory usage, so I can optimizing my view.
Is there anyone give me a hand? Thanks.

EDIT: I just noticed you didn't actually say whether you're talking about iOS or Mac OS X. I assumed iOS; if it's OS X you're on your own :P For future reference, it's good practice to tag your question with the OS or framework you're developing for.
Basically, drawRect is the most memory-heavy way of dealing with views. iOS can't optimise the subview system like it normally does because it doesn't know what you're doing, so there's multiple levels of frame buffers that need to be redrawn before the frame reaches the screen. Consider whether entire views need to be redrawn, and seriously consider using the subview layout system (needsLayout: and friends) or Core Animation to do what you need.
If you seriously need drawRect:, don't give your view any subviews. Subviews do not play nice with drawRect. Either do all your drawing in the view you need, or use the layout subsystem or CA.
Finally, last year's WWDC sessions are good help here. Session 131 "Performance Optimisation on iOS" and session 104 "Designing apps with scroll views" both talk about UIView performance. Here's the link to the sessions if you've lost it: WWDC session videos

Related

Whats the most memory conserving method to create an infinite amount of sliding UICollectionViews

I'm creating a project which uses a UICollectionView. As the user selects a cell this slides them to another UICollectionView of similar nature. Ive been noticing that no matter how I go about this I wind up with mountains of memory usage.
I've been experimenting with placing UICollectionView's in full page UICollectionViewCell's so as to take advantage of the reusability of UICollectionViews. The downside of this approach has been memory retention as the CollectionViews are never fully deallocated. Ive also heard that it is not the best practice to put a UICollectionView in a UICollectionView.
I've experimented with UIPageViewController's containing UIViewController's with the UICollectionView inside. This is more efficient as the UIViewController's can be deallocated as the user swipes back however as long as the user continues to select cells and create new view controllers the memory grows unbounded looking like a mountain.
As a bit of a hybrid I attempted as well to put ViewControllers containing UICollectionView's on UICollectionViewCell's. This method seemed to work best but I had to deallocate the view controllers manually as the user swiped.
Are there any strategies or libraries anyone could recommend that would fit this problem. How can I keep the memory down. I understand I'll need some kind of reusable views.
Ive been looking into this Library so far thank you in advance for all of your advise
Parchment Library
I think I understand what you're saying. You have a UICollectionView that can drill down to another UICollectionView, leaving the first one and its backing data retained until you come back and pop it off. Drilling down further and further allocates more and more memory until you back out.
I'd keep things as simple as possible. Solutions like putting a UICollectionView inside UICollectionViewCells can cause your code to get unnecessarily complicated, resulting in new issues and code that's programmer-hostile. If the user experience that works best is a collection view UI that you can drill down into infinitely, then go with that paradigm.
Your issue is not with UICollectionViews, it's in managing your backing data's memory use. That could be done a few ways. It would help to know what kind of data you have that's so large, and what "large" means, but here are a few approaches that come to mind.
One idea would be to unload any large data when you go to the next screen. For example, if your datasource uses an array with a bunch of large images, clear them out when the next view is pushed. Reload the data when your view appears again, or do it lazily when the view's cells need it, whichever works best for you. This would be the easiest approach and probably take care of your memory concerns.
A second approach would be to use one UICollectionView and use custom animations so it looks like a new collection view is pushing/popping from an old one, when in fact you're just changing the data for the collection view and reloading. You could even provide animations that are more interesting than pushing/popping.
On top of either of these approaches, you could implement the UICollectionView prefetch API calls to load data just before you need it. That will reduce your memory footprint even further.
All of these approaches assume that you can load the data to display from storage-- that it's not just in memory from recent webservice requests. Your users are guaranteed a miserable experience if your app has to keep requesting the same large data from the web over and over. So, if you don't have the data stored locally already, set up a cache.
Regardless of the approach, this is something you should be able to handle without adopting a library. UICollectionViews are designed to be memory friendly. Your issue is really in determining the best way to manage your backing data's memory use.

Which is best if I wanted Page based design?

Which one is good to use for a page based design, Pageviewcontroller or UIScrollview with paging.
Which will consume less memory? I have done it via UIScrollview; but it's consuming very huge memory. Any help will be greatly appreciated.
Using UIScrollView for the application is not a handy task. For iOS 6, you should use UIPageViewController. But for iOS 5, UIPageViewController is not be good as it only provides scrolling for the page transition.
You may reuse the UIScrollView views then,
Many examples are there in SO like this
It is hard to say which is "best" - it depends on what makes sense for your app. Which ever approach you choose you can minimise memory consumption by "lazy loading" content into the scroll view when it is needed. For example, if you were displaying pages of images and the images are stored as files in your app then you should only add the current image and the image either side of the current image. As you scroll you can load and add the next image and release.
Here is a tutorial that shows this approach with UIScrollView- Multiple virtual pages in a UIScrollView with just 2 child views
UIPageViewController makes it a bit simpler by adopting a dataSource pattern, so you can concentrate on responding to data requests and not have to worry about manipulating the views.

Too many UIViews? What's the cost, what are the tradeoffs?

EDIT:
Imagine that I'm in the business of reimplementing CoreText.
I'd get an NSString here which is an ordered list of code-points, and a set of glyphs there (an NSFont/CTFont) that basically is a list of CGPathRef.
Something in-between is in charge of the layout.
And some kind of environment must provide a CGGraphicContext to render into.
The kind of objects I have to display are CGPath. I am worried of creating one UIView for each CGPath! For a block of text, you wouldn't create an UIView for each character-glyph, would you?
I have a potentially large set of (tiny) objects to draw at once. In the 1000's. Many will be offscreen until scrolled into view.
One one hand it looks like my best bet would be to create one UIView for each one. The reasons are that:
with proper tiling I can make sure that the views that are offscreen
are not even in memory most of the time and brought up to life when
required
more importantly, when zooming I want to set each such view's layer contentScale to the zoomFactor such that I still get the full
precision and not the blurry stair-case
On the other hand I have no idea how the (otherwise perfectly generic) parent view will behave with 1000's of children.
So I am tempted to rethink my code such that the "children" just become an area of the single view.
But then I start thinking of setting that view's layer contentScale and I'm worried about exhausting memory just while zooming: I do not know if the layer machinery is "clever" enough not to create a huge backing bitmap when most of its contents would be invisible anyway
What to do, oh what to do?
Anyone has any experience they want to share?
I recently saw a presentation on a game involving hundreds of sprites implemented as UIViews, mostly image views. Performance was excellent on both an older and recent generation iPad. His code is available at https://github.com/bentford/ButtonWars/ , so you may want to test it to see if the performance meets your needs.
There is often an assumption that views are heavyweight, but experience doesn't bear that out.
If most of the objects will not be on the screen at any time, it's probably better not to create views for all of those off-screen objects. You need to intelligently add views for just those objects that are visible, and remove them from the view hierarchy when they go off-screen. You might be able to use UICollectionView to make this easy - it is very well designed, efficient, flexible, and easy to use and customize - but I don't know if it works with zooming.
If you decide to draw everything in one view, with deep zooming, you should look at CATiledLayer. It is designed for zooming in very deeply. Apple's MKMapView uses it under the covers to support scaling its viewport from Earth-sized down to a (relatively) few square meters.
If you cant use UICollectionView due to iOS version boundations, and you are intrested in re using views in UIScrollView, than have a look at this class. It is used the same way UITableview is used. Just pass in number of views and it will reuse the views.You can go through readme. Its very straightforward.

UICollectionView performance - _updateVisibleCellsNow

I'm working on a custom UICollectionViewLayout that displays cells organized by day/week/month.
It is not scrolling smooth, and it looks like the lag is caused by [UICollectionView _updateVisibleCellsNow] being called on each rendering loop.
Performance is OK for < 30 items, but at around 100 or more, its terribly slow. Is this a limitation of UICollectionView and custom layouts, or am I not giving the view enough information to perform correctly?
Source here: https://github.com/oskarhagberg/calendarcollection
Layout: https://github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarWeekLayout.m
Data source and delegate: https://github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarView.m
Time Profile:
Update
Maybe its futile? Some testing with a plain UICollectionViewController with a UICollectionViewFlowLayout that is given approximately the same amount of cells/screen results in a similar time profile.
I feel that it should be able to handle ~100 simple opaque cells at a time without the jitter. Am I wrong?
Also don't forget to try to rasterize the layer of the cell:
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
I had 10 fps without that, and boom 55fps with!
I'm not really familiar with GPU and compositing model, so what does it do exactly is not entirely clear to me, but basically it flatten the rendering of all subviews in only one bitmap (instead of one bitmap per subview?).
Anyway I don't know if it has some cons, but it is dramatically faster!
I have been doing considerable investigation of UICollectionView performance. The conclusion is simple. Performance for a large number of cells is poor.
EDIT: Apologies, just re-read your post, the number of cells you have should be OK (see the rest of my comment), so cell complexity may also be a problem.
If your design supports it check:
Each cell is opaque.
Cell content clips to bounds.
Cell coordinate positions do not contain fractional values (e.g. always calculate to be whole pixels)
Try to avoid overlapping cells.
Try to avoid drop shadows.
The reason for this is actually quite simple. Many people don't understand this, but it is worth understanding: UIScrollViews do not employ Core Animation to scroll. My naive belief was that they involved some secret scrolling animation "sauce" and simply requested occasional updates from delegates every now and then to check status. But in fact scroll views are objects which don't directly apply any drawing behaviour at all. All they really are is a class which applies a mathematical function abstracting the coordinate placement of the UIViews they contain, so the Views coordinates are treated as relative to an abstract contentView plane rather than relative to the origin of the containing view. A scroll view will update the position of the abstract scrolling plane in accord with user input (e.g. swiping) and of course there is a physics algorithm as well which gives "momentumn" to the translated coordinate positions.
Now if you were to produce your own collection view layout object, in theory, you could produce one which 100% reverses the mathematical translation applied by the underlying scrollview. This would be interesting but useless, because it would then appear that the cells are not moving at all as you swipe. But I raise this possibility because it illustrates that the collection view layout object working with the collection view object itself does a very similar operation to the underlying scrollview. E.g. it simply provides an opportunity to apply an additional mathematical frame by frame translation of the attributes of the views to be displayed, and in the main this will be a translation simply of position attributes.
It is only when new cells are inserted or deleted moved or reloaded that CoreAnimation is involved at all; most usually by calling:
- (void)performBatchUpdates:(void (^)(void))updates
completion:(void (^)(BOOL finished))completion
UICollectionView requests cell layoutAttributes for each frame of scrolling and each visible view is laid out for each frame. UIView's are rich objects optimised for flexibility more than performance. Every time one is laid out, there are a number of tests the system does to check it's alpha, zIndex, subViews, clipping attributes etc. The list is long. These checks and any resulting changes to the view are being conducted for each collection view item for each frame.
To ensure good performance all frame by frame operations need to be completed within 17ms. [With the number of cells you have, that is simply not going to happen] bracketed this clause because I have re-read your post and I realise I had misread it. With the number of cells you have, there should not be a performance problem. I have personally found with a simplified test with vanilla cells, containing only a single cached image, the limit tested on an iPad 3 is about 784 onscreen cells before performance starts to drop below 50fps.
In practice you will need to keep it less than this.
Personally I'm using my own custom layout object and need higher performance than UICollectionView provides. Unfortunately I didn't run the simple test above until some way down the development path and I realised there are performance problems. I'm so I'm going to be reworking the open source back-port of UICollectionView, PSTCollectionView. I think there is a workaround that can be implemented so, just for general scrolling about, each cell item's layer is written using an operation which circumvents the layout of each UIView cell. This will work for me since I have my own layout object, and I know when layout is required and I have a neat trick that will allow the PSTCollectionView to fall back to its normal mode of operation at this time. I've checked the call sequence and it doesn't appear to be too complex, nor does it appear at all unfeasible. But for sure it is non-trivial and some further tests have to be done before I can confirm it will work.
Some more observations that might be helpful:
I am able to reproduce the problem, using flow layout with around 175 items visible at once: it scrolls smoothly in the simulator but lags like hell on iPhone 5. Made sure they are opaque etc.
What ends up taking the most time seems to be work with a mutable NSDictionary inside _updateVisibleCellsNow. Both copying the dictionary, but also looking up items by key. The keys seems to be UICollectionViewItemKey and the [UICollectionViewItemKey isEqual:] method is the most time consuming method of all. UICollectionViewItemKey contains at least type, identifier and indexPath properties, and the contained property indexPath comparison [NSIndexPath isEqual:] takes the most time.
From that I'm guessing that the hash function of UICollectionViewItemKey might be lacking since isEqual: is called so often during dictionary lookup. Many of the items might be ending up with the same hash (or in the same hash bucket, not sure how NSDictionary works).
For some reason it is faster with all items in 1 section, compared to many sections with 7 items in each. Probably because it spends so much time in NSIndexPath isEqual and that is faster if the row diffs first, or perhaps that UICollectionViewItemKey gets a better hash.
Honestly it feels really weird that UICollectionView does that heavy dictionary work every scroll frame, as mentioned before each frame update needs to be <16ms to avoid lag. I wonder if that many dictionary lookups either is:
Really necessary for general UICollectionView operation
There to support some edge case rarely used and could be turned off for most layouts
Some unoptimized internal code that hasn't been fixed yet
Some mistake from our side
Hopefully we will see some improvement this summer during WWDC, or if someone else here can figure out how to fix it.
Here is Altimac's answer converted to Swift 3:
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale
Also, it should be noted that this code goes in your collectionView delegate method for cellForItemAtIndexPath.
One more tip - to see an app's frames per second (FPS), open up Core Animation under Instruments (see screenshot).
The issue isn't the number of cells you're displaying in the collection view total, it's the number of cells that are on screen at once. Since the cell size is very small (22x22), you have 154 cells visible on screen at once. Rendering each of these is what's slowing your interface down. You can prove this by increasing the cell size in your Storyboard and re-running the app.
Unfortunately, there's not much you can do. I'd recommend mitigating the problem by avoiding clipping to bounds and trying not to implement drawRect:, since it's slow.
Big thumbs up to the two answers above!
Here's one additional thing you can try: I've had big improvements in UICollectionView performance by disabling auto layout. While you will have to add some additional code to layout the cell interiors, custom code seems to be tremendously faster than auto layout.
Beside the listed answers (rasterize, auto-layout, etc.), you may also want to check for other reasons that potentially drags down the performance.
In my case, each of my UICollectionViewCell contains another UICollectionView (with about 25 cells each). When each of the cell is loading, I call the inner UICollectionView.reloadData(), which significantly drags down the performance.
Then I put the reloadData inside the main UI queue, the issue is gone:
DispatchQueue.main.async {
self.innerCollectionView.reloadData()
}
Carefully looking into reasons like these might help as well.
In few cases it is due to Auto-layout in UICollectionViewCell. Turn it off (if you can live without it) and scrolling will become butter smooth :)
It's an iOS issue, which they havnt resolved from ages.
If you are implementing a grid layout you can work around this by using a single UICollectionViewCell for each row and add nested UIView's to the cell. I actually subclassed UICollectionViewCell and UICollectionReusableView and overrode the prepareForReuse method to remove all of the subviews. In collectionView:cellForItemAtIndexPath: I add in all of the subviews that originally were cells setting their frame to the x coordinate used in the original implementation, but adjusting it's y coordinate to be inside the cell. Using this method I was able to still use some of the niceties of the UICollectionView such as targetContentOffsetForProposedContentOffset:withScrollingVelocity: to align nicely on the top and left sides of a cell. I went from getting 4-6 FPS to a smooth 60 FPS.
Thought I would quickly give my solution, as I faced a very similar issue - image-based UICollectionView.
In the project I was working in, I was fetching images via network, caching it locally on device, and then re-loading the cached image during scrolling.
My flaw was that I wasn't loading cached images in a background thread.
Once I did put my [UIImage imageWithContentsOfFile:imageLocation]; into a background thread (and then applied it to my imageView via my main thread), my FPS and scrolling was a whole lot better.
If you haven't tried it yet, definitely give a go.

Best approach for UITableViewCell with complex format

I'm new to iOS development pondering how best to approach a fairly simple design problem. I want to display a set of items, each one of which has the structure as sketched. In a given set, not more than 10's of items.
Each item includes a thumbnail image, a heading, a blurb, and a set of buttons. There are two complications:
The amount of text and number of buttons is variable.
The text requires some internal formatting (italics and bold).
I've considered these approaches:
Use a table view, with custom, resizable UITableViewCell, probably using something like OHAttributedLabel for the text. For the variable number of buttons, either lay these out programmatically or possibly use the new collection view (for older iOS, have to use 3rd party grid view).
Use a table view with custom cell based on UIWebView.
Do the whole set as one UIWebView.
Use a table view with sections; each item having its own section and parsing out the buttons and text to rows.
Would love to get suggestions about how a more experienced iOS dev would approach this.
EDIT: I am now considering that the best way may be:
5) Use UICollectionView for the whole thing.
UPDATE: In the end, I laid the whole thing out in code as a custom table cell (ie., #1). This was a good choice, not only for the reasons given in the answer, but because as someone new to iOS development, it's something I needed to get under my belt. Didn't even use collection view for the buttons, because I was worried about performance and also the hassle of supporting iOS5.
I do think that using collection view for the whole design (#5) would have been an elegant solution, and I actually started down that path. However, some complications not shown in the simplified pic above made that unwieldy.
2nd UPDATE: #1 turned out to be a dead end. My final solution used a UIWebView (#3) - see answer.
I have found some resources that might be useful to some people who is doing complex tableviewcell and want fast scrolling. I am still developing it, but I want to share this first to you guys.
Facebook iOS release note: they mentioned techniques: core text, pre/asynchronous calculation of table height, do a lot of things on background thread, save layout attribute in core data. http://www.facebook.com/notes/facebook-engineering/under-the-hood-rebuilding-facebook-for-ios/10151036091753920
Fast scrolling sample: https://github.com/adamalex/fast-scrolling
Apple's sample project TableViewSuite. The 4th example.
https://developer.apple.com/library/ios/#samplecode/TableViewSuite/Introduction/Intro.html
Very close to solution..YES
I know this is an old thread, but I found it very interesting, as I am just now getting around enough as an iPhone developer to reach these types of performance concerns. I found a very interesting article on Facebook's site by Facebook Engineering describing how they implemented UITableView and overcame the dynamic sizing issues, also with rapid content management. It seems they precalculated using deeper objects and kept everything asynchronous and pre-cached where possible. I'm going to provide a link to the article, but I'm going to copy the section that tackles exactly this problem. I hope you find it useful. Here's the link, https://www.facebook.com/notes/facebook-engineering/under-the-hood-rebuilding-facebook-for-ios/10151036091753920, and the most relevant excerpt:
(Re-)Building for Speed
One of the biggest advantages we've gained from building on native iOS has been the ability to make the app fast. Now, when you scroll through your news feed on the new Facebook for iOS, you'll notice that it feels much faster than before. One way we have achieved this is by re-balancing where we perform certain tasks. For example, in iOS, the main thread drives the UI and handles touch events, so the more work we do on the main thread, the slower the app feels. Instead, we take care to perform computationally expensive tasks in the background. This means all our networking activity, JSON parsing, NSManagedObject creation, and saving to disk never touches the main thread.
To give another example, we use Core Text to lay out many of our strings, but layout calculations can quickly become a bottleneck. With our new iOS app, when we download new content, we asynchronously calculate the sizes for all these strings, cache our CTFramesetters (which can be expensive to create), and then use all these calculations later when we present the story into our UITableView.
Finally, when you start Facebook for iOS, you want to see your news feed, not a loading spinner. To provide the best experience possible, we now show previously-cached content immediately. But this introduces a new problem: If you have a lot of stories in your news feed, UITableView throws a small spanner in the works by calling the delegate method -tableView:heightForRowAtIndexPath: for each story in your news feed in order to work out how tall to make its scrollbar. This would result in the app loading all the story data from disk and calculating the entire story layout solely to return the height of the story, meaning startup would get progressively slower as you accumulate more stories.
The solution to this particular problem has two main parts. Firstly, when we do our initial asynchronous layout calculations, we also store the height of the story in Core Data. In doing so, we completely avoid layout calculation in -tableView:heightForRowAtIndexPath:. Secondly, we've split up our "story" model object. We only fetch the story heights (and a few other things) from disk on startup. Later, we fetch the rest of the story data, and any more layout calculations we have to do are all performed asynchronously.
All this and more leads to high frame rates while scrolling and an app that remains responsive.
I originally accepted (and implemented) #Daij-Djan's answer, but now I believe the best approach is #3 (UIWebView). It comes down to performance.
UITableView strains to perform well with custom cells with subviews, especially in the context of cells with varying heights. The rows of buttons make the scrolling choppy. As suggested by Apple in Cells and Table View Performance , I made sure that subviews were all opaque, however there is no way to follow the suggestion of "Avoid relayout of content."
Add onto that dynamic cell heights and attributed strings and these tables scroll pretty poorly. I suppose the next optimization would be to override drawRect, but at that point I decided to try UIWebView.
UIWebView is not without its performance issues either! Its scrolling performance degrades pretty fast as the content grows, however, I can manage that by hiding content and letting user "open" it as desired.
no 1 is maybe the most work directly followed by #2 BUT
as ACB said, it's also the most flexible and IMO will surely provide the best look'n'feel
no 3 works but will not feel as smooth / alway be tad 'html-ish'
no 4 sounds like highway to hell (later on. it will be a PITA to modify/maintain)

Resources