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]
}
}
}
Related
I have a custom section header in my UITableView that contains A UICollectionView of some avatar images. The data is sourced once and cached for the images (images change so infrequently that it doesn't warrant real time updates).
I was hoping to make it so this header never redraws again even if the UITableView refresh is called. The reasoning for this is every time you take action, it causes the images to flicker as they're being redrawn from their default anonymous silhouette to the actual image of the person. The images are cached, but it doesn't matter because
I assume this is not possible by design - a UITableView will destroy everything and reload it all over again every time the refresh is called, correct?
I just wish I could hook into the refresh and preserve the section header, and reload the rows only.
Thanks for any ideas/guidance, I know this is a little uncommon but I don't want to move the header out into its own view because i'm using a UITableViewController directly, and it would be a real pain to have to embed it in a containerview and all that.
Yes, even you cached the image and set by local or something, it will take time to load. If you are using lazy loading, it will be splash ( for sure :) ). So, the only possible thing is, you have to update your tableView after refreshing is using tableView.beginUpdates and tableView.endUpdates. Input some add cell or remove cell base on the data after refreshing
I'm implementing chat feature in my app where I want to show more chat message when user pulls to refresh something similar to what we have on iMessage. I've been exploring various options but couldn't find any simple solution to implement this.
I've already implemented pull to refresh feature. Can anyone write steps or code to implement this feature either in pull to refresh function or using any table view delegate methods?
P.S. I don't want to use cocoapods or any 3rd party code.
Here is my pull to refresh function
// Pull to refresh
#objc func refresh(_ refreshControl: UIRefreshControl) {
fetchMessages(completed: {
self.messagesTable.reloadData()
})
refreshControl.endRefreshing()
}
Actually it's not pull to refresh in iMessage. It called lazy load. Lazy load means when you have a lot of data and you paginate it. So in order to get chat like table view with history:
Slice your chat database into small parts like 10-20 message per slice.
Load last slice in the table view.
In TableView...WillDisplayCellForRow... detect when the first row of table view will going to be displayed.
Load another slice and insert at the top of the table view
Think this as a paginated list of items. Let you fetch and show 100 item at a time. While the user request for more you fetch more 100 item and render it to your chat details page.
Now in general form you maintain an offset and limit variable which are type of Int. If you are familiar with offset and limit of a paginated api you can ignore the next section.
Say you first fetch 0-100th message in that case your offset would be 0 and limit would be 100. After the fetch operation completes your offset variable would be updated to 100. So next time you fetch, you fetch from 101-200th message and update the offset value to 200. This way you go ahead.
So you have to maintain a offset and limit. limit variable denote the number of item to fetch at once.
So whenever you pull to refresh the tableview, you fetch the items depending on offset & limit and populate the data items, then update the tableview.
Primarily to update the tableview just reload the tableview after populating the data arrays. If you want to only load the new items in the tableview take look at appledoc
func insertRows(at indexPaths: [IndexPath],
with animation: UITableView.RowAnimation)
Happy coding.
If the data set is too large and I want to show the data on a table View. What should be the best possible approach ?
First Approach-
I call the API in cellForRowAtIndexPath. This would make the request everytime I scroll which would mean hitting the service again and again. May be I can cache the data as well but still I think it is not the best possible approach.
Second Approach-
First, Save all the data locally and then show it. This would mean occupying more of the internal storage.
Which approach is better or is there any better approach available ?
What approach will you take depends on whether your API supports pagination or not! If the API does not support pagination then you left with no other solution then to retrieve every thing in one shot and show it in tableView.
If your API supports pagination, then you can make a call to load the first page in ViewDidLoad and then implement scrollViewDidScroll delegate to check when user scrolled to Bottom of the table/collectionView and trigger the web service to fetch next page of response. This will continue till server responds with empty array.
Here is a simple scrollViewDidScroll override which you can make use of to decide if user reached end of tableView/collectionView
extension ViewController : UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let bottomEdge = scrollView.contentSize.height + scrollView.contentInset.bottom - scrollView.bounds.height
if scrollView.contentOffset.y >= bottomEdge {
//make a webservice call now
}
}
}
Creating too many connections is not a good idea.
You should get all of them and show in a single page for few items.
But since there is too much data, you have 2 option. Combining them is better as a 3rd option.
Don’t show any data, implement a search bar, request data with search text. Now you requested a subset of the data according to search text.
Request a subset of data (first 20 for example) and imlement load more button (instead of button you can use scrollview’s delegate too) or pagination.
If response have image links, you should download the images in cellForRowAtIndexpath. If you start to download them before, user will wait too much.
I'm making a UITableView with comments. I have the latests comments on the bottom, and the older ones at the top.
I'm saving the comments in an NSMutableArray. When i reach the top i want to load more older comments. How can i do this?
You need a few methods:
Fetch Method -
This depends on how you are implementing the data retrieval. Say by default you only show the most recent 10 comments. If your array contains only these 10 comments, then you need a mechanism to perform a fetch of either a local or a remote database to pull another 10 additional comments and add them to the array. If though the array always contains all comments related to that post (say 56 comments in this case), the additional fetch is not necessary, though one could argue that retrieving and storing comments that users might never see is inefficient. Some math will need to be carried out in order to display the right amount of comments, but I will get to this in a little bit.
Updating the Table -
You will want to call [tableView reloadData] when the top cell is viewed because there will be more data to show. Depending on whether the array contains only the comments to be displayed or all comments related to the post, you may need to call the fetch method above as well. For example, if your array contains only 10 comments and you need to pull the additional 10, then you will first need to perform the fetch, and when the fetch completes you will need to call reloadData. If the array contains all 56 comments, then you will need to update the numCommentsToShow property as described below, and then immediately call reloadData
Update Trigger -
The method that reloads the table needs to be called when the top cell is viewed; however, you do not want to call the reload table right away when the view first appears. There are numerous ways of accomplishing this, and it really depends on if you want to additional comments to appear when the top cell is partially visible, fully visible, or even about to be viewed (which actually might be the cleanest for the user). One easy implementation (but not necessarily the most ideal) is to include the call to update the table in the cellForIndexPath method. If indexPath.row is 0 then call update. You will want to first test though that the view was not just loaded before allowing the update. You will also want the cell returned to the contain the comment that would have appeared before the update, because the cellForIndexPath method will be called again after the fetch. If you want the cell to be fully visible before allowing the update, you could do something like this: Best way to check if UITableViewCell is completely visible
The Math -
If the array only contains the comments to be shown, I would actually make the NSMutableArray an NSArray. When you get the results of the fetch, I would just hold those 20 comments in a completely new array, and have the comment array property just reference the new array. Then call reloadData. Otherwise, adding the comments to an NSMutableArray while the user is scrolling sounds a bit dangerous to me, but I could be wrong. Btw, some people might actually only perform the fetch on the 10 next comments (i.e. 11-20), but I would retrieve 1-20 if you are allows the users to edit their comments since the most recent comments are the ones most likely to change. After the fetch, the array will contain the 20 comments, and they should be in order of least to most recent. This makes it easy for the cellForIndexPath method, which should just retrieve the comment at index indexPath.row for each cell. The cell currently being viewed (the same one before the update) would be at the index equal to the number of new items just pulled.
If the array contains all comments related to the post (56 in my example), then you need to have a property that contains the number of comments to be displayed at this given point in time. When the view first appears, this property is set to some default number (10 in my example). The numberOfRowsInSection method needs to return this property which I will call numCommentsToShow. However, the cellForIndexPath needs to retrieve the correct items in the array. If they sorted from least recent to most recent, you actually need to retrieve the comment at the index equal to the number of comments in the array less numCommentsToShow plus indexPath.row. It gets a bit confusing doing it this way. When you call the update, you need to increase numCommentsToShow by some incremental amount (in my example) 10, but you should be careful to not increase it to greater than the number of items in the array. So you should actually increase it by the minimum of the incremental amount and the amount of comments not yet shown.
Issues with Scrolling
You probably will encounter some issues while scrolling since the cell at the top of the table will actually become one of the middle cells of the table. Thus, the table will likely jump to the top after the update. There is some math that you can do to ensure that the scroll position of the table after the update is set so that it appears to the user that the position of the scroll has not changed, when in reality it has. And there is an even more difficult challenge that will occur if the user moves the table down while the table has not yet updated.
Before explaining how to do this, the trend these days seems to be avoiding this situation. If you look at the facebook app, there is never really a infinite scroll to the top. There is a button to load the newer posts that automatically scrolls to the top. And comments on posts start at the top with the least recent comment and you can click a button to "view more comments" to see more recent ones. Not to say that this is good user experience, but programming the fix might be more time confusing than it's worth.
But anyway, to get around the scroll issue, I would use the setContentOffset method since a UITableView inherits from UIScrollView. You should not use the scrollToRowAtIndexPath method of a UITableView since it is possible that the current scroll position might not be completely lined up with any given row. You should make sure that the animated parameter is set to NO. The exact position though that you want to scroll to involves a bit of math and timing. It is possible that the user has scrolled to a new position while the data was being fetched, so you have offset the current position, not whatever the position was before the fetch. When the fetch is complete and you reload the table, you need to immediately call setContentOffset. The new y position needs to be the current scroll y position + to the number of new comments to show * the row height.
There is a bit of a problem though. It is possible that the view will be in the middle of scrolling when the fetch is complete. Calling setContentOffset at this point will probably cause a jump or a stall in the scrolling. There are a few ways of handling this case. If the user has scrolled away from the top of the table, then perhaps he/she might not want to view those records anymore. So perhaps you ignore the data you fetched or save it in another place without including the results in the table. Another is to wait until the table is finished scrolling before using the new data. The third way to continue scrolling with the same velocity and deceleration as you were before the table was reloaded, but I am not sure how you would accomplish this offhand.
To me this is more pain than it's worth.
Hope this helps,
Here is my solution in Xamarin, with code to handle the scroll issue. My header is a UIActivityIndicator, which is removed when there is no more content to load. DidScroll is an event which is fired in the Scrolled method of the UITableViewSource. DidScrollEventArgs contains the UIScrollView passed from the Scrolled method.
So in the UITableViewSource:
public class DidScrollEventArgs : EventArgs
{
public UIScrollView scrollView;
public DidScrollEventArgs (UIScrollView scrollView) : base ()
{
this.scrollView = scrollView;
}
}
public event EventHandler<DidScrollEventArgs> DidScroll;
public override void Scrolled (UIScrollView scrollView)
{
if (DidScroll != null)
DidScroll.Invoke (this, new DidScrollEventArgs (scrollView));
}
And in my UIViewController:
source.DidScroll += async (object sender, MessageSource.DidScrollEventArgs e) => {
nfloat height = e.scrollView.ContentSize.Height;
nfloat contentYoffset = e.scrollView.ContentOffset.Y;
if (contentYoffset == 0 && source.model.Count > 0 && height > MyBounds.Height && !isLoading && isMore) {
isLoading = true;
DateTime cutOff = source.model[0].SentDate.Value;
//fetch more content
List<MessageModel> newModels = await MessageAccess.GetMessagesAsync(ThreadId, cutOff, true);
if (newModels.Count == 0){
isMore = false;
tableView.TableHeaderView = null;
isLoading = false;
return;
}
//add it on to the front of the current content
models = newModels.Concat(source.model).ToList();
source.model = models;
tableView.ReloadData();
//calculate where to scroll to (new height - old height)
nfloat yOffset = tableView.ContentSize.Height - height;
//scroll there
tableView.SetContentOffset(new CGPoint(0, yOffset), false);
isLoading = false;
}
};
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.