How to fix smooth scrolling issues in a UITableView with custom view controllers in headers? - ios

I have created a tableview in such a way that all the rows are of 0 height but with custom headers. I am displaying different view controllers in different headers. I am able to parse the table properly but when I scroll down, it doesn't scroll smoothly.
The view controllers I added in the headers are mostly collection view and page view controllers, which display images.
What can I do to fix this issue?
Thanks in advance!

This is really tough without any code, but understand these principles of a UITableView:
Every time you show a cell or header that includes a view controller or view that hasn't been instantiated, it creates that on the main thread which is also doing the scrolling, hence the lag time.
UICollectionViewControllers and UIPageViewController are EXTREMELY heavy classes that take a lot of time to instantiate and setup. I would recommend NOT putting them in a UITableView unless you have to and if you do, make sure you're only instantiating one of them per cell and not recreating them each time.
Loading images can also take quite some time if not done asynchronously, especially depending on where you're getting them from. Offload as much as you can onto separate threads.

Related

Using TableView instead of ScrollView

For creating a login page or something like that, in autolayout case I use tableView instead of using a scrollView in viewController. For IndexPath majority cases I reuse the customCell. For entirely different items only I use another customCell. Since the Cell Class are increasing in my project. I dont know is it a good practice. Please share your opinion to increase the overall performance and least memory usage for my application.
Putting UITableView in all views is not a good practice every time.
I recommend using scrollviews and content views inside them in pages like login.
Use Tableviews only in those screens which show repeated contents.
I have seen codes generated through tableview controllers in every page.
I dont do in that way.
Better you go for Scrollviews.. Its a good method as far as I have seen.

Embedding UIViewcontrollers inside UICollectionviewCells

In my app I have a home page which has a Card/Grid Layout with multiple section. So UICollectionView is the obvious choice here. But these cards vary a lot in design, loading data and their functionality too. So putting all those in one single controller would be cumbersome, complex and hard to maintain. So we thought we need to separate these out into their own UIViewControllers. Each handles a type of card and calculates its contentSize to be shown inside a card. Our main home page controller is only responsible for putting those viewcontroller views inside the UICollectionView cells.
To summerize,
A HomeViewController which has a CollectionView
Every view inside the collectionViewCell and its data are maintained by its own controller Class.
All the instances of the controller are inside the HomeViewController and added as its children.- Apple Guide
The appearance calls to all the children viewControllers are made by ourselves since, the contentSize (Which is used as the item size for the collectionView layout) calculation of the controller views need the view to be loaded and data to be set.
My question is this, is this design choice right? Or what approach would you chose to implement such a screen? Since we create a lot of viewControllers (one for every cell), does this affect memory or performance? And If I want to cache only some viewControllers or load them as the user scrolls or when its time to add it to the collectionView cell, how do I go about it as calculating the size of that cell depends upon the data and the views while laying out the CollectionView layout.
I think your approach is valid and from a theoretical standpoint it makes sense to separate a view from its data. But, in this case I would prefer to have the UICollectionViewCell subclass implement its own data, basically acting as a viewController. This will simplify your code a bit since you don't need the added viewControllers and the code that comes with them; instead you can simply add your tableView to the cell's contentView. This seems more natural to me, as then you can have HomeViewController's collectionView automatically handle calling its delegates when cells are moved offscreen and on, and during the loading process. You won't need to send messages to cells about who they are or when they should load, this will all be taken care of for you automatically. Also, I really don't think having the view lifecycle methods on each cell is valuable, and is less intuitive than having a cell load and refresh as part of a collectionView. Either way works, good luck!

Delay between viewWillDisappear and viewDidDissapear

