i got a UITableview with sections.
The sections are handled by Core Data's NSFetchResultsController, however this is less important concerning my problem.
I'd like to have a predefined amount of rows in my UITableview. Let say i start the application, i'd like to see only 10 rows, even if i got 14 entries in Core Data. When i click a button "load more.." the next 10 rows should be loaded (loading is implemented i just need to figure out a way to display the right amount of rows). If i got 10 rows at the beginning and in the first section are 7 and in the second are 5 then the last two rows of the second section should not be display until i click the load more button to show the next 10. So i think i have to implement this logic in numberOfRowsInSection and numberOfSectionsInTableView but i need a hint from you how to solve this...
Basically i would need a loop which loops through the sections. If the amount of rows i want to display is reached i return all sections until this point to be displayed. Then i would need to retrieve all the rows of this sections and the rows of the sections before to calculate the numberOfRowsInSection for each section...
Anyway somebody who can help me implement this ?
Thanks in advance
Perhaps a good way to solve this is to use the fetched results controller. You can try setting the fetchLimit on the fetched results controller's fetchRequest. If the sorting of your FRC is correct, it should retrieve the first n objects.
-(void)loadRows:(NSUInteger)numberOfRows {
NSFetchRequest *request = self.fetchedResultsController.fetchRequest;
request.fetchLimit = numberOfRows;
NSError *error;
[self.fetchedResultsController executeFetchRequest:request error:&error];
}
Related
First off I sincerely apologize if this has been answered. I have been battling with this for too long. I would appreciate any kind of help.
I am developing an application that gets data asynchronously from different web sources and parses it into objects that are handled by my main Model. This model provides information for a UITableView. When data is received and parsed, i need to modify the number of sections and rows in the table view. I first receive the data that decides the number of sections, and then asynchronously data that modifies the number of rows in each section and their content. I have the table views data sources set up.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [self.sijainti.pysakit count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
//if ([[[self.sijainti.pysakit objectAtIndex:section] ajatStr] count] == 0) return 1;
NSLog(#"ajatStr of section: %d count: %d",section, [[[self.sijainti.pysakit objectAtIndex:section] ajatStr] count]);
return [[[self.sijainti.pysakit objectAtIndex:section] ajatStr] count];
}
The object names are in Finnish (long story). The second line of numberOfRowsInSection was commented to ease debugging. Now, because the data is dynamic i need to update the interface when i have data ready for display, or the user wont know about it until they scroll away and back again. I have a method for this:
- (void)updateRowWithCell:(SCPysakki *)valmisPysakki {
if (!valmisPysakki.isInArray) return;
int section = [self.sijainti.pysakit indexOfObject:valmisPysakki];
[self.tableView beginUpdates];
NSIndexSet *sectionSet = [NSIndexSet indexSetWithIndex:section];
[self.tableView reloadSections:sectionSet withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
}
This is called only after the number of sections is known. I do NOT want to use reloadData due to efficiency, and animation. I used to have this set up so that i had only rows and was just drawing rows inside of the rows. But once the rows became taller than the iphone screen, it became very unclear, because you couldn't see the title. So i made them sections, and the rows drawn real rows. Then i was suddenly faced with a problem. reloadRowsAtIndexPaths: withRowAnimation: worked absolutely fine but using reloadSections: withRowAnimation: gave me this exception:
* Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1914.84/UITableView.m:1037
2012-09-17 04:11:27.769 pysappi[2351:f803] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
I was able to eliminate this by adding the [self.tableView endUpdate] and [self.tableView endUpdate] to the update method. But oh no, that only works if I:
Use reloadData after receiving the data deciding the number of sections (which in turn shortens the tableview so that after the whole update the user at the top of the table after every single update. I would rather update the sections dynamically, without using reloadData, except when removing Sections, but if I try to do this i get the exception again.)
update the sections after receiving data for only one Row (so if I try to update all the rows in the section once I've received all their data, I get the exception again.)
Now this creates a situation where creating a good user interface becomes impossible. I'm convinced that the underlying problem is not solved with the start and end updating calls, but that just fixes it for some very very odd reason.
I also tried adding the rows manually with insertRowsAtIndexPaths: and updating them manually also, but this returns the exact same error. During a long debugging progress i have been able to determine that this exception rises after cellForRowAtIndexPath: gets called and it returns a cell for one of the new rows that was added during the updating, even though the numberOfRowsInSection should have updated the amount of rows. I also suppose that if those few circumstances apply this is why the start and end updating methods make the row updating work.
The only thing that has worked outside of what i explained above, is using only reloadData, but that is just practically impossible considering the amount of updating.
This became a lot longer than i thought, and its 4.30 AM. Not the first time I'm up this late thanks to this exception. If you need more information ask. Thank you in advance.
(Answered in the comments. Converted to a community wiki answer. See Question with no answers, but issue solved in the comments (or extended in chat) )
#deleted_user wrote:
number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), - you had a section with zero rows and did an update with a row - cocoa didnt see that as an insert and failed you
I've been trying to work out how to use table views and I'm a little stuck if I'm honest. I wanted to use a tableview with a limited number of rows (say 50 max). It starts of empty, with 0 rows. Then I wanted to do something along the lines of:
[self logMessage:#"Waiting for response"];
Which inserts a new row at the bottom with the above text. If I do another call to this pseudo function:
[self logMessage:#"Server response received"];
It should insert yet another new row below the previous row, and ensure it is visible. Once the above limit of 50 is reached, and a new message is inserted, I wanted the oldest message to be removed. All of this would be scrollable, with the latest being visible by default.
Am I looking at the right thing to do this? Eventually, I was hoping to have this in a nice little drawer below the main window, which I can then toggle from the main menu if needed. But as I said, I can't work out how to use a table view properly, it doesn't seem to be as straight forward as other objects are.
Any example code would be greatly appreciated!
Since log viewer is a read-only application of a UITableView, the way you do it is rather straightforward once you understand the basics. Recall that table views rely on their data models to provide them with the correct information that needs to be displayed.
A data model for "the last fifty lines of log" could be as simple as an NSMutableArray: use insertObject:atIndex: to add lines, and removeLastObject to remove the "overflow" lines, like this:
NSMutableArray *logLines = [NSMutableArray array]; // <<== this goes into the init method
-(void) addLogLine:(NSString*)line {
[logLines insertObject:line atIndex:0];
while (logLines.count > 50) {
[logLines removeLastObject];
}
}
Now you can use logLines as your table's "model": the data provider can tell how many lines there are by looking at logLines.count; the content of each row in the table will be the object at the corresponding index in logLines, and so on. Take a look at the UITableView section of your favorite iOs tutorial for the "boilerplate code" that needs to be written in order to display array elements in a UITableView.
I already asked a similar question but still can't get it to work. I have a core data database with 30k+ records. I am trying to fetch it into a tableview. I watched the WWDC presentation on core data and whatever the presenter suggested it didn't work for me. I deleted most of the fields from that table and only left like 4 which contains nsstring and date. I used setFetchBatchSize with different numbers and still when using instruments the fetch controller was fetching all 30k records. I watched it over and still not sure what i am doing from to lower the fetch. I used a separate thread and showed svprogreshud but that made it even slower i guess because the thread is not running right away.
Also The data is divided 12 sections and i have a field in core database for it with one character to set up sections.
I also set -com.apple.CoreData.SQLDebug 1 and when running on the device it showed 3sec loading that fetch request with all the records but showed
CoreData: sql: pragma cache_size=200
CoreData: annotation: total fetch execution time: 3.1087s for 36684 rows.
Here is some of the code
-(void)useDocument{
if (self.peopleDatabase.documentState == UIDocumentStateClosed){
[self.peopleDatabase openWithCompletionHandler:^(BOOL success){
[self setupFetchResultsController];
}];
} else if (self.peopleDatabase.documentState == UIDocumentStateNormal){
[self setupFetchResultsController];
}
}
-(void)setupFetchResultsController
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"people"];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"pplicon" ascending:YES]];
[NSFetchedResultsController deleteCacheWithName:nil];
self.fetchedResultsController = nil;
[request setFetchBatchSize:50];
//[request setFetchLimit:100];
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:request managedObjectContext:self.peopleDatabase.managedObjectContext sectionNameKeyPath:#"pplicon" cacheName:#"peopleCache"];
}
Thank you in advance!
UPDATE
Jody thank you again for answering.
I went to the core data setup to the table and pplicon indexed is checked. Do i need to check anything else. I followed the directions for the wwdc video and ran instruments in core data fetches it shows NSManagedObjectContext executefetch .. Fetch Count 36684 and then another one with Fetch Count 12 Also in sampler the most time spent in peopleviewcontroller setupfetchResultsContoroller 363.75ms and UIAplication Run 287.6ms. In activity Monitor the app takes up 91mb real memory. Is there anyway to improve this time? What else can i check and see where the hang up is?
As far as the sections. I have a character in the database pplicon which used to show for section and the data is sort by it.
Data Model for now has one Entity with 5 Attribues as string and 1 Attribute as Date
I think I have commented on a very similar question in the past. You absolutely must run Instruments and see what it says. It will tell you exactly where it is spending its time.
You are using sections, which can be expensive. Is your section attribute "pplicon" setup in the database as an Index? If not, you will most definitely have to scan all items in the database. The only chance you have of not scanning all records is to make it an index. The internals may be able to just use an index table to get the unique index values. However, it still has to return section objects.
Again, you must turn on instruments while testing so you can see exactly where you are spending your time.
Are section names computed? When section names are computed, Core Data automatically fetches all of them and invoke section name to get sorting right.
I have a tabbed application, that has 2 tabs with 2 UITableView.
I also have 2 NSFetchedResultsController type objects, but both of them are on the same entity with different ordering and different fetch limit.
When I download more objects from the internet and insert them to the database, my NSFetchedResultsController type objects will ignore the fetchLimit. For the first one I set a fetchLimit of 10 and for the second I set a fetchLimit of 50. Initially I have 10 objects in the database. Everything is fine. After I download more 40 objects the first one also loads the more 40 objects, but it has a fetchLimit of 10.
What's wrong with this?
NSFetchedResultsController ignoring fetchLimit in case if it observers context changes.
I think that it's not so simple operation to correctly update table via momc observation, when you're restricted to fetchlimit.
SOLUTION #1
So, in case if big update has been occured, you should re-fetch data.
So you should do something like this in FRC delegate:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
if (bigChangesPeformed) {
NSError * error;
// Re-fetching to get correct fetch limit
[self.fetchedResultsController performFetch:&error];
if (error) {
// bla-bla-bla
}
[self.tableView reloadData];
}
}
The fetch limit of NSFetchRequest is the limit used when performing fetches from the data store. It's not intended to limit the size of the fetched results array. If you want to limit the total number of cells displayed in your tableview, you should probably use UITableView's tableView:numberRowsForSection: to limit it.
If you really want to set a limit of only 10 items displayed (like showing a Top 10 list), there's probably no real reason to use more than 1 section either. If you restrict the number of sections in your tableView to 1 using numberOfSectionsInTableView: and the number of rows in that section AT MOST 10 with tableView:numberRowsForSection:, your table will always show up to 10 records, regardless of how many records are added into the data store.
You can use sort descriptors to order the objects managed by your NSFetchedResultsController instance, which I imagine is useful for displaying a Top 10 list.
I'm trying to create a UITableView with dates, shouldn't be very exciting, I know. It starts around the current date, but the user should be able to scroll down (the future) and up (the past) as far as he/she wants. This results in a potentially infinite number of rows. So what's the way to go about creating this?
Returning NSIntegerMax as the number of rows already crashes the app, but even if it wouldn't, that still doesn't account for being able to scroll up. I could start half way of course, but eventually there's a maximum.
Any ideas how to do or fake this? Can I update/reload the table somehow without the user noticing, so I never run into a border?
SOLUTION:
I went with #ender's suggestion and made a table with a fixed amount of cells. But instead of reloading it when the user scrolls to near the edges of the fixed cells, I went with reloading the table when the scrolling grinds to a halt. To accomodate with a user scrolling great distances without stopping, I just increased the row count to 1000, putting the ROW_CENTER constant to 500. This is the method that takes care of updating the rows.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
NSArray *visible = [self.tableView indexPathsForVisibleRows];
NSIndexPath *upper = [visible objectAtIndex:0];
NSIndexPath *lower = [visible lastObject];
// adjust the table to compensate for the up- or downward scrolling
NSInteger upperDiff = ROW_CENTER - upper.row;
NSInteger lowerDiff = lower.row - ROW_CENTER;
// the greater difference marks the direction we need to compensate
NSInteger magnitude = (lowerDiff > upperDiff) ? lowerDiff : -upperDiff;
self.offset += magnitude;
CGFloat height = [self tableView:self.tableView heightForRowAtIndexPath:lower];
CGPoint current = self.tableView.contentOffset;
current.y -= magnitude * height;
[self.tableView setContentOffset:current animated:NO];
NSIndexPath *selection = [self.tableView indexPathForSelectedRow];
[self.tableView reloadData];
if (selection)
{
// reselect a prior selected cell after the reload.
selection = [NSIndexPath indexPathForRow:selection.row - magnitude inSection:selection.section];
[self.tableView selectRowAtIndexPath:selection animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}
The magic breaks when a user scrolls to the edge of the table without stopping, but with the table view bounces property disabled, this merely feels like a minor glitch, yet totally acceptable. As always, thanks StackOverflow!
You should establish a fixed number of cells and adjust your datasource when the user scrolls near the end of the tableview. For example, you have an array with 51 dates (today, 25 future and 25 past). When the app tries to render a cell near one of the borders, reconfigure your array and call reloadData
You might also have a look at the "Advanced Scroll View Techniques" talk of the WWDC 2011. They showed how you would create a UIScrollView which scrolls indefinitely. It starts at about 5 mins. in.
Two thoughts:
Do you really need a UITableView? You could use a UIScrollView, three screens high. If end of scrolling is reached, layout your content and adjust scrolling position. This gives the illusion of infinite scrolling. Creating some date labels and arranging them in layoutSubviews: should not be too much of an effort.
If you really want to stick to UITableView you could think about having two UITableViews. If scrolling in your first one reaches a critical point, spin off a thread and populate the second one. Then at some point, exchange the views and trigger the scrolling manually so that the user does not note the change. This is just some idea from the top of my head. I have not implemented something like this yet, but I implemented the infinite UIScrollView.
I answered this question in another post: https://stackoverflow.com/a/15305937/2030823
Include:
https://github.com/samvermette/SVPullToRefresh
SVPullToRefresh handles the logic when UITableView reaches the bottom. A spinner is shown automatically and a callback block is fired. You add in your business logic to the callback block.
#import "UIScrollView+SVInfiniteScrolling.h"
// ...
[tableView addInfiniteScrollingWithActionHandler:^{
// append data to data source, insert new cells at the end of table view
// call [tableView.infiniteScrollingView stopAnimating] when done
}];
This question has already been asked: implementing a cyclic UITableView
I'm copying that answer here to make it easier because the asker hasn't ticked my answer.
UITableView is same as UIScrollView in scrollViewDidScroll method.
So, its easy to emulate infinite scrolling.
double the array so that head and tail are joined together to emulate circular table
use my following code to make user switch between 1st part of doubled table and 2nd part of doubled table when they tend to reach the start or the end of the table.
:
/* To emulate infinite scrolling...
The table data was doubled to join the head and tail: (suppose table had 1,2,3,4)
1 2 3 4|1 2 3 4 (actual data doubled)
---------------
1 2 3 4 5 6 7 8 (visualizing joined table in eight parts)
When the user scrolls backwards to 1/8th of the joined table, user is actually at the 1/4th of actual data, so we scroll instantly (we take user) to the 5/8th of the joined table where the cells are exactly the same.
Similarly, when user scrolls to 6/8th of the table, we will scroll back to 2/8th where the cells are same. (I'm using 6/8th when 7/8th sound more logical because 6/8th is good for small tables.)
In simple words, when user reaches 1/4th of the first half of table, we scroll to 1/4th of the second half, when he reaches 2/4th of the second half of table, we scroll to the 2/4 of first half. This is done simply by subtracting OR adding half the length of the new/joined table.
Written and posted by Anup Kattel. Feel free to use this code. Please keep these comments if you don't mind.
*/
-(void)scrollViewDidScroll:(UIScrollView *)scrollView_
{
CGFloat currentOffsetX = scrollView_.contentOffset.x;
CGFloat currentOffSetY = scrollView_.contentOffset.y;
CGFloat contentHeight = scrollView_.contentSize.height;
if (currentOffSetY < (contentHeight / 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY + (contentHeight/2)));
}
if (currentOffSetY > ((contentHeight * 6)/ 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY - (contentHeight/2)));
}
}
P.S. - I've used this code on one of my apps called NT Time Table (Lite). If you want the preview, you can check out the app: https://itunes.apple.com/au/app/nt-time-table-lite/id528213278?mt=8
If your table can sometimes be too short, at the beginning of the above method you can add a if logic to exit when data count is say for example less than 9.