Best approach for UITableViewCell with complex format - ios

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)

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.

Is it possible to change the sensitivity/pull distance of a UIRefreshControl?

I have a UITableView displaying a table of data with a somewhat large contentInset value. As a result, the user cannot trigger the UIRefreshControl because it expects them to scroll too far.
Basically, I'm wondering if it's possible to make UIRefreshControl adjust for contentInset values.
Edit: This question is mostly about curiosity, so I removed the extra details and rephrased the question to be more direct.
I think you're going about this a bit wrong. If you've encountered this issue because of the mechanism you've created to preload cells, you should think of a different approach for preloading them. If there's any content to be rendered / loaded / downloaded, you could be doing this in a background thread and leave a minimal amount of work for cellForRowAtIndexPath. If you insist on keeping this implementation, I'd suggest looking up a custom refresh control as there is no way to customize the build in UIRefreshControl in terms of content offset for refresh (AKA sensitivity). I believe this is ultimately a good thing - there should be consistent user experience across apps running on iOS which use a refresh control.

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.

Optimising a UIScrollView

people
My project has a social media stream which loads paged objects and also pulls to refresh. I am using an MGScrollView which extends UIScrollView and I am wondering how I can optimise it. The issue is that after hundreds of items are loaded into the stream, it will become quite a memory hog.
It's not really possible to use a page pattern as all my content varies in size. The items that appear can vary in complexity and number of sub views. Added to that, because of the pull to refresh feature, items can pop into the top of the stream making dropping and adding items on/off screen pretty tricky.
Does anyone know of any sweet project I can leverage to help me do this?
Files attached of stream screen dumped from the app itself:
I think you should try adding features (animation and pull to refresh) to UITableView instead of optimising the scrollview. TableView is the solution of memory issues on scroll view. But of course that will make you use more cpu.
I think, if you use table view, your cells' height will be dynamic. For performance concern, you can check this article about twitter's iOS application.
ehttp://engineering.twitter.com/2012/02/simple-strategies-for-smooth-animation.html

Tricks for improving iPhone UITableView scrolling performance?

I have a uitableview that loads fairly large images in each cell and the cell heights vary depending on the size of the image. Scrolling performance is decent, but can sometimes be jerky.
I found these tips I found on the FieryRobot blog:
glassy-scrolling-with-uitableview
more-glassy-scrolling-with-uitableview
Does anyone have any tips for improving uitableview scrolling performance?
Cache the height of the rows (the table view can request this frequently)
Create a least-recently-used cache for the images used in the table (and invalidate all the inactive entries when you receive a memory warning)
Draw everything in the UITableViewCell's drawRect: if possible avoid subviews at all costs (or if you require the standard accessibility functionality, the content view's drawRect:)
Make your UITableViewCell's layer opaque (same goes for the content view if you have one)
Use the reusableCellIdentifier functionality as recommended by the UITableView examples/documentation
Avoid gradients/complicated graphical effects that aren't pre-baked into UIImages
If you are subclassing
UITableViewCell, don't use a Nib,
write it in code instead. It's much
faster than loading Nib files.
If you're using images, make sure
you're caching them so you don't
have to load from file more than
once for each (if you have the
memory -- you'd be surprised how
much space images take up).
Make as many elements opaque as
possible. Similarly, try not and use
images with transparency.
The developer behind Tweetie has written extensively about this and has some code that demonstrates how it was done for that app. Basically, he/she advocates one custom view per table cell, and drawing it manually (rather than subviewing with Interface Builder, among other options).
fast-scrolling-in-tweetie-with-uitableview
Also, Apple has updated its own sample code for TableView in its TableViewSuite tutorials (maybe in response to this?)
TableViewSuite
#1 performance killer for UITableView scrolling is drawing shadows on any cell view layer, so if scrolling performance matters then don't do shadows unless basically it doesn't slow down your main thread.
thought this had to be said since none of the accepted answers made mention of shadows and layers. :+)
Any problem with UITableView scrolling performance can be solved using techniques already described in other answers. However many a times sluggish performance is caused by something inherently erroneous, or repetitive.
The fact that UITableView reuses the cells, and the fact that each cell may need its own image - together makes the solution bit complex. From how it's being solved the general way, here I summarize things that should be taken care of:
Load data into data source - from REST / database. This step should be done on background, eventually using dispatch_async along with GCD queue.
Create and initialize relevant data model objects and putting them inside an array
[tableView reloaddata]
Inside cellForRowAtIndexPath, include code that will set data (text) from correct data model object of the array.
Now images maybe in the form of URL too, so this step might be little quirky because of cell reuse done by table view. The heart of the fact is to load once again image from device cache / URL using async queue, then set it to correct cell.image (whatever is your cell image property).
To avoid problems, refer to this tutorial about lazy loading of images inside table view.

Resources