I'm seeing some performance degradation in my application after some time and I'm trying to figure out what's going exactly.
I have a complex view controller (VC1) which contains scroll view, few table views inside, some custom cells with horizontal scrolling and custom drawing etc.
After several (around 10) refreshes of all these objects (reloading tables, reposition subviews etc) when I try to call presentViewController to push another view controller above VC1 I can see about 2 seconds delay between viewWillDisappear and viewDidDissapear
I tried to profile the app to see if there are memory leaks but couldn't find any. Memory usage grows when view refreshes and switches between different modes, but then it become more or less stable in around 30m.
Works fine in Simulator, but visible slower on iPhone5. And this slowness is visible only when I try to switch from that view controller.
I ran a profiler and recorded where these 2 seconds are spent. Here is link to trace file: https://dl.dropboxusercontent.com/u/6402890/trace.trace.zip
Majority of the time spent by UIKit doing layout as I can see.
What can I do to optimize it? Is there way to take may be a snapshot of a view and use it for "leaving view" animation and restore view hierarchy when we're coming back?
UPDATE: Adding screenshot for the profiler (click for full resolution):
UPDATE2:
After analyzing output from recursiveDescription I can see the following:
In the easiest case I have ~200 lines in the output. And performance is ok.
When I switch to more complex scenario hierarchy of views growth to ~500 lines, but still performs ok.
After multiple refreshes this number goes to ~2000 and this is where it become slow. Analyzing output with 2000 views I can see that ~1500 of them belong to hidden cells that are not even displayed in this mode anymore. When I'm refreshing table views cell types change too, and I'm utilizing different cells, but why the cells that are not used anymore are still being in subviews of table views?
Any recommendations?
From your stack, I suspect you've added a large number of views you didn't mean to add. Since it's related to reloads, I would check your reload logic and make sure it doesn't re-add all the views in your hierarchy without removing the previous views. You can write a quick debug routine use -recursiveDescription to recursively walk the -subviews of each view and print them out to see what's in the hierarchy.
It's possible that your issue is in the layer hierarchy rather than the view hierarchy, but the symptoms you describe make me think views.
EDIT: From your update, you probably have one of two things going on. Most likely, if these are actual UITableViewCells that shouldn't even exist anymore, then you have a retain loop somewhere. Alternately, your cellForRowAtIndexPath: may be incorrect and may be adding new views to an existing cell when it should just be reconfiguring the cell.
In either case, though, 200 views seems a lot of views for a "best case." You may be overusing views in places that you should be doing custom drawing. If the performance is ok, then… ok, but I'd test carefully on your slowest supported devices.
From the Instruments Time Profiler output, you can see that NSISEngine is eating up a ton of CPU. That class is responsible for doing the Auto Layout constraint evaluation and layout calculation.
So it looks like you are using Auto Layout, at least for some of the views.
Are you by any chance removing and re-adding constraints anywhere at runtime? I've seen this exact problem caused by that (can explain more if this is relevant).
If you aren't removing constraints, it sounds like you might have a fairly complex view hierarchy, and if you're using Auto Layout throughout, it's likely that you have a lot of constraints. As you may know, Auto Layout degrades in performance pretty quickly above a certain point due to super-linear time complexity of solving constraints. Check the output of po [[UIWindow keyWindow] recursiveDescription] from the debugger to see what your view hierarchy looks like.
I'm not sure what your view controller transition looks like, but you could try removing the disappearing view controller's view from its superview before doing the present. That should prevent it from doing layout calculations as it transitions. If that solves the performance issue, you could quickly snapshot the view hierarchy and then replace it with a single new UIImageView of the snapshot to display during the transition animation.
(One final thing: are any of your table views using Auto Layout in their cells? do any of these table views have more than ~20 cells?)
When presenting another controller your original controller has to be animated out of the window, which causes the view's frame to change and probably triggers all layoutSubviews methods and your manual adjustments.
You could try to avoid this by deactivating autoresizesSubviews in viewWillDisappear.
It is not very clean, but then probably all the calculations you are making may not be as well!
Try to optimize them:
Don't call layoutSubviews directly and call setNeedsLayout only if really needed.
Try to replace your manual resizing code with autoresizingMask or autoLayout.
Adjust views lazily and only if they are visible and if their size and not origin really has changed.
Don't reload your tables entirely but try to change only individual rows.
Try to use only a single table view.
Make sure to reuse cells.
The problem may have something to do with that you are placing UITableView instances inside a UIScrollView. That is explicitly prohibited by Apple's documentation for UIWebView (surprise!):
Important: You should not embed UIWebView or UITableView objects in UIScrollView objects. If you do so, unexpected behaviour can result because touch events for the two objects can be mixed up and wrongly handled.
I suspect that may also mess up table view cell reuse mechanism. Anyway, I'd also recommend to check if you are not 'leaking' any views at all. Keep in mind that even invisible views participate in layout if they are in the view hierarchy.
Edit: in response to Update 2
It's evident that cell reuse mechanism is not functioning properly. Try to make sure you are using correct instances of UITableView when dequeueing table view cells from reuse queue (check your data sources).

