I have 6 sections in a UItableView, every section displays 2 cells, normally, I want it like this:
However, here is what I have:
Every indexPath.row is duplicated in every section.
Here is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"avenir";
Avenir *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell) {
cell =[[Avenir alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.equipea.text=[arrayofClubA objectAtIndex:indexPath.section];
cell.equipeb.text=[arrayofClubB objectAtIndex:indexPath.section ];
return cell;
}
The elements are retrieved from two NSMutableArrays, one for the first left element in cell and the other for the right element cell.
What is wrong?
Thank you for helping.
You need to calculate the correct index from both the row and the column. Since you have two pairs of rows per section, you need to multiply section by two, and add row, which will be either zero or one. The end result should look like this:
NSUinteger pos = indexPath.section*2 + indexPath.row;
cell.equipea.text=[arrayofClubA objectAtIndex:pos];
cell.equipeb.text=[arrayofClubB objectAtIndex:pos];
You're always fetching the identical text for each section, since
cell.equipea.text=[arrayofClubA objectAtIndex:indexPath.section];
always returns the same value for each section (since indexPath.section contains the section's index). Perhaps you wanted to do to the following instead?
cell.equipea.text=[arrayofClubA objectAtIndex:indexPath.row];
Also, for these kind of uses, it might be a lot more straight forward to use the free Sensible TableView framework as it automatically handles displaying your arrays.
Related
In one section of my app I have a UITableView which is working fine right now. I would like to set row 0 cell.textLabel.text to #"Some string". Once row 0 has been set I would then like to load the rest of the rows from an array. Currently on load my array populates the table view but I'm trying to set row 0 as a sticky. The closest example I can think of is a forum topic that is set to stay at the top. My array is constructed of returned data from a web service call.
It's been a while since I've messed with table views, and I'm having a blank on this one.
The table view is 1 section, and I get the rows by counting the elements in the array. Since I would like to create an additional cell (row 0) I would call [array count] + 1. I don't know if this approach is the best one which is why I'm reaching out to the community here.
Any insight or a shove in the right direction would be great at this point.
You're on the right track:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [array count]+1;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if ([indexPath row] == 0) {
// Code for first
[[cell textLabel] setText:#"First cell"];
} else {
[[cell textLabel] setText:[array objectAtIndex:[indexPath row]-1]];
}
return cell;
}
If you want the top of your table to be "sticky", why not consider using that string as a section header or title? In this case, the header stays visible at all times until the next section (e.g. if you had two sections, that is) is fully on the screen.
In any event, in one of my current projects I'm required to do roughly the same thing that you're doing and I have a static string being returned in row 0 (which scrolls off the top of screen when the table view scrolls down).
And in my UITableViewDataSource method, I always add one for the static cell to the number of objects in my array and in my "cellForRowAtIndexPath:" method, I increment the row by one when the indexPath.row is not zero. And if it is zero, I return my static string.
And dark_knight provides some nice sample code that illustrates what I was describing to you. So +1 to him/her.
I want my tableView to show 6 rows with text in it, in this case "Example." As far as I can tell, I have my numberOfSectionsInTableView: and numberOfRowsInSection: set properly. See example code below:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
// Return the number of rows in the section.
return 6;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = #"Example";
return cell;
}
The problem is when you see the image below showing lines for rows that shouldn't/don't exist.
How do I get rid of the lines showing past row 6?
The generally accepted way of doing this is to add a footer view with a frame size of CGRectZero, as such:
[tableView setTableFooterView:[[UIView alloc] initWithFrame:CGRectZero]]
What this does is tell the table that there is a footer, and so it stops displaying separator lines. However, since the footer has a CGRectZero as its frame, nothing gets displayed, and so the visual effect is that the separators simply stop.
Swift Version
The easiest method is to set the tableFooterView property:
override func viewDidLoad() {
super.viewDidLoad()
// This will remove extra separators from tableview
self.tableView.tableFooterView = UIView(frame: CGRect.zero)
}
This is Because of Your Table-view Height. Weather you have Write
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
// Return the number of rows in the section.
return 6;
}
But its show rows According to Table-view Size. If you Dont want to show This extra Lines then Make UITableView Style Plain To Grouped.
Short and simple answer..
self.tableView.tableFooterView = [UIView new];
You could do something along the lines of:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:7 inSection:0];
[self.mytableView cellForRowAtIndexPath:indexPath].hidden = YES;
Im sure there are some better ways but this is the first thing that came to mind.
If you're referring to the light gray lines that appear below the last row, that's simply the default way a UITableView draws the row separator.
You could try changing the Separator style in Interface Builder (see the images below) to see if one of those might be more to your liking.
You didn't say what you do want to see past the last row. If you just want to see the window background, then just embed your table view in a UIView that's just tall enough to show the number of rows you want to see. If you want to see more rows without scrolling, then you would have to adjust the size of that containing view based on the number of rows.
To programmatically remove it, use this:
[yourTableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
It's a lot easier to:
return numberOfSections + 1
return 0 rows in the final section
This keeps it simple!
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.
}
I am puzzled at the following line "static NSString *MyIdentifier = #"MyIdentifier";" in the method: cellForRowAtIndexPath
What does that line do?
Is it just creating a random pointer to an NSString object and assigning it the string?
Why is it being called MyIdentifier, I have seen this in many examples.
#import "AddToFavorites.h"
#implementation AddToFavorites
- (id)initWithStyle:(UITableViewStyle)style {
if (self = [super initWithStyle:style]) {
}
return self;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier:MyIdentifier] autorelease];
}
// Configure the cell
return cell;
}
#end
Here is another example, this one has a different string, CellIdentifier.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"TimeZoneCell";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [self tableviewCellWithReuseIdentifier:CellIdentifier];
}
[self configureCell:cell forIndexPath:indexPath];
return cell;
}
UITableViews can automatically reuse cells to save on memory. To take advantage of this, you must specify a "reuse identifier" which is used by the UITableView to be able to look up existing cells ("dequeueReusbaleCellWithIdentifier") with the same identifier as the one you will create if it can't find an existing cell.
The line creates a static variable (global in that it is shared by all code paths and only initialized once, but local in that you can only access it in this method) to hold the NSString for the identifier. My guess is that this is to ensure that the same pointer is used every time, as comparing pointers is quick and easy, while comparing the contents of strings can take a little bit longer.
For performance as mentioned but also to get help from the compiler in catching spelling errors. There is no checking of your identifier if you use a #""-string literal. The compiler will error out you if you misspell the static identifier. Also codesense will autocomplete the static identifier.
The identifier is a key or tag that allows you to have multiple separate collections of cells for different purposes.
This saves you time and RAM memory - let's find out how.
Let's suppose you had a contacts list application, with two types of contacts, businesses and friends.
If you wanted to display these differently, then you might design two types of cell - one with a picture (friend photo) and name in black font, and one with just the name of the company and no picture or icon.
When the user is using the application, it might need to display 3 friends and 4 companies with names starting with "A-M" at first, so it needs 3 friend cells and 4 company cells. You pass it these, and tag all the friend cells with the identifier "friend", and all the business ones with the identifier "business".
When later on the view changes and just wants names starting with "P-T", you might just have 7 businesses. Ideally you would re-use the cells you already created, so it requests 7 cells with identifier "business", and it turns out you already tagged 4 cells that you already created with "business", so it simply re-uses those. The remaining 3 you already created have the wrong tag, so it ignores those (or maybe deletes them?) and creates 3 new business type cells and gives them the tag "business".
By re-using cells in this way you save on Memory (only need as many cells can be displayed at once of each type), and Performance (no need to go to the effort of allocating and initialising new cells while scrolling up and down). You trade this off against the additional programmer effort of writing this selection code and giving things ids.
They could have automatically tagged cells based upon the objective-C type, but this wouldn't work if you programmatically created the contents of a cell rather than subclassing or using the Interface builder to lay out your cells. So they provide the identifier mechanism instead.
If you only have one type of cell in your table, just call it "Alice" and forget about it.
I have a tableView that needs to be updated after information has been inserted from another view. If I perform a
[self.tableView reloadData];
The very next time I insert more information in another view and try to reload the table, all the currently visible rows are duplicated.
In other words, when I start up the app I have:
tableView:
Row 1
Row 2
Then I submit some information that will also show up in the table and suddenly I have:
tableView
Row 1
Row 2
Row 3 <- info I just added
Row 1
Row 2
My numberOfRowsInSection implementation looks like this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [ItemsController sharedItemsController].count;
}
My cellForRowAtIndexPath implementation looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ItemsController* controller = [ItemsController sharedItemsController];
NSMutableArray* recentItems = controller.listOfRecentItems;
CustomCell *cell = nil;
NSUInteger row = [indexPath row];
if( row < recentItems.count )
{
Items* item = [recentItems objectAtIndex:row];
if( recentCellData == nil )
recentCellData = [[NSMutableDictionary alloc] initWithCapacity:[indexPath length]];
if( [recentCellData count] > 0 )
cell = [recentCellData objectForKey:[NSString stringWithFormat:#"%d", row]];
if (cell == nil) {
UIViewController * view1 = [[UIViewController alloc] initWithNibName:#"CustomCell" bundle:nil];
cell = (CustomCell*)[view1 view];
[recentCellData setObject:cell forKey:[NSString stringWithFormat:#"%d",row]];
}
// do some other stuff here
}
// Set up the cell
return cell;
}
What's the best way to update the table and avoid duplicating the currently visible rows.
Thank in advance for all the help!
The error isn't in how you're reloading the table, it's in how you're providing data to it. Set a breakpoint in the data source methods and the method that adds new rows to see where you're going wrong.
You'll only end up with five items if tableView:numberOfRowsinSection: returns 5. Thats the simple answer to your question, but I see other problems here. I'm wondering why you have this test: row < recentItems.count. Is that array the same thing as [ItemsController sharedItemsController].count? You really need to be using the same array for both methods.
(Also, it's not a syntax error, but you shouldn't use the property syntax for things that aren't declared as properties. You should write [recentItems count] instead.)
I'm also confused by the code you use to set up the cell. Cells are meant to be reusable. That is, you create one cell, then reconfigure it every time in your implementation of tableView:cellForRowAtIndexPath:. Your code creates a cell for each item in your list. This is very memory-inefficient, and will likely crash your program due to insufficient memory on the iPhone if you keep lots of cells in memory like this.
The recommended approach is to call dequeueReusableCellWithIdentifier:. If that returns nil, then you set up a cell using the initWithFrame:reuseIdentifier: initializer. The table view is very smart, and will only ask you to redraw the cell when it needs you to.
Your recentCellData dictionary looks really shaky to me, too. What if you insert an item after the item with key #"2"? All the items with key #"3" onward will need to be shifted one element to the right to work the way you expect. That's a ton of bookkeeping that seems rather unnecessary to me. If you really needed something like this -- and to be clear, I don't think you do -- why wouldn't you use an NSMutableArray, which is much easier to use?
I added a bit more info above.