I have created a UITableView in Storyboard and it is dynamically cell. The problem is that when there are not enough cells to reuse, it randomly empties a few of my cells. I think this is logical, but I want to resolve this problem.
I give an example:
I have a UITableView that is capable to generate 10 cells in a view. But now, I only want to show 8 cells out of 10. It gives no problem when I have only a section. With more than 1 section, it will always empty 2 cells and show 8 cells, but it should show 10 cells.
Can anyone help me with this? Thank you.
Updated With Source code
#pragma mark - List View
#pragma mark - Table View Delegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section {
// Number of rows is the number of time zones in the region for the specified section.
return self.listCollection.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ListCell" forIndexPath:indexPath];
for (id object in cell.contentView.subviews) {
[object removeFromSuperview];
}
[cell.contentView addSubview:[self.listCollection objectAtIndex:indexPath.row]];
return cell;
}
self.listCollection has an array of UIView Object.
Updated With Images:
Image 1
Image 2
It is happening because you are using 2 sections but are not specifying the content for each section separately. To understand this we need to look into the description of addSubview: method from Apple Documentation
This method establishes a strong reference to view and sets its next responder to the receiver, which is its new superview.
Views can have only one superview. If view already has a superview and that view is not the receiver, this method removes the previous superview before making the receiver its new superview.
Have a good look at bold section in second paragraph. As you are using the same view object from listCollection to populate both the section, so newest created cell will become the superview for this view object and previous cell will be left out with nothing but the plane contentView. You can get the real feel by assigning some default color to the cell contentView. Your blank cell will be displaying the color in full content while other cells will display the view
cell.contentView.backgroundColor = [UIColor greenColor];
Solution
I will recommend to use 2 different datasource for both sections as explained below.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section {
// Number of rows is the number of time zones in the region for the specified section.
if(section==0)
return self.listCollection.count;
else
return <row count for section 1>
}
Same way you need to modify the cellForRowAtIndexPath: code for each section.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ListCell" forIndexPath:indexPath];
for (id object in cell.contentView.subviews) {
[object removeFromSuperview];
}
if(section==0)
[cell.contentView addSubview:[self.listCollection objectAtIndex:indexPath.row]];
else
[cell.contentView addSubview:[<second array of views> objectAtIndex:indexPath.row]];
return cell;
}
But if that is your case that you have to use same array, then you can use this technique. Thanks to Rishi for that.
Last but not least, you should use a custom UITableViewCell class as Michal Zygar has suggested in his post.
UIView *tempView = [self.listCollection objectAtIndex:indexPath.row];
NSData *tempArchiveView = [NSKeyedArchiver archivedDataWithRootObject:tempView];
UIView *viewOfSelf = [NSKeyedUnarchiver unarchiveObjectWithData:tempArchiveView];
[cell.contentView addSubview:viewOfSelf];
You are using the UITableView wrong way.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ListCell" forIndexPath:indexPath];
for (id object in cell.contentView.subviews) {
[object removeFromSuperview];
}
[cell.contentView addSubview:[self.listCollection objectAtIndex:indexPath.row]];
return cell;
}
This is highly inapriopriate. Your listCollection should contain model data. In - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath you should configure the cell with this data.
So you should subclass the UITableViewCell for example
#interface ApartmentCell:UITableViewCell
#property (weak, nonatomic) UILabel* floor;
#property (weak, nonatomic) UILabel* unit;
#property (weak, nonatomic) UILabel* type;
#property (weak, nonatomic) UILabel* area;
#property (weak, nonatomic) UILabel* price;
#end
and then
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ApartmentCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ListCell" forIndexPath:indexPath];
Apartment* apartment=self.listCollection[indexPath.row];
cell.unit.text=apartment.unit;
//and so on with other fields
return cell;
}
Related
This is a little hard to explain, but I will try my best. The code below is just some simple code I put together to demonstrate my problem better. When a user selects the second cell (row) for example, the tableview properly changes the cell with this [cell selected:YES]; when tableview reloads. If the second cell is selected and my tableview reloads with more new cells, the wrong cell is selected. The previous selected second cell becomes the fourth or fifth cell when the tableview updates, but the second cell is still selected.
I know this is because of selectIndexPath index row is two. Is there a way around this?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
FriendCell *cell
if (selectedIndexPath.row == indexPath.row )
[cell selected:YES];
else
[cell selected:NO];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
selectIndexPath = indexPath;
}
If your source array can changed (add/remove elements) then you have to keep track of the selected item, not the selected index. You haven't shown what your data source object is, but let's say it was an array of strings:
#property (strong,nonatomic) NSMutableArray *tableData;
#property (strong,nonatomic) NSString *selectedItem;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
FriendCell *cell
if (self.selectedItem != nil && [self.tableData[indexPath.row] isEqualToString:self.selectedItem]) {
[cell selected:YES];
} else {
[cell selected:NO];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.selectedItem = self.tableData[indexPath.row];
}
I have added tableview on top of UIViewController and I have also implemented the UITableViewDataSource and UITableViewDelegate methods
For some reason, I am not getting the value out of didSelectRowAtIndexPath. I get the expected value of indexPath.row but the selectedCell.textLabel.text returns nil.
I am not able to figure out what may be the problem. I am using the dynamic table.
//Array of multiple objects is filled through the delegated arrays and the arrays are properly filled.
#pragma mark - Table view data source
- (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 _arrayForMultipleObjects.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
array = [[NSMutableArray alloc]init];
// Configure the cell...
_singleSelectionLabel= (UILabel *) [cell viewWithTag:2];
_singleSelectionLabel.text=[self.arrayForMultipleObjects objectAtIndex:indexPath.row];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *valu ;
NSLog(#"%i count",indexPath.row);
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
NSString *cellText = selectedCell.textLabel.text;
NSLog(#"%#",cellText);
//Here cellText is null
if([tableView cellForRowAtIndexPath:indexPath].accessoryType==UITableViewCellAccessoryNone)
{ [tableView cellForRowAtIndexPath:indexPath].accessoryType=UITableViewCellAccessoryCheckmark;
valu = cellText;
}
NSLog(#"%#",valu);
}
If you are using storyboard then the default cell style of table cells is Custom which gives you a blank cell where you can add other types of controls to it.
If you dragged a label onto the cell and changed its text, then you are likely using a Custom style of UITableViewCell.
The property textLabel is only available on cells of types other than Custom, such as Basic, or Detailed.
Please double check if this is the case for you.
Edit: As ready suggested you can fetch the text like this:
self.arrayForMultipleObjects[indexPath.row]
So I've used this tutorial to populate a UITableView with custom cells that represent balances. When stepping through the code, I witness the correct amount of cells get created (only 4 with the current test data) and their labels' text set correspondingly.
My problem is when the table is displayed on the screen, only the first row/cell is displayed.
Any insight as to why this could be occurring would be greatly appreciated!
Removed old code.
BalanceCell.h:
#import <UIKit/UIKit.h>
#interface BalanceCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UILabel *nameLabel;
#property (weak, nonatomic) IBOutlet UILabel *amountLabel;
#property (weak, nonatomic) IBOutlet UILabel *modifiedLabel;
#end
EDIT:
My TableView delegate methods are now as follows:
- (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 [_balances count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
BalanceCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[BalanceCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = [_hex colorWithHexString:_themeColourString];
return cell;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(BalanceCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
Balance *item = [_balances objectAtIndex:indexPath.row];
cell.nameLabel.textColor = _themeColour;
cell.nameLabel.text = item.name;
cell.amountLabel.textColor = _themeColour;
cell.amountLabel.text = [NSString stringWithFormat:#"%#%#", item.symbol, item.value];
cell.modifiedLabel.textColor = _themeColour;
cell.modifiedLabel.text = [NSString stringWithFormat:#"%#", item.modified];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 94;
}
As #Sebyddd suggested, I now register the NIB in the viewDidLoad method.
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"BalanceCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
}
These changes may make my code more correct but still only the first cell is displayed.
If cells are getting created and returned properly I guess height is not being set propery. By default I beleive all cells have a height of 44. If your cell exceeds this height it might not get displayed.
You can tell the tableview to adjust height for every cell using (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath delegate
In that delegate just return your cells height.
EDIT:
You are using dequeueReusableCellWithIdentifier: which will return A UITableViewCell object with the associated identifier or nil if no such object exists in the reusable-cell queue.
Instead use dequeueReusableCellWithIdentifier:forIndexPath: which will return A UITableViewCell object with the associated reuse identifier. This method always returns a valid cell.
You need to register the nib/class for that custom cell in viewDidLoad
Try this:
if (cell == nil) {
[tableView registerNib:[UINib nibWithNibName:#"BalanceCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
cell = [[BalanceCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
Use this tuto : http://www.appcoda.com/uitableview-tutorial-storyboard-xcode5/ , your tuto is a bit outdated, and hard to follow !
I have a UITableView in the Groups (Sectioned) mode. Each section of the table contains about 5 cells.
Each cell of the table consists of one UILabel and one UICollectionView. UICollectionView will contain a set of simple items with a horizontal scrolling.
So, I have something like a Grid View and it looks in Storyboard like:
In the Simulator it looks like:
The problems that I can't understand:
When I scroll any of the rows horizontally - some another row (that may be in the same or some another section) can synchronously scrolls with the first one, looks like they have some connection with each other or it's the same UICollectionView!
How can I use different UICollectionView in each UITableViewCell or break that "strange connection"?
My code (Header):
#interface ipAthleteHistoryTableViewController : UIViewController <UICollectionViewDataSource, UITableViewDataSource, UITableViewDelegate>
#property (weak, nonatomic) IBOutlet UITextField *datesRangeFromPeer;
#property (weak, nonatomic) IBOutlet UITextField *datesRangeToPeer;
#property (weak, nonatomic) IBOutlet UITableView *history;
- (IBAction)datesRangeFromPeerEditingExited:(id)sender;
- (IBAction)datesRangeFromPeerSet:(id)sender;
- (IBAction)datesRangeToPeerEditingExited:(id)sender;
- (IBAction)datesRangeToPeerSet:(id)sender;
- (IBAction)todaySpeedButtonPressed:(id)sender;
- (IBAction)weekSpeedButtonPressed:(id)sender;
- (IBAction)monthSpeedButtonPressed:(id)sender;
- (IBAction)quarterSpeedButtonPressed:(id)sender;
#end
Implementation:
- (void)viewDidLoad
{
_dumbNumberOfItems = 24;
_dumbNumberOfItems2 = 5;
[super viewDidLoad];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _dumbNumberOfItems2;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return _dumbNumberOfItems;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 5;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [NSString stringWithFormat:#"12.09.2014:%ld", (long)section];
}
- (UITableView *)findParentTableView:(UITableViewCell *)tableCellView
{
return (UITableView *)[ipNavigationHelper findParentViewOfClass:[UITableView class]
ofChildView:tableCellView];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ipAthleteHistoryTableCell *cell = [tableView dequeueReusableCellWithIdentifier:athleteHistoryChart_ExcerciseTrialsCell forIndexPath:indexPath];
[[cell excerciseTitle] setText:[NSString stringWithFormat:#"Title %li:%li; %ld", [indexPath section], (long)[indexPath row], (long)[cell excerciseTrials]]];
[[cell excerciseTrials] reloadData];
return cell;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ipAthleteHistoryExcerciseTrialCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:athleteHistoryChart_TrialCell
forIndexPath:indexPath];
ipAthleteHistoryTableCell *upCell = [self findParentTableCell:collectionView];
[[cell actualTrial] setText:[ipNumeralsConverter convertToLatinArabNumber:[indexPath row]]];
[[cell actualRepeats] setText:[NSString stringWithFormat:#"%li", [[[self findParentTableView:upCell] indexPathForCell:upCell] section]]];
[[cell targetRepeats] setText:[NSString stringWithFormat:#"%li", [[[self findParentTableView:upCell] indexPathForCell:upCell] row]]];
[[cell workWeight] setText:[NSString stringWithFormat:#"%li", [indexPath row]*10]];
return cell;
}
All the initial work (like IDs registration, cells prototyping, etc.) is done using Storyboard.
Your problem is probably related to the fact that UITableViewCells get reused. So when you scroll and see a new cell, it really is just one of the cells that went away. To deal with your issues, you'll need to make sure the cell is properly initialized in cellForRowAtIndexPath and make sure you override any previous data you had in there.
Ok, finally I've decided to escape from the cells reusing functionality by creating unique Cell IDs and creating it "on-the-fly" without Storyboard prototyping. It looks fine now.
The code is standard:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellId = [NSString stringWithFormat:#"%#-%ld-%ld",
athleteHistoryTable_ExcerciseTrialsCell,
(long)[indexPath section],
(long)[indexPath row]];
ipAthleteHistoryTableCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell == nil)
{
cell = [[ipAthleteHistoryTableCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:cellId];
[[cell excerciseTrials] registerClass:[ipAthleteHistoryExcerciseTrialCell class]
forCellWithReuseIdentifier:athleteHistoryTable_TrialCell];
[[cell excerciseTrials] setDataSource:self];
}
[[cell excerciseTitle] setText:[NSString stringWithFormat:#"Excercise Title %li:%li; %ld",
[indexPath section],
(long)[indexPath row],
(long)[cell excerciseTrials]]];
[[cell excerciseTrials] reloadData];
return cell;
}
now I a beginner in IOS programming, I created a view and placed a tableview inside it, then I created a xib with the template I want to use for the tablecell and made a class for that call. the table cell has a label (set on load of the cell), a switch and a textfield.
The table loads fine, with the labels showing the correct text they should, there is 12 rows that are loaded from the database. sqLite.
the problem:
when I edit the textfield in row 1, the text field in row 9 is edited, and vise versa. when I edit in row 2, row 10 is edited! upto row 4,12 not only the text field, but also toggling the switch toggles.
Some code:
AlertViewController.h
#property (weak, nonatomic) IBOutlet UITableView *table;
AlertViewController.m
#synthesize table;
static NSString *alertCellID=#"AlertViewCell";
----some code here ----
-(void)ViewDidLoad
{
---some code here----
self.table.delegate=self;
self.table.dataSource=self;
if(managedObjectContext==nil)
{
id appDelegate=(id)[[UIApplication sharedApplication] delegate];
managedObjectContext=[appDelegate managedObjectContext];
}
[self loadCategories];
[table registerNib:[UINib nibWithNibName:#"AlertViewCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:alertCellID];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [arrCategories count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Category *cat=[arrCategories objectAtIndex:indexPath.row];
AlertViewCell *cell = [tableView dequeueReusableCellWithIdentifier:alertCellID];
UILabel *lblGroupName=(UILabel *)[cell viewWithTag:101];
lblGroupName.text=cat.nameEn;
UITextField *txtHours=(UITextField *)[cell viewWithTag:103];
txtHours.delegate=self;
return cell;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
Edit:
I dont know is it related to the scroll size fitting 8 records? and how to overcome that?
Ok I solved it as follows:
First the issue was because i m using dequeuereusablecellwithidentifier, which seems to return the same reference every x number of rows, where x is the number of rows that appear on the screen without scrolling, so this made cell 1=cell 9, cell 2=cell 10 and so on.
so to solve it, I had to make the identifier unique, and to solve the issue that the identifier was used to load the registered nib, I had to register the nib with this unique identifier names.. here is the changes:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier=[NSString stringWithFormat:#"%#%d",alertCellID,indexPath.row];
[table registerNib:[UINib nibWithNibName:#"AlertViewCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:identifier];
Category *cat=[arrCategories objectAtIndex:indexPath.row];
AlertViewCell *cell = (AlertViewCell *)[tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
if(!cell)
{
NSArray *topLevelObjects=[[NSBundle mainBundle] loadNibNamed:#"AlertViewCell" owner:self options:nil];
cell=[topLevelObjects objectAtIndex:0];
}
UILabel *lblGroupName=(UILabel *)[cell viewWithTag:101];
lblGroupName.text=cat.nameEn;
UITextField *txtHours=(UITextField *)[cell viewWithTag:103];
txtHours.delegate=self;
return cell;
}