UITableView...the correct way - iphone

I'm trying to make a UITableView like the native calendar app:
but I'm trying to learn the best way to do this. I'm able to get this for the most part with a switch statement in the cellForRowAtIndexPath method, but I'm having troubles changing the textColor when a cell is selected.
For some reason cell.isSelected is always NO, and I have no way to reload the tableview after another cell is selected anyway.
Should I subclass UITableViewCell for something this simple and store an array of cells?
Any help would be appreciated, thanks.

There is no need to subclass, as stated in the app doc the delegation function:
tableView:willSelectRowAtIndexPath:
should do the trick
EDIT:
Below you find some code that should demonstrate the idea of using the delegate. Please note that this code is untested, as i am currently not in front of my xcode.
-(void)willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (prevIndexPath != nil) {
UITableViewCell* prevCell = [self.tableView cellForRowAtIndexPath: prevIndexPath];
prevCell.textLabel.textColor = [UIColor black]; // your initial color here
}
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath: indexPath];
cell.textLabel.textColor = [UIColor green];
prevIndexPath = indexPath;
}
Make sure to set the UITableViewDelegate protocol to your controlling class that manages your TableView and set the tableViews.delegate to that.
To make this code run, you also have to define a property or variable with the name prevIndexPath. This ones holds the previously selected cell that is needed to revert to the cell to its initial color.

Related

how to set hidden property to NO for particular cell in tableView

I am new to iphone.I have a small doubt that is,I have a table view with 66 rows initially i placed a progress view to all rows but in viewdidload i set it to hidden for all those like below in tableViewClass(ShowProgressViewCont)
cell.progressView.hidden = YES;
here (cell) is the the reference of the CustomCell class.In this class i declare a progress view and setter and getter properties also be set here in this class
here in tableview there is a download button in each cell.If we click on that download button(for example in 66th cell).we have to remove the hidden property to the progress view for that particular 66th cell only.The remaining 65 cells should have the progress view in hidden only like that for all cells.
If any body know this please help me....
Are you familiar with the concept of table cells being reused?
viewDidLoad is not an appropriate place to manipulate the content of a single cell. It may work fine if the table is so small that all of its cells fit on the screen (in both orientations).
When there are more cells in the table than beeing displayed on the screen, then those cell that became invisible recently is being reused and displayed again.
So if there are 6 cells on the screen at a time then table cell no. 7 (sometimes 8) will be identical with cell no. 1.
cellForRowAtIndexPath would be a better place to hide/unhide certain views of a cell.
If it is a custom cell already then the cell's layoutSubViews could be appropriate, too. However, only in the tableViewController's cellForRowAtIndexPath you will have easy access to both, the table's data and the associated cell.
cellForRowAtIndexPath is called every time when a cell is about to become visible. Use the default reusing-mechanism to ensure that a proper cell object will be reused. It is pretty much straight forward.
Get the cell at index 65 and then cast it to your custom cell
UITableViewCell *cell = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:64 inSection:0]];
YourCustomCell *customCell = (YourCustomCell *)cell;
customCell.progressView.hidden = NO;
First set the row number in your download button in CellForRowAtInedexPath
downloadButton.tag = indexPath.row
[downloadButton addTarget:self action:#selector(actionDownload:) forControlEvents:UIControlEventTouchUpInside];
in actionDownload, make the IndexPath from button.tag and get the cell from "cellForRowAtIndexPath:",
finally update via reloadRowsAtIndexPaths: withRowAnimation:
Hide your progress view in your custom cell means make the default property of every progress view is hidden ,and then your button click method .
CutomeCell *cell = (CutomeCell *)[[button superview] superview];
[cell.progressView setHidden:NO];
NSIndexPath *rowToReload = [[self tableView] indexPathForCell:cell];
NSArray* rowsToReload = [NSArray arrayWithObjects:rowToReload, nil];
[UITableView reloadRowsAtIndexPaths:rowsToReload withRowAnimation:UITableViewRowAnimationNone];
may this help you ...
your model should be tracking the progress for every button clicked. for purposes of this answer, let's say the model is held in a property called modelInformationArray (which would contain an array of objects that relate to each cell in the table)
when the download button is clicked for a cell, modelInformationArray is modified in such a way that its object knows a download is being processed for it.
that object reports the downloading processing in a member called downloadingProcessingStarted. there are many other ways to do that part.
to get to the answer to your question … to then unhide the progress view, you would do something as follows in your UITableViewDataSource implementation of tableView:cellForRowAtIndexPath: .
- (UITableViewCell*)tableView:tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
YourCustomCell* cell = ...
if ([[self.modelInformationArray objectAtIndex:indexPath.row] downloadingProcessingStarted])
cell.progressView.hidden = NO;
}

