Filtering UITableViewCells with animation - iPhone Development - iphone

This seems simple enough but as yet I am unable to find a solution.
Basically I have a segmented control with two options. The first is the default (and is automatically displayed on load) and when selected displays all rows in a table view. The second is a filter limiting the rows displayed. This is the exact same set-up as used on the "Recents" tab of the iPhone's Phone app that filters 'All' and 'Missed' calls.
At present I have the data loading from two different arrays. The problem is that when I swap the data there is no animation to denote that the rows have been filtered. Apple have implemented this in their Phone app but I can see no way of acheiving this.
Perhaps each cell will need to be deleted and re-added as the user switches between the two states - or perhaps setting the height of the cells that I wish to hide to 0 would acheive the same effect? Does anyone have any experience of producing this accordian-type animation?
I have looked here for some clues but am having problems rolling some code that works. Has anyone implemented this before? If so, how did you get it to work?

You can accomplish a similar effect by calling deleteRowsAtIndexPaths:withRowAnimation: and insertRowsAtIndexPaths:withRowAnimation: on your table view with a UITableViewRowAnimationFade animation.

Have you looked into reloadSections:withRowAnimation:?
The basic idea is to call reloadSections:withRowAnimation: and in your UITableViewDataSource implementation switch on the segmented control's selectedSegmentIndex.
Assuming your data is flat (only one section) it would look something like this:
- (IBAction)segmentSwitch:(id)sender
{
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
switch (self.segmentedControl.selectedSegmentIndex)
{
default:
case 0:
return [self.allRows count];
case 1:
return [self.onlySomeRows count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
id data;
switch (self.segmentedControl.selectedSegmentIndex)
{
default:
case 0:
data = [self.allRows objectAtIndex:[indexPath row]];
break;
case 1:
data = [self.onlySomeRows objectAtIndex:[indexPath row]];
break;
}
//TODO: use data to populate and return a UITableViewCell...
}

Related

Displaying a "No rows found" message in UITableView with Core Data

I have implemented an iPhone app that uses UITableViewController/UITableView and Core Data. Further, I use a NSFetchedResultsController to manage the table data. This was all very straight forward and works great. I then decided that I should display a message in the UITableView when no rows where found/retrieved. After researching this, it appeared that the best way (perhaps the only way) to do this was to return a "dummy" cell that contains the message. However, when I do this, I get a nastygram from the runtime system that complains (and rightfully so) about data inconsistencies: "Invalid update: invalid number of sections. The number of sections contained in the table view ...". Here is the relevant code:
- (NSInteger) numberOfSectionsInTableView: (UITableView *)tableView
{
if ([[self.fetchedResultsController fetchedObjects] count] == 0) return 1;
return [[self.fetchedResultsController sections] count];
}
- (NSInteger) tableView: (UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([[self.fetchedResultsController fetchedObjects] count] == 0) return 1;
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex: section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *) tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
if ([[self.fetchedResultsController fetchedObjects] count] == 0) {
UITableViewCell *cell = [[UITableViewCell alloc] init];
cell.textLabel.text = #"No widgets found.";
return cell;
}
STCellView *cell = (STCellView *)[tableView dequeueReusableCellWithIdentifier: #"ShieldCell"];
[self configureCell: cell atIndexPath: indexPath];
return cell;
}
I have read responses from similar questions and it appears that I should use
insertRowsAtIndexPaths: withRowAnimation:
to insert the "dummy" message row into my table. However, this also means removing the "dummy" row when a real row is inserted. I can do this, but it seems like there should be an easier way to accomplish this. All I want to do, is to display a message indicating that there are no rows in the table (simple enough?). So, my question is this: Is there a way to display a message in an UITableView without using the "dummy" cell approach OR is there a way to convince UITableViewController/NSFetchResulsController that this is only a "dummy" row and they should not get so upset about it because it is not a real row (from my point of view) in the table?
Any help you can provide would be very appreciated (I am a struggling newbie to iPhone development and I want to learn the best practices). Thanks.
Rather than hack with the tableview datasource to get the intended UI you should add the "No rows found" message to the tableview header instead.
I did as follows in viewDidLoad.
UILabel *label = [[UILabel alloc] init];
[label setTextColor:[UIColor lightGrayColor]];
[label setText:#"No widgets found."];
[label sizeToFit];
label.frame = CGRectMake((self.tableView.bounds.size.width - label.bounds.size.width) / 2.0f,
(self.tableView.rowHeight - label.bounds.size.height) / 2.0f,
label.bounds.size.width,
label.bounds.size.height);
[self.tableView insertSubview:label atIndex:0];
In this case, each TableViewCells must be opaque to hide the label. or need to toggle the hidden property of the label according to the row count.
An alternative approach, which I have used before is to use Core Data to manage the update for you by inserting a 'no rows' entity for the section where no rows have been detected in your model class, which handles the data update.
There are a number of ways to implement this e.g. set the name/title field to a known status message or a flag within the entity. Once inserted you can detect the 'no rows' entity in the cellForRowAtIndexPath delegate method and insert an alternative table cell to show the message.
Just remove the 'no rows' entity before refreshing the data for that section.
My simple suggestion to display an empty message is to rearrange your controller to be a simple UIViewController (not a UITableViewController).
This UIViewController is composed by a UITableView (the controller is the data source and the delegate for your table) and by a UILabel (or a UIView that contains a UILabel) that displays the empty row message.
In this manner you can control the visibility of the table and the label based on the retrieved rows.
This approach could be laborious but I think it's good to avoid hacking NSFetchResultsController and data source. Furthermore you could have a complete control on arranging the position for your empty message.
As #Rog suggested you could also use the table view header to display that message. As you prefer.
Hope it helps.

iPhone UI Design question - Best way to design forms?

I want to design an app that needs user to input few things like start date, end date, bunch of other options and some text comments for which I am planning to use pickers to select the data that will slide up modally. I will need to move the view up and down to make sure that the element being filled stays in focus when the pickers and keyboard slides up and down.
My question is what would be the best view to implement such a "form"? I was thinking grouped table view where I could separate the fields section wise.
Is there any other way to implement these things?
By experience or best practices, are there any better alternatives or sample code or apps out there that I can explore?
Dev.
The most iPhone-like interface for forms is going to be a grouped table view. It is what most users will expect, after using other apps which use grouped table views for adding and editing structured data.
A good practice is to create an enum (enumeration) for sections and for rows within sections, e.g.:
typedef enum {
kFormSectionFirstSection = 0,
kFormSectionSecondSection,
kFormSectionThirdSection,
kFormSections
} FormSection;
typedef enum {
kFormFirstSectionFirstRow = 0,
kFormFirstSectionSecondRow,
kFormFirstSectionRows
} FormFirstSectionRow;
...
In this example, you can use this enumeration to refer to sections by name instead of number.
(In practice, you probably wouldn't use kFormSectionFirstSection as a descriptive name, but something like kFormSectionNameFieldSection or kFormSectionAddressFieldSection etc., but this should hopefully illustrate the structure of the enum.)
How would you use this?
Here's an example of a few table view delegate methods which demonstrate how this is useful:
- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
return kFormSections;
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
switch (section) {
case kFormSectionFirstSection:
return kFormFirstSectionRows;
case kFormSectionSectionSection:
return kFormSecondSectionRows;
...
default:
break;
}
return -1;
}
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// cell setup or dequeue...
switch (indexPath.section) {
case kFormSectionThirdSection: {
switch (indexPath.row) {
case kFormThirdSectionFourthRow: {
// do something special here with configuring
// the cell in the third section and fourth row...
break;
}
default:
break;
}
}
default:
break;
}
return cell;
}
This should quickly show the utility and power of enumerations.
Names in code are much easier to read than numbers. When you're dealing with delegate methods, if you have a good descriptive name for a section or a row, you can more easily read the logic of how the table view and cells are managed.
If you want to change the order of sections or row, all you have to do is rearrange the order of enumerated labels in the enum construct. You wouldn't need to go into all the delegate methods and change magic numbers, which quickly becomes a tricky and error-prone dance once you have more than a couple sections and rows.

iPhone UITableView with index can I push a different detailed view with every different cell?

I know its possible to create a table that has an index on the side and a search bar at the top that a user can type in to find an item, but is it possible to say to the table if array isEqual to "item1" push view1? I would like to push a different view with each cell. Anyone have any advice?
Sure. Just create the appropriate view (controller) depending on the cell's indexPath in tableView:didSelectRowAtIndexPath:.
Create the cell based on the index path. If you create all the cells ahead of time, store them in an array by row index. If you are creating them as needed, do something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *result;
switch ( [indexPath row] ) {
default: result = [self tableView:tableView normalCellForRowAtIndexPath:indexPath]; break;
case 3: result = [self tableView:tableView detail3CellForRowAtIndexPath:indexPath]; break;
case 5: result = [self tableView:tableView detail5CellForRowAtIndexPath:indexPath]; break;
}
return result;
}
Check out the sample programs from the book Beginning iPhone Development. You must sign up, but its free. You should look specifically at chapter 9's sample code called Nav. Shows you exactly what you need. This was my first book on the subject and was well worth it.
http://www.iphonedevbook.com/forum/viewforum.php?sid=3010c045df967353c6b3894889e6b8f5
Cheers!
Ken

How to block a UITableView Cell from being deleted in iPhone

I have a UITableView With Two Sections. First section has one row and the section one has variable number of rows which varies according to data entry in database.
I want that user cant delete the cell from first section but he is able to delete the cells from the second section.
I have implemented the commitEditingStyle method for the tableview but the problem is it allows the user to delete the row from first section.
I can put some flag to check it in commitEditingStyle but what I want to do is just block it to show the editing button. That is user wont be able to see the delete button when he swips his or her finger on the table cell.
I did set the property editing=false but cant set editingStyle since it is readonly property. Setting editing=false doesnt work.
tnx.
Yeah this works.
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.section) {
case 0:
return UITableViewCellEditingStyleNone;
break;
case 1:
return UITableViewCellEditingStyleDelete;
break;
default:
return UITableViewCellEditingStyleNone;
break;
}
}
Have you tried setting the tableViewCells editing style to none for the first section cells? I think that should work for you

