I've got an app that displays information from our web service. The user has the option to add rows to a table view to display more information. I'm using:
[self.tableView insertSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
to insert the row. It gives a nice bit of animation so the user knows where the row is they just inserted. My problem is that the row being added has to be in view in order for the animation to be seen. So I scroll down so that the row to be added is in view:
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
then start the ASIHTTPRequest asynchronously to get the info for the new row.
If there is a decent amount of data, the animation shows great. Problem is, if the user is on WiFi or not pulling much info, the ASIHTTPRequest returns before scroll completes and the new row is just there (the user doesn't see it get added). If I force a delay (.03 seconds) between the call to scroll the Table View and start the request, I get the desired effect, but then there is a longer delay than necessary on the loads that were already being displayed as desired.
I thought I would try to save the time, in ms, from when the view was scrolled and the request finished, and make sure it was longer that .03 seconds, but I kept getting an error with
[NSDate timeIntervalSinceReferenceDate]
so I figured I'd ask here if there was a better way before I hacked into that solution more.
Thanks!
Using a combination of timers and a couple flags will help you out, which is basically what you are already doing.
Related
As it's clear from the title, I have a scrolling performance issue in a table view.
Since I have read nearly every question that is posted online in this regard, and I assume all of you have much more experience with UITableView and its techniques, I won't bother with general stuff, and I just wanna point out some key things in my code that may help you help me spot where I'm doing wrong.
The UI in each cell is very very basic, so rendering each doesn't take considerable time. No shadows, no rounded corners, no extra effect, nothing. Just a few labels and two images, that's all.
The datasource is an NSArray which is already fetched from CoreData. The data of the labels are set from the content of the array, without much calculations or process required.
The height is each cell is a static integer, so the tableView:HeightForRowAtIndexPath: will immediately return the result as fast as possible. No calculations required.
The tableView:CellForRowAtIndexPath: dequeues and reuses cell with reusable identifiers so any re-creation is avoided.
So far everything is perfectly smooth. The issue is where items in Core Data are fetched from a server (Which is extremely fast) as user scrolls down. Data binding is done inside tableView:willDisplayCell:atIndexPath: to prevent tableView:CellForRowAtIndexPath: from becoming slow, as data needs to be loaded just before the cell goes live on the screen. I also fetch new items from server inside this method whenever there're some cells remaining till the last item fetched. So for example when there are totally 50 cells data fetched and put in the CoreData already and this method is called for cell number let's say 40, I request another 50 cell data from server, so that it will be ready whenever user reaches the end of the table.
As I expect this should only be called for the cells that go live on the screen. But putting some NSLogs shows that it is called multiple times until next 200 cells data are fetched (I guess the amount changes depending on device or simulator and the memory available on them and also OS limits). For example, I'm testing on an iPhone 7+, and I start the app and I go the page in which the table is. It fetches first 50 items and only first 4 items are shown on the screen, But I see that tableView:willDisplayCell:atIndexPath: is also called for cell #25, so another 50 is fetched immediately, and then it is called for cell #75, so another 50 is fetched, and this goes on for like first 200-300 cells, and then when fetching is stopped, scrolling is extremely fast and optimized until next 200-300 cells are fetched.
What can I do? Shouldn't tableView:willDisplayCell:atIndexPath: fire whenever a cell is about to be displayed? Where else should I fetch data as user scrolls?
Any ideas or suggestions is REALLY and GREATLY appreciated.
How to append 20 data in every one go when I pull to refresh the table in ios.first time load 20 data then on pulltorefresh add 20 more data and so on.
That's not exactly how "pull to refresh" is supposed to work. It's supposed to actually refresh the data being displayed, and not be a replacement for "load more".
In order to achieve what you originally asked, all you need to do is keep an indication of how many elements you have loaded so far and every time the user pulls to refresh, just add the next 20 to the array and reload the table data.
However, what you should be doing, is implement "pull to refresh" just the way it was intended to be used, and add another logic for "load more" which will be called whenever the user scrolls all the way to the bottom of the table view. There, you can load 20 more elements and display them to the user.
The easiest way is to use already build component. You can try to use SVPullToRefresh. This repo contains both pull to refresh and infinite scroll.
Pull to refresh is used to reload existing data, or reset all data and pull only latest.
Infinite scroll is used to append data as you scroll.
If you need only infinite scroll then you can go for this one - UIScrollView-InfiniteScroll.
If you are interested and would like to have a look at more difficult pull to refresh (can be done something similar in infinite scroll as well). Then the best thing to look at would be, probably, CBStoreHouseRefreshControl.
- (void) scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == scrollObj) {
CGFloat scrollPosition = scrollObj.contentSize.height - scrollObj.frame.size.height - scrollObj.contentOffset.y;
if (scrollPosition < 30)// you can set your value
{
//code here you want to do when refresh done
//Suppose you have 20 record in arryTemp
//arryTemp is used to populate data into table
for(int i=0;i<arryTemp.count;i++){
arryTemp =[aryyTemp addObject(arryTemp objectAtIndex(i)];
}
[tableView reloadData]
}
}
}
I have somewhat of an odd problem. I've already got a UITableView setup with custom heights for my table cells. The problem comes in when the content is loaded in the background for the cells. I need the cells to load asap, but theres a big image for each cell, that may take some time to load (from the network or flash), so the load is started in the background when the cell is first loaded. This means that the height for the cell is initially wrong, and needs to be updated later with a minimum of glitching. The problem I'm having is that it doesn't always want to update the table, or the image, and can cause some excess scrolling.
It's also incredibly slow. I have some optimizations I need to do to make it a bit less slow (make smaller copies of the main image, and cache the height for the cells temporarily).
I've tried all sorts of things. I make sure the table is told to update in the main thread, I've tried a bare set of beginUpdates/endUpdates, reloadRows/reloadSections (table is in grouped mode, with one row per section). I have yet to get the table to update properly once the image has finished loading.
If anyone has some insight into how to get this to work as smoothly as possible I'd appreciate it.
The place to go is your heightForRowAtIndex: method. It needs to know if the image exists or not and return a value accordingly. Every time each row finishes loading it's image, you should call reloadRows~ method like this:
NSArray *indexPaths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:x inSection:y]];
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
I'm not entirely sure if this will cause some heavy scrolling on your tableView, but it's an option.
Another option is to update in chunks. Say every 5-10 images (depending on how many you have) that get loaded reload their respective rows with that same method.
In my app,i am using tableviews, populated through XML parsing, i get my XML from an ftp, the XML contains other xml urls for the next views and images urls and text, the problem is when an element of the tableview is selected it pushes to another view, But load for a very long time.
I run my app into the simulator, i never tested it on a device.
does anybody knows what may be the problem?
Is it a connection issue?
will it work fine on a device?
I would suggest testing your code on a device and not making assumptions about the peformace. I assume you are going to the server and ask for those images to display in the pushed view. In which case you should load the view with image placeholders and spin off the downloading images in a secondary thread (good habit for slow loading as to not block the UI) and update the image placeholders as the image data become available.
I will gladly update this answer if you need more specific help.
If you are loading a tableview in your pushed view, the hang will happen on the view before that pushed view (that is very terrible UX). So move the logic of the code that take a while into the subclass of the "pushed view", you can implement something like a spinner (I use MBProgressHud) while you crunch you data. This way you will get the view loaded before the lag begins.
Now the flow to improve UX can be as follows:
1. In the - (void)viewDidLoadof the pushed view you can hide the tableview (or not).
[self.myTable setHidden:YES];
2. In the -(void)viewDidAppear:(BOOL)animated of the pushed view throw up a spinner for the user to know you are doing work (work here being going to the server to look for the images from the urls) and do the work.
//your code here will vary
3. When you know that all your data is downloaded just load the table, hide the spinner and show the table. You can do this at the end of the -(void)viewDidAppear:(BOOL)animated method.
[MBProgressHUD hideHUDForView:self.view animated:YES];
[myTable reloadData];
I recommend sending all the expensive work into a secondary thread, because a user would hate to decide not to wait and have a blocked back button in the navigation bar. But for to answer you original question you pushed view should load immediately, but then wait to get the data.
I'm trying to implement a table view which should display cells according to the value of a UISlider.
Example: The table is empty. The user quickly drags the slider up to a value of 400. Now the table should contain 400 rows.
The data the cells should show is stored in core data and has about 400 entries. We're trying to achieve a cool effect when dragging the slider, so you can visually see each cell being added (if you're viewing the part of the table view where the cell would end up, of course). The built in animations supplied when using - (void) reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation are sufficient, and the effect looks cool when going between 1 to 10 rows.
The problem: After the value has reached 10 or more it starts to lag too much to work. I think it's because reloadSections: has to check every cell every time the slider changes value and I loose too much precious time.
So, I'm looking for ideas on how to implement this. Should I use an NSFetchedResultsController and change the fetch request every time the slider changes value (which could happen 20 times per second)? Should I have all the data loaded into an array and just reuse cells the standard way?
I've tried both ways and the lag is pretty much the same. I'm thinking the problem might be that reloadSection: is too slow. I'm thinking that - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation would be a better choice, but I'm looking for more ideas on how to implement this.
Any help would be appreciated!
The last code I tested when the slider changes value:
- (IBAction) sliderChangedValue:(UISlider *)slider
{
// Make sure we only run this function on integer changes. It fires too often on float changes.
if ((int)slider.value == lastSliderIntegerValue) {
return;
}
if (slider.value > lastSliderIntegerValue) {
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:(int)slider.value inSection:0]]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
else {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:(int)slider.value inSection:0]]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
lastSliderIntegerValue = (int)slider.value;
I'm not incredibly experienced with iOS development, but I have done a lot of game dev. It seems like the root of your problem is the amount of animation that your program has to render. Since you said that your motivation for this program is preserving the cool effect, then you should change everything possible to optimize your program up to the point the user can perceive. I understand that isn't very clear, so here's a possible solution:
You shouldn't be animating the addition of every single row for every speed at which the user can move the slider. Instead, only animate as many as you need to for the experience to be smooth. This can be accomplished by calculating the rate at which the slider is being changed, and then adjusting how many rows are added to the table before reloadData is called. Only animate the addition of the last row in the cycle.
Because I don't know what system you're developing for or really much at all about the graphical power of the various iOS platforms, I can't really give you specific values, but you should play around with different settings and see what works.