IOS: choice a UIScrollView or a UITableView

In my app I should create a view with a loto of informations;
these informations are divided in 4 section, every section can contain text, images lists ecc...
this is a brutal example...
Now I'm dubious to what type of solution to adopt.
In a my fast opinion a big scrollview is difficult to organize. And a big tableview with section is complicated to organize with code... What are your ideas?
UITableView is optimized for "reusable" cells, which is appropriate for scrolling in long lists.
Another benefit of using an UITableView, as others suggested, is that it only instantiate visible cells, so memory consumption is reduced.
In your case, since your content looks specific and non repetitive, I would suggest using a simple UIScrollView which is easier to use. (UITableView inherits from UIScrollView btw)
If memory/performance is an issue, then prefer UITableView or simply write your own logic to only instantiate views that are visible (by using scrollOffset for example)
EDIT:
On second thoughts, in your case, UICollectionView is surely a better candidate than UITableView.
Especially if you plan some day to do something like a 2 columns layout on iPad...
You should go with UITABLEVIEW, easy to manage easy to understand, more reusability and good memory management
If you have lots of content to scroll through, a UITableView might help you with keeping memory usage down.
When a cell scrolls out of sight, it gets removed from the view and kept around by the UITableView for later use (via -dequeueReusableCellWithIdentifier:). If you run low on memory then I believe that those invisible views (UITableViewCells) will get released. This basically means that your app will only keep views in memory that are actually visible. More will be cached, but can be purged any time if needed.
If you display a lot of data, and just add it all to a UIScrollView it will potentially use much more memory than if you used a UITableView. You might have to implement a similar mechanism to what UITableView does to remove (and potentially release) invisible views.
So, you can basically achieve the same effect, but a UITableView does a lot of that work for you already.
If you display lots of data (probably more than about two screens full) I'd lean towards using a UITableView.
This sort of thing is very easy to create in Interface Builder now with static cells; you can layout the entire interface visually and set up outlets for the cells (and/or their subviews) in order to configure the content in your view controller.

How can I make my UITableView request the X next cells that are outside the visible table?

I would like to have my UITableView load 3 or 4 cells outside of the table so that any data to be shown there is already loaded when I scroll down.
I have some images, and data that must be downloaded before it can be shown in the cell.
This causes a visible delay before the images are loaded when scrolling.
I can manually trigger loading of this data by doing it in the UITableViewDataSource tableView:cellForRowAtIndexPath: method. I've done this before, but I'm curious if there's an easier way to do it.
Is there any way to expand the reusable cell pool, or adjust how the cells are loaded/recycled?
EDIT:
To clarify, I have lazy loading of images and data in place.
Everything works fine, I just wanted an easy solution to the "prefetching" problem.
Which can be solved in many ways that has nothing to do with the view itself. Right now you can easily see the images load as you scroll. I just wanted them to load right before they become visible.
You may be looking for an asynchronous table view that loads the data asynchronously.
Apple provides a sample app demonstrating this:
LazyTableImages
Of course, you could pre-cache the data and begin downloading data into your datasource before they scroll.
The general idea is that you are loading data into a datasource (that is separate from the UI), so you can do this at any time (and in the background). You can display temporary data or some type of loading image or spinner if the data isn't loaded yet.
If data of the cell will be loaded when cel becomes visible, you can programmatically scroll the table view by scrolling to the bottom cell and go back to the first cell without animation. Another way would be creating all the cells and placing them into array when your view controller is created, and feed the table from that array that contains already created cells. I think there is no way to extend the cell pool as you are asking. Hope this helps, Good Luck!

Resources