Better way to count uncompleted reminders & update table view cell? - ios

I'm using this to count all the uncompleted reminders in the defult list and put it in the detail of the table view cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
NSPredicate *predicate2 = [self.reminderStore predicateForIncompleteRemindersWithDueDateStarting:nil ending:nil calendars:#[[self.reminderStore defaultCalendarForNewReminders]]];
[self.reminderStore fetchRemindersMatchingPredicate:predicate2 completion:^(NSArray *reminders)
{
cell.detailTextLabel.text = [NSString stringWithFormat:#"%d", [reminders count]];
NSLog(#"count: %d", [reminders count]);
}];
...
return cell;
}
The problem is that because it's on another thread, it's not updating the table cell until it needs to reload it. (it does prints in the log). What's the best way of fixing that, so it would load automatically and won't need to load again every time? Also, is there another way to faster count reminders?

Related

*** Assertion failure in -[UITableView _endCellAnimationsWithContext:]

I'm working on an iOS application and have a UITableView containing text messages (a table cell for each message). When a new message arrives a new cell is added in the UITableView. At first I used a single UITableView section and added all cells under it.
All worked fine until I decided to change the scheme and use multiple sections with one row each (it was the cleanest way I found to enforce a space between the cells). The problem I have with this scheme is that every time I try to add a new cell I get this exception:
2015-09-30 11:48:35.659 restcomm-messenger[15069:489766] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3505.16/UITableView.m:1702
And I get it when [self.tableView endUpdates] is executed (see below for full code)
I have checked many SO answers in similar questions but I think I have done everything right. Here are the 'interesting' code sections:
New message arrives, update backing store and the insert row to table:
...
// update the backing store
[self.messages addObject:[NSDictionary dictionaryWithObjectsAndKeys:type, #"type", msg, #"text", nil]];
[self.tableView beginUpdates];
// trigger the new table row creation
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:[self.messages count] - 1]]
withRowAnimation:animation];
[self.tableView endUpdates];
...
Section and row count callbacks:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [self.messages count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
Fill cell callback:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *type = [[self.messages objectAtIndex:indexPath.section] objectForKey:#"type"];
if ([type isEqualToString:#"local"]) {
LocalMessageTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"local-message-reuse-identifier" forIndexPath:indexPath];
cell.senderText.text = [[self.messages objectAtIndex:indexPath.section] objectForKey:#"text"];
return cell;
}
else {
RemoteMessageTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"remote-message-reuse-identifier" forIndexPath:indexPath];
cell.senderText.text = [[self.messages objectAtIndex:indexPath.section] objectForKey:#"text"];
return cell;
}
}
I would expect that to work. Remember that for the original scheme (1 section may rows), it works just fine :(
If you want more details, here's the full code for the related Table View Controller: https://github.com/Mobicents/restcomm-ios-sdk/blob/master/Lab/restcomm-messenger-debug/restcomm-messenger/MessageTableViewController.m
Any hints are welcome
Antonis

If statement to display cell

my question is, if it is possible to display a cell only if there is data present for a specific post. I'm thinking something along the line of this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object {
PFQuery *query = [PFQuery queryWithClassName:#"Post"];
if(![query includeKey:#"Photo"]) {
UITableViewCell *cell = [self tableView:tableView cellForNextPageAtIndexPath:indexPath];
return cell;
}
static NSString *CellIdentifier = #"PhotoCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
PFImageView *photo = (PFImageView *)[cell viewWithTag:1];
photo.file = object[#"image"];
[photo loadInBackground];
return cell;
}
I've tried this but i can not get it to work every which way try, any help will be apreciated, thank you in advanced.
Tables need a model, and that model is almost always an array. The datasource protocol's job is to ask about that array.
So what's in your array? Sometimes nothing, because you haven't fetched the data for it yet, or because the user has no data that belongs there. From your question, it sounds like sometimes your array has 1 element -- something representing a post or an image -- and sometimes your array has 2 elements -- something representing a post and an image.
Thinking this way lets you divide your work into two parts: (1) do a fetch and create an array that represents the current state, (2) implement a table that presents the state of that array to the user.
Lets start with the array (note this is pseudo-code, meant to illustrate the idea):
// in interface
#property(strong,nonatomic) NSMutableArray *model;
// in init
_model = [NSMutableArray array];
// sometime at or after viewDidAppear, update your model by querying parse
// I don't understand your parse data model, but you want some query
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// based on the parse objects returned, initialize model
[self.model addObject: ...];
// tell your table that the model has changed
[self.tableView reloadData];
}
}];
With your array handled elsewhere, the datasource job is simpler:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.model.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// dequeue a cell, configure its subviews based on self.model[indexPath.row]
// no parse code here. aim to configure the cell strictly based on the model
return cell;
}
Notice how the table view is ignorant of parse.com and the parse code is largely ignorant of the table view.

IOS Searchbar returns wrong data

I am going crazy trying to get my IOS Searchbar working for the Iphone. I access data from a remote server and populate a content file. I then do a filter which creates a filtered content file. I then do a [self.tableView reloadData()]. It works fine the first time around. Then I change my scope and do another fetch of data from my server and filter it and do another reload. However, the second time the table shows the first 9 items from the previous display rather than the new 9 items from the filtered file. I console display the file count in the filtered file which in this case is 9 in the tableView numberOfRowsInSection: I also display each item going through the cellForRowAtIndexPath. In the cellForRowAtIndexPath I am displaying the correct 9 unfiltered items but they do not show up on the table! The table shows the first 9 items from the old display instead.
My question is doesn't the new data display on the table instead of the old data even though the count is correct? Why am I displaying the correct items on the console but yet the display shows items from the old display. WHat do I need to do to make the new data appear? I know this is pretty hard to comprehend but I am listing some of my code below in hopes that someone can give me a clue on why the table view is not being updated with the latest data.
// This is where I get data back from the server.
self.listContent = [[NSArray alloc] init];
if(_scopeIndex == 0)
self.listContent = [jsonObject objectForKey:#"burials"];
else
if(_scopeIndex == 1)
self.listContent = [jsonObject objectForKey:#"obits"];
else
self.listContent = [jsonObject objectForKey:#"photos"];
if(self.listContent > 0)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self filterContentForSearchText:searchString scope:
[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
[self.tableView reloadData];
});
}
Below is where the data is filtered. In this case the unfiltered and filtered file are the same.
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope{
/*
Update the filtered array based on the search text and scope.
*/
[self.filteredListContent removeAllObjects];// First clear the filtered array.
/*
Search the listContent for names that match and add to the filtered array.
*/
for (int i = 0; i < [self.listContent count]; i++)
{
NSComparisonResult result = [self.listContent[i][#"LastName"] compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame)
{
[self.filteredListContent addObject:self.listContent[i]];
}
// }
}
}
This is where I get the table count of filtered items.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (tableView == self.searchDisplayController.searchResultsTableView)
{
NSLog(#"Filtered Name count = %i", [self.listContent count]);
return [self.filteredListContent count];
}
else
{
NSLog(#"Name count = %i", [self.listContent count]);
return [self.listContent count];
}
}
ANd this is where I update the cells in my table:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *kCellID = #"cellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellID];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellID];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
if(indexPath.row > [self.filteredListContent count] - 1)
return cell;
NSDictionary *burial = [self.filteredListContent objectAtIndex:indexPath.row];
NSString *lastname = burial[#"LastName"];
NSString *firstname = burial[#"FirstName"];
NSString *burialname = [NSString stringWithFormat: #"%#, %#", lastname, firstname];
cell.textLabel.text = burialname;
NSLog(#"Cell name= %# index path=%i", cell.textLabel.text, indexPath.row);
return cell;
}
I changed my logic to go to the server one time to get my content and this time included scope indicators in my content table. This enables me to process scope filters without having to go back to the server for data for a specific scope. Doing this resulted in proper view tables being displayed when changing scope. I would not recommend going to the server ascynchronously whenever the scope changes on the search as it really screws up the view table.
Some things to try:
Maybe you're reloading your tableView too soon, or
Your cellForRowAtIndexPath needs to distinguish between the table views (just as your numberOfRowsInSection does), or
Maybe you don't have all of your delegates set up correctly. The searchBar is used with a UISearchDisplayController, which has 2 delegates: searchResultsDataSource and searchResultsDelegate. Make sure those are set to self (or whatever class handles these).
How are you using searchbar? Logic should be that you have tableview with all data and the you use search bar to filter out matching results and add them to search data array and while searchbar is active you display search data array.
Is
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
used somewhere?
Added following
If you have this function set up you can update search array and reload it using something like
if (self.searchDisplayController.searchResultsTableView != nil)
{
//Updates searchData array
[self searchDisplayController:self.searchDisplayController shouldReloadTableForSearchString:searchBar1.text];
//Updates display
[self.searchDisplayController.searchResultsTableView reloadData];
}
else
{
[_channelsTableView reloadData];
}
It is calling searchDisplayController with current search bar text. When new tableview data comes from server you can activate searchDisplayController to refresh search results array and then it is possible to refresh the display.

iOS: When is cellForRowAtIndexPath called? Or do tags get updated?

I'm starting to get confused. I'm using a FetchedResultsController for my tableview data. In each cell I have a button and a textfield tagged with the indexPath.Row in the cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Data model and cell setup
static NSString *CellIdentifier = #"MainCategoryCell";
MainCategoryTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
MainCategory *mainCategory = [self.fetchedResultsController objectAtIndexPath:indexPath];
/* ... */
cell.title.tag = indexPath.row;
cell.iconButton.tag = indexPath.row;
return cell;
}
Now my row move method is a bit more complicated for the Fetched Results controller. However I'm pretty sure the tags don't get updated after the moving. Is that normal and is the cellForRow method only called after creating a new cell? Do I have to update the tags myself in the move method? And how could I access there the tag properties of the objects within the cells?
- (void)tableView:(UITableView *)tableView
moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath
toIndexPath:(NSIndexPath *)destinationIndexPath;
{
// Process the row move. This means updating the data model to correct the item indices.
//reordering has been defined in the CoreDataViewController so the
//FetchedResultsController doesn't mess up the reordering since he would update
//the fetched results permanently while reordering
self.reordering = YES;
//Makes only a mutable copy of the array, but NOT the objects (references) within
NSMutableArray *fetchedResults = [[self.fetchedResultsController fetchedObjects] mutableCopy];
// Grab the item we're moving
NSManagedObject *resultToMove = [self.fetchedResultsController objectAtIndexPath:sourceIndexPath];
// Remove the object we're moving from the array.
[fetchedResults removeObject:resultToMove];
// Now re-insert it at the destination.
[fetchedResults insertObject:resultToMove atIndex:[destinationIndexPath row]];
// All of the objects are now in their correct order. Update each
// object's displayOrder field by iterating through the array.
int i = 1;
for (MainCategory *fetchedResult in fetchedResults)
{
fetchedResult.position = [NSNumber numberWithInt:i++];
}
// Save
NSError *error = nil;
[self.budgetDatabase.managedObjectContext save:&error];
// re-do the fetch so that the underlying cache of objects will be sorted
// correctly
[self.fetchedResultsController performFetch:&error];
self.reordering = NO;
}
Yes it is normal that the tags won't get updated when you move cells. Since all cells have the possibility of being shifted, simply reload the table view to have it regenerate the tags for your button and text box.
[tableView reloadData];

Adding a row in TableView iOS

I´m quite new to iOS development and I´m having a terrible time by trying something that should be easy; to add an extra row in a TableView everytime the user clicks on one of the existing rows. There is no real purpose on that action, I´m just wanting to understand the behaviour of TableView.
So I did the following:
I used a Split View-based template and changed the number of rows to 30 in the RootViewController.
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return 30;
}
The method tableView:didSelectRowAtIndexPath looks in the following manner:
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
/*
When a row is selected, set the detail view controller's detail item to the item associated with the selected row.
*/
NSMutableArray* paths = [[NSMutableArray alloc] init];
NSIndexPath *indice = [NSIndexPath indexPathForRow:30 inSection:0];
[paths addObject:indice];
detailViewController.detailItem = [NSString stringWithFormat:#"Second Story Element %d with all its information and bla bla bla", indexPath.row];
[[self tableView] beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[[self tableView] endUpdates];
}
When I execute the program and click on one of the elements, I receive the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (30) must be equal to the number of rows contained in that section before the update (30), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted).'
I did not change any other part of the code that the template provides.
I read quite extensively the documentation from Apple and the responses to the following questions:
Add a row dynamically in TableView of iphone
and
how to properly use insertRowsAtIndexPaths?
The second question seems to address the same problem, but I´m not capable to understand what is happening. What do they mean with dataSource? The response that I understand better says the following:
It's a two step process:
First update your data source so numberOfRowsInSection and cellForRowAtIndexPath will return the correct values for your post-insert data. You must do this before you insert or delete rows or you will see the "invalid number of rows" error that you're getting.
What does this update of the data source implies?
Sample code would be HIGHLY appreciated, because I´m totally frustrated.
By the way, all that I´m trying has nothing to do with entering the editing mode, has it?
You need to keep the count returned by tableView:numberOfRowsInSection: in sync!
So when you have 30 rows and then tell the tableview to insert a new row you need to make sure tableView:numberOfRowsInSection: will now return 31.
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section
{
return self.rowCount;
}
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.rowCount++;
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}
In practice you would probably use an array to track your rows return [self.rows count]; etc
The answer is quite simple. When you want to modify a table view you need to perform two simple steps:
Deal with the model
Deal with table animation
You already perform the second step. But you have missed the first one. Usually when you deal with a table you pass it a data source. In other words some data to display within it.
A simple example is using a NSMutableArray (it's dynamic as the name suggests) that contains dummy data.
For example, create a property like the following in .h
#property (nonatomic, strong) NSMutableArray* myDataSource;
and in .m synthesize it as:
#synthesize myDataSource;
Now, you can alloc-init that array and populate it as the following (for example in viewDidLoad method of your controller).
self.myDataSource = [[NSMutableArray alloc] init];
[self.myDataSource addObject:#"First"];
[self.myDataSource addObject:#"Second"];
Then, instead of hardcoding the number of rows you will display (30 in your case), you can do the following:
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
return [self.myDataSource count];
}
Now, in you didSelectRowAtIndexPath delegate you can add a third element.
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.myDataSource addObject:#"Third"];
[[self tableView] beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[[self tableView] endUpdates];
}
It looks like one big problem is with tableView:numberOfRowsInSection:. You need to return the correct number of rows in that method.
To do that, it's usually best to maintain an NSArray or NSMutableArray of items for the table view so in that function, you can say: return [arrayOfValues count];. Keep the array as a property of your view controller class so that it's readily accessible in all methods.
The array can also be used in cellForRowAtIndexPath:. If you have an array of NSString, you can say cell.text = [arrayOfValues objectAtRow:indexPath.row];.
Then, when you want to add an item to the table view, you can just add it to the array and reload the table, e.g. [tableView reloadData];.
Try implementing this concept and let me know how it goes.
You can Also do that for dayanamic table cell
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [arrayStationStore count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIndentyfire;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIndentyfire];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndentyfire];
}
cell.textLabel.text = [arrayStationStore objectAtIndex:indexPath.row];
return cell;
}
-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Check if current row is selected
BOOL isSelected = NO;
if([tblStationName cellForRowAtIndexPath:indexPath].accessoryType == UITableViewCellAccessoryCheckmark)
{
isSelected = YES;
}
if(isSelected)
{
[tblStationName cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
[arrayReplace removeObject:indexPath];
NSLog(#"array replace remove is %# ",arrayReplace);
}
else
{
[tblStationName cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
[arrayReplace addObject:indexPath];
NSLog(#"array replace add is %# ",arrayReplace);
}
return indexPath;
}

Resources