I am implementing a UITableView with two images in a cell. Both images will be obtained via URL. I was wondering what is the best approach to load both these images asynchronously.
There were a couple of recommendations:
http://www.hollance.com/2011/03/mhlazytableimages-efficiently-load-images-for-large-tables/
and
http://www.markj.net/iphone-asynchronous-table-image/
However, I am not very sure which is the best approach for my requirements
1) Loading of around two hundred cells in a UITableView
2) Each cell contains two images to be obtained via URL
Those links should be useful. One good thing about UITableViews is that they do not create all of the cells at once. This means that if the images start loading only when applicable cells are created, it'll roughly load the ones on the screen first, which is a desirable behavior. If you make sure that the images are only retained by the UIImageViews then you won't risk having all images cached in memory in a low memory situation, as cells can be recreated. Depending on what kind of data you are loading, you may wish to cache the images – sized to their target dimensions – in the filesystem or database.
Related
I'm building a UICollectionView with two sections. Each section has its own layout - the first section is a 2-by-y grid and the second section is a 3-by-y grid. In addition to section-specific layouts, I need sticky headers (such as those incorporated by default into UITableViews).
I have already built a fully functional subclass of UICollectionViewFlowLayout that handles section-specific layouts and sticky headers appropriately. However, the solution doesn't scale well past 250+ cells in either section. I did some profiling and investigation, and the root of the problem seems to be shouldInvalidateLayoutForBoundsChange. I return YES in my subclass because of the need for dynamic calculation of my supplementary views (the headers) as the user scrolls. This causes invalidation of both supplementary view and cell layouts, meaning that the flow layout calls prepareLayout on the cells over and over despite the fact that the cell layouts don't actually require invalidation as they aren't changing. When the number of cells requiring layout every refresh cycle creeps into the hundreds or thousands, performance drops significantly.
What I've Tried
I cache the UICollectionViewLayoutAttributes for the cells upon first calculation, allowing the flow layout to reference the cache when the system calls layoutAttributesForItemAtIndexPath. The advantage of caching in this way flies out the window when I'm constantly invalidating the layout, as prepareLayout runs it back and repopulates the cache every time. I tried implementing a system in which prepareLayout would only populate the cache on the first pass and execute no logic on subsequent passes. This dramatically improved performance, and worked, but the solution breaks down when inserting and deleting cells, causing an assertion failure.
I've also done a significant amount of research into other implementations of sticky headers and similar layout requirements involving consistent invalidation, but none of these solutions need to simultaneously tackle the section-specific layout problem that I have. As a result, their recommended solutions aren't feasible. I'm a bit out of my depths on this one...
For anyone interested in implementing something similar, here's the solution I arrived at. This solution avoids the need to default shouldInvalidateLayoutForBoundsChange to YES, saving a ton of CPU overhead, while keeping the sticky headers intact.
I manually added my headers as subviews of self.collectionView and wrote a method to track self.collectionView.contentOffset.y to float and stick the headers appropriately as content scrolls. The one caveat of this solution is that it doesn't scale well to larger numbers of sections; by adding the views manually, you lose some benefits of UICollectionReusableView such as dequeuing and recycling. In my implementation there are a maximum of 5 sections, so the sacrifice was minimal.
I'm designing whole UI using storyboard and all is well but for table view cells it seems to be too much. Get's crowded in designer and views can vary good deal which calls for many outlets.
I decided to custom draw them. I understand process of doing it but 2 things bother me:
Performance. Will custom-drawn cells be slower than storyboarded ones?
Reuse. I understand how reuse works and it seems that completely "erasing" contents and re-drawing might be slower than just creating new cell every time. Is that true?
If you look at design - to me it seems to be easer to write and maintain complete drawing through the code because of font backgrounds, colors, lines, etc.
In relation to How to determine if a user has scrolled to the end of an NSTableView
Thanks Josh.
Is there a way to use this mechanism to implement a NSTableView that provides some sort of infinite scroll or pagination.
The idea is to tell NSTableView to load up to a certain number of records, say 1k records at once and than as user scrolls closer to the end pull another 1k records and maybe forget the first 1k records.
This pattern is well defined/used in web applications and java. Only the visible number of rows is loaded initially and the rest is pulled async as user scrolls up and down the table.
I am interested in some obj-c code or tips on how to code this.
I know about filtering/limiting the number of records that go into the tableview but lets ignore that for a moment.
Thanks.
Given the details you've provided, I'll generalize a bit but here's how I might solve it:
First, I'd set a MUCH SMALLER batch size than 1000 records. If the result count or "the most anybody is ever going to want to see" is indeterminate (and it sounds like it is in your case), the user probably doesn't even care past the first 100 or so. If your user often requests a large, expensive list and immediately wants to see stuff so far away from the beginning they hurl the scroller downward for two minutes straight before they stop and look around, perhaps a more intuitive sort order is needed instead of asking Google Image for 1000 more animated kitten gifs. ;-)
The controller behind the (definitely view-based for view reuse) table view will need some sort of request queue since I assume you're batching things in because they're expensive to retrieve individually. This will manage the asynchronous requesting/okay-now-it's-loaded machinery (I know that's vague but more detail is needed to get more specific). You'll make sure any "currently alive" views will somehow get this "it's ready" notification and will go from some "busy" UI state to displaying the ready item (since we NEVER want to keep the table waiting for a ready-to-display view for the object at a given row, so the view should at least show some "still waiting for details" indication so quick scrolls over lots of rows won't stall anything).
Using a view-based NSTableView and associated data source methods will let the table view handle only keeping enough copies of your custom NSTableCellView around to reuse during scrolling. Since you have to provide a configured view when asked, the view's default state can either be "draw nothing if not ready" or some visually generic placeholder until the object is realized and ready (then you can just reload that row instead of the whole table). This way the table keeps scrolling and drawing rapidly because it doesn't care about what your controller is doing to fulfill the promise of updating the visible rows (that custom cell view of yours will observe its represented object's updates).
You probably want the scrollers to reflect the total number of rows batched in so far if the upper bound is astronomical - reflecting that size would make the scroll grip both tiny and very sensitive. Instead, just grow the scroller (via the table view's row count) by what the user has "requested" so far, all the way back to the beginning of the list. Any time more are batched in, you'll want to add the batch size to your controller's total batched row count. This still lets the scroller zoom by rows the user couldn't distinguish at that speed anyway. You communicate the row count change to the table view by sending it -noteNumberOfRowsChanged and replying to its resulting data source request ( -numberOfRowsInTableView: ) with the updated total row count you stashed in a property of your controller. It'll ask for views for the newly visible rows as needed (which will be in some neutral, unfulfilled visual state until it's realized as before), update the scroll view, lather, rinse, repeat.
You could use NSCache to keep memory usage low. Set its countLimit to several times your batch size and let it drop previous batches if it decides it needs to dump the first n model objects, then batch them back in if the table view suddenly asks for a view for a row no longer in the batch window's range.
Without knowing more about your requirements and architecture, it's hard to get more specific. If I haven't hit the mark, consider editing your question to include more detail. If I'm totally off base from what you're asking for, please clarify. :-)
I know more about iOS, but I think the answer is similar. Table views are intrinsically finite, but you can roll your own custom scroll view to do this. The trick is to set a large content size and implement layout in your subclass (which will get called on every scroll change). In that method, check to see if the content offset is near zero or near the content size. If it is, then translate the content offset back to the center of the content size and translate all the subviews (keep them on one parent content view) by the same distance so the user doesn't see any motion. Make a datasource protocol and keep asking your datasource for "cells" that tile the visible part of the view.
It should be up to the datasource to recognize what we would have called a page-fault in the olden days, to decide that some of the model in memory should be discarded in favor of the model where the user is scrolling.
I poked around for an NS equivalent, but didn't see one on cursory search. Here's a decent-looking reference on the idea done in iOS.
I'm developing an iPhone Cocos2D game and reading about optimization. some say use spritesheet whenever possible. others say use atlassprite whenever possible and others say sprite is fine.
I don't get the "whenever possible", when each one can and can't be used?
Also what is the best case for each type?
My game will typically use 100 sprites in a grid, with about 5 types of sprites and some other single sprites. What is the best setup for that? guidelines for deciding for general cases will help too.
Here's what you need to know about spritesheets vs. sprites, generally.
A spritesheet is just a bunch of images put together onto one big image, and then there will be a separate file for image location data (i.e. image 1 starts at coordinate 0,0 with a size of 100,100, image 2 starts at coordinate 100,0, etc).
The advantage here is that loading textures (sprites) is a pretty I/O and memory-alloc intensive operation. If you're trying to do this continually in your game, you may get lags.
The second advantage is memory optimization. If you're using transparent PNGs for your images, there may be a lot of blank pixels -- and you can remove those and "pack" your texture sizes way down than if you used individual images. Good for both space & memory concerns. (TexturePacker is the tool I use for the latter).
So, generally, I'd say it's always a good idea to use a sprite sheet, unless you have non-transparent sprites.
I have a large data set (some 3500 objects) that returns from a remote server via HTTP. Currently the data is being presented in an NSCollectionView. One aspect of the data is a path pack to the server for a small image that represents the data (think thumbnail for simplicity).
Bindings works fantastically for the data that is already returned, and binding the image via a valueurl binding is easy to do. However, the user interface is very sluggish when scrolling through the data set - which makes me think that the NSCollectionView is retrieving all the image data instead of just the image data used to display the currently viewable images.
I was under the impression that Cocoa controls were smart enough to only retrieve data for the information that is actually being output to the user interface through lazy loading. This certainly seems to be the case with NSTableView - but I could be misguided on this thought.
Should valueurl binding act lazily and, moreover, should it act lazily in an NSCollectionView?
I could create a caching mechanism (in fact I already have such a thing in place for another application - see my post here if you are interested Populating NSImage with data from an asynchronous NSURLConnection) but I really don't want to go this route if I don't have to for this specific implementation as the user could potentially change data sets often and may only want small sub-sets of the data.
Any suggested approaches?
Thanks!
Update
After some more testing it seems that the problem arises because a scroll action through the data set causes each image to be requested from the server. Once all the images have been passed over in the data set the response is very fast.
So question... is there any way of turning off the valueurl fetch while scrolling and turning it back on when scrolling has finished?
My solution is to use a custom caching mechanism like the one I already use for another application. The problem manifests itself because as you scroll past images that have not yet been downloaded, the control triggers itself to go and fetch the as yet non-downloaded files.
Once downloaded the images are available locally and therefore scrolling speed normalizes. The solution is to check to see if the image is available locally and present an alternate app-bundle graphic while the image is being downloaded in the background. Once the image has been downloaded, update the model with the image replacing the stub image that came from the bundle.
This leaves the UI in a very responsive state throughout, leaves the user with the ability to interact and allows for a custom background management of the images.
Of course it would have been nice if Cocoa id all this for me, but then what would I be left to do? :-)