How to find the row index of a UIButton is a TableCell? (iPhone)

In my app, I have a table displaying a cell in each row.
In Interface Builder, I dragged a button onto the cell, styled it as a Dark Info button, and connected it to a IBAction.
That is working fine.
Only, I want the button to behave differently, depending on the row of the table where the cell of the button is.
How would I get that row index?
I realize that I might display a lack of basic understanding of the object hierarchy, but I hope you guys will forgive me
Thanks
Sjakelien
It's definitely not easy to do if you don't have some data set up first. If you can, have an NSDictionary where the buttons are the keys and the values are the index paths, that you update whenever you return a cell from -tableView:cellForRowAtIndexPath:. Something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
[indexDict setValue:indexPath forKey:theButton];
return cell;
}
- (void) buttonPressed:(UIButton *)button {
NSIndexPath *indexPath = [indexDict valueForKey:button];
...
}
You can maintain tags. When you drag and drop the button, check the interface build and you will see "tag" property for these buttons. Assign different values for each of your button ( I assume you have different buttons for different rows, this solution will not work if you have same cell identifier for different rows ). And when you receive an event check for tag value.
I had similar problem with my work and i was maintaing NSArray for each button tag created.
In your tableView delegate and datasource methods (check the docs!) you have several methods, the best one for this is
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Drop this method in your implementation and say something like
switch (indexPath.row) {
case 0:
//set variable or do method based on row 0 (first row)
break;
case 1:
//set variable or do method based on row 1 (second row)
break;
case 2:
//set variable or do method based on row 2 (third row)
break;
}//and so on
}
another way is to change the base class of your UIButton in your view,
then using this other class wich basically extends an UIButton with an added NSInteger row #property (remember to #synthesize it).
You'll then set this property during the cell setup, and you can retrieve this property within the message method