UITableViewCell Accessory Repeating?

In my app, I have a detailed view where the user can edit attributes of for example a person, their name, address etc.
In two cells, rather than being able to select them to edit their contents, they have a right accessory, a UISwitch, however sometimes, its inconsistent, but they replicate onto other cells in my last section.
I have been scanning my code dozens of times over with a fine comb and can't find the damn cause. What might cause this? Here is the code that I use to create the UISwitch on just a single cell:
if (indexPath.section == 0 && indexPath.row == 1)
{
cell.textLabel.text = #"Confirmed";
//Make the cell unselectable
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
//Create and add uiswitch
confirmedSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
[confirmedSwitch addTarget:self action:#selector(switchConfirmedStatus:) forControlEvents:UIControlEventValueChanged];
[confirmedSwitch setOn:[venue.isConfirmed boolValue]];
cell.accessoryView = confirmedSwitch;
}
So you expect it to only show up on that cell, see anything odd with that code? I have checked my if statements and all my brackets indexPath checks are correct.
Anyone see this before or have any clues?
The problem is because of reusability issues in the UITableView. You probably use the same identifier for all cells and this causes the cellForRowAtIndexPath to be implemented in other rows (when you scroll up and down).
This is a common problem when you are developing applications with tableView and there are plenty of questions like this on StackOverflow.
As a general solution, you will need to do either of these.
Use different identifiers when you assign dequeueReusableCellWithIdentifier for each cell. This is fairly simple, and you just need to assign different identifiers for them.
Subclass you UITableViewController, and create your own CustomTableViewController which will implement the necessary components in the cell. I believe you will need to override the set Layout Subviews method.
Take a array in view will appear and add object 0 for this row and 1 for all other rows and then in cellForRowAtIndexPath check if array has 0 at that index if it has then put switch otherwise nill..
for(int i =0;i<10;i++)
{
if(i==1)
{
[arrayForSwitch addObject:#"0"];
}
else
{
[arrayForSwitch addObject:#"1"];
}
}
then in cellForRowAtIndexPath write condition
if([arrayForSwitch objectAtIndex:indexPath.row isEqualToString:#"0"])
{
cell.accessoryView = confirmedSwitch;
}
else
{
cell.accessoryView =UITableViewCellAccessoryNone;
}
it will now remain same

UITableViewCell - view showing data on top of data when being reused

I'm using a custom UITableViewCell. They load up great, but when they get reused instead of replacing the text in the labels, they are some how writing over the top of them. Any ideas how to stop this behaviour?
I assume I'm not reseting the view correctly before it gets reused. I'm currently empty the labels so they have just a blank #"" string. But I still get the old text plus the new (very messy).
I'm certain there's an obvious solution to this (presently i just don't reuse the cell, but this isn't best practice and is slow on old devices), so if someone can help I'd be very grateful.
Thanks
ED
As requested here is the method for amending the cell
static NSString *CellIdentifier = #"Cell";
TransactionCellView *cell = (TransactionCellView *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"TransactionCellView" owner:self options:nil];
cell = tblCell;
}
[cell resetCell]; // clears the labels
[cell setData:[dataArray objectAtIndex:[indexPath row]]; // replaces the data and updates the labels
return cell;
}
Solved this issue!!! Amazingly simple when you know what the problem is/was.
I was using IB and "Clear Graphics Context" was not selected on my UILabels. So that is why the next text was just overlaying the old.
THanks guys for trying to help.
I suspect that you are adding the label as a subview programatically in your cellForRowAtIndexPath method. You need to make sure that you are not adding that subview every time the cell is recycled. Instead create the label only when creating a new cell (not when recycling a cell) then assign a tag value to the label and then in the future, when it gets recycled, retrieve the label by tag value and change its text.
You are correct in that the cells are being reused. Your code should be set up roughly using the following pattern:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString* identifier = #"someidentifier";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell) {
// create the cell and add all subviews to it
} else {
// update the cell and access appropriate subviews to modify what is displayed
}
return cell;
}
The cell will be created the first time the identifier is used. For all subsequent requests, the cell is pulled from the UITableView cache (via dequeueReusableCellWithIdentifier), and you can then access its subviews either by tag, index, type, or whatever mechanism you choose.
Somewhat related is that you can have multiple cell identifiers, which allows you to create multiple instances of different cells depending on the data that you have. In one of my projects, I have 4 different cells, each dependent upon the number of lines of data that they will display (anywhere between 1 and 4). This helps ensure a smooth scrolling experience regardless of how many lines the cell has since the renderer doesn't have to worry about dynamically changing the height of the cell on the fly.
Try this..
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
for (UIView* subView in cell.contentView.subviews)
{
[subView removeFromSuperview];
}
// Your Code to customize cell goes here.
}

UITableView Separator Style Question

I have a tableview that is blank by default. User can add cells to it.
I want the separator lines to be clear when there are no cells, and grey when there are cells.
I am using this code:
if ([[self.fetchedResultsController fetchedObjects] count] == 0)
{
self.routineTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.routineTableView.separatorColor = [UIColor clearColor];
}
else
{
self.routineTableView.separatorColor = [UIColor grayColor];
}
The problem is, when I launch the app with a blank table, and if I add cells, the grey lines are not there there until I restart the app. But if I start with cells there, then delete them, then re-add them, the lines are there. Any suggestions?
Maybe you are missing this?
...
else
{
self.routineTableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; // or you have the previous 'None' style...
self.routineTableView.separatorColor = [UIColor grayColor];
}
EDIT :
You need this but not only this... According to Apple Documentation :
The value of this property is one of the separator-style constants described in UITableViewCell Class Reference class reference. UITableView uses this property to set the separator style on the cell returned from the delegate in tableView:cellForRowAtIndexPath:.
That means the style wont change for cells that are already loaded. Just scrolling the table to force cells to redraw should make separators appearing...
You then have to :
set it BEFORE cell is inserted
OR
reload tableView when the first cell is added
which is not easy to do with a NSFetchedResultsController, you should look into its delegate for a solution... or change direction, like hiding the tableView until you have a result maybe...
EDIT 2 : You can also simply add this :
[self.tableView reloadData];
but that's a dirty workaround that will just reload full tableView, losing most benefits of NSFetchedResultsController...
A quick fix I usually do is:
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if ([tableView respondsToSelector:#selector(setSeparatorStyle:)]) {
[tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
}
}
This changes the boolean flag of whether there will be a separator or not. Put this in viewDidLoad:
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
And to make sure you've really made it go away, set the seperatorColor property to whatever the background color of the view and cell would be:
// If the background is white
self.tableView.separatorColor = [UIColor whiteColor];
So then even if somehow the above does not get called and the separator is still persisting - it would be the same color as what is behind it, therefore invisible.
Good luck.

How can a UITableViewCell know of its own indexPath?

The standard Grouped UITableView style allows UITableViewCells to be drawn with rounded corners at the top and bottom of each section. How is this accomplished? How does the cell know its own location within its section, and how does it know when to change its rounded edges?
I want to make my own rounded cells, and I have images to use, but don't know when to show which image
Note: I already know how the UITableView works, and I know how to use it. I just thought that since a UITableView is able to automatically draw rounded corners at the correct places, I should be able to as well, without needing to add anything to my data source or delegate.
NSIndexPath *indexPath = [(UITableView *)self.superview indexPathForCell: self];
int rows = [(UITableView *)self.superview numberOfRowsInSection:indexPath.section];
if (indexPath.row == 0 && rows == 1) {
// the one and only cell in the section
}
else if (indexPath.row == 0) {
//top
}
else if (indexPath.row != rows - 1) {
//middle
}
else {
//bottom
}
It's very simple. suppose cell is the object, whose position is to be found out.
UITableView* table = (UITableView *)[cell superview];
NSIndexPath* pathOfTheCell = [table indexPathForCell:cell];
NSInteger sectionOfTheCell = [pathOfTheCell section];
NSInteger rowOfTheCell = [pathOfTheCell row];
There is sectionLocation method of UITableViewCell that returns integer telling you what you need:
1 - middle cell
2 - top cell
3 - bottom cell
4 - single cell
I had no issues using this in several production apps since 2010.
UPDATE: one of our binaries was automatically rejected recently (end of 2018) because we were using 'sectionLocation' property, so it's not a good option anymore.
Add something like this into your header files and you can use it:
typedef NS_ENUM(NSInteger, MMMTableViewCellLocation) {
MMMTableViewCellLocationUndefined = 0,
MMMTableViewCellLocationMiddle = 1,
MMMTableViewCellLocationTop = 2,
MMMTableViewCellLocationBottom = 3,
MMMTableViewCellLocationSingle = 4
};
#interface UITableViewCell ()
/** Undocumented method of UITableViewCell which allows to know where within section the cell is located,
* so the cell can draw its borders properly. */
- (MMMTableViewCellLocation)sectionLocation;
/** Override this one to know when the value of sectionLocation changes. */
- (void)setSectionLocation:(MMMTableViewCellLocation)sectionLocation animated:(BOOL)animated;
#end
You can use
- (NSIndexPath *)indexPathForCell:(UITableViewCell *)cell
for this issue. In my example I am using this to scroll the cell (with custom content) to the top of the view.
If you need more robust and general stuff, take a look at http://cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.html - Matt Gallagher shows what you need, pretty effectively. He basically recreates UITableViewController from UIViewController, while adding ability to use your own custom graphics. I'm just working on applying this to one my projects, so far it looks it would do the job.
Unfortunately, I have found no solution to this problem, and have resorted to subclassing UITableViewController and UITableViewCell into a generic solution that I can extend as necessary.
You don't do this in cell. Rounded corners are drawn in [tableView viewForHeaderInSection] and viewForFooterInSection.
The way I do it is to use Plain tableview style, then use these two views for roundness and cells are normal, no rounds.
Without getting into who draws what, you can know which cell is the last cell in its section inside of cellForRowAtIndexPath very easily.
You're passed in the indexPath of the cell you need to provide, right? You're also passed the tableView.
call [tableView numberofRowsInSection:indexPath.section] and if it's == ([indexPath.row]-1) you know you're being asked to supply the last cell in that section.
At the time that cellForRowAtIndexPath is being called, the cell is guaranteed to be at the indexPath passed in.
To expand upon Darren's answer (which I found most useful, thanks Darren!), what you can do is to iterate through all of the superviews' until you find the parent UITableView. This should be future proof since you do not rely on a fixed hierarchy of views.
I use a recursive method that will return the UITableView if it finds one or return nil if there is none.
- (UITableView *)parentTableViewOf:(UIView *)view {
Class class = [view.superview class];
NSLog(#"Class : %#", NSStringFromClass(class));
if([view.superview isKindOfClass:[UITableView class]]) {
return (UITableView *)view.superview;
} else {
return [self parentTableViewOf:view.superview];
}
return nil;
}
So far I've used this one and it seems to work without hiccups. Hope it helps! :)
The cells dont know where they go...The table view has cells, You are the one telling the table view WHAT goes in the cell. You do this in the DataSource where you implement cellForRowAtIndexPath...The way this works :
An index path has a row and a section
For a grouped table view
A section pertains to a group, and a row pertains to 1 entry in that section,
the way UITableView knows how many rows are in a section and how many sections there are is the DataSources methods numberOfSectionInTableView and the method numberOfRowsInSection, this will make the right calls to cellForRowAtIndexPath, it is up to you to recognize which section and row is being queried and you need to build your cell according to these specifications.
A good way to do this i s you can have a Dictionary with keys of section names and values of NSArray with the values that go in that section.
So you implementation for numberOfSectionsInRows would look like
return [[dictionary allKeys] count]
And the implmentation of numberOfRowsInSection would look like
NSString* key=[[dictionary allKeys] objectAtIndex:sectionNumber]
return [[dictionary objectForKey:key] count]
You can always refer to the UITableView programming guide at http://developer.apple.com/iphone/library/documentation/UserExperience/Conceptual/TableView_iPhone/Introduction/Introduction.html
Hope that helps
Simply add a property to your custom UITableViewCell (depending on implementation) class that contains an int, NSNumber, or an NSIndexPath specifying which one it is. In you're using a data structure instead, then put it in you element in that data structure. Then you simply set the property when you create the data structure, something like elt.id=i, and then you access it in the cellForRowAtIndexPath, something like if (elt.id == 0 || elt.id == n-1) where n is the number of rows in your section.
I might have totally missed your question, but if I did, just comment and I'll post again.