I have a UITableView which has expandable custom cells. Multiple cells can be expanded. For that I have an array expandedArray in which I store the indexPath of all cells that are expanded. Initially all cells are collapsed. I want in initial start all cells to be expanded. This is my cellForRowAtIndexPath code :-
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// VISITORS LISTS TV
NSUInteger count = 0;
VisitorsListsCell *vcell = (VisitorsListsCell *) [tableView dequeueReusableCellWithIdentifier:#"VisitorsListsCell"];
if (vcell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"VisitorsListsCell" owner:self options:nil];
vcell = [nib objectAtIndex:0];
}
// Button - Round Corners
[vcell button].layer.cornerRadius = 5;
[vcell button].contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
NSString *text = [visistorsList objectAtIndex:indexPath.row];
// SET PROPERTIES OF CELL
// ....
if ([expandedArray containsObject:indexPath]) {
// Do expanded cell stuff
[vcell setButtonText:[visistorsList objectAtIndex:indexPath.row] withCount:(int)count isExpanded:YES];
[vcell.button setImage:[UIImage imageNamed:#"arrowup.png"] forState:UIControlStateNormal ];
} else {
[vcell setButtonText:[visistorsList objectAtIndex:indexPath.row] withCount:(int)count isExpanded:NO];
[vcell.button setImage:[UIImage imageNamed:#"arrowdown.png"] forState:UIControlStateNormal ];
}
[[vcell listsTableView] reloadData];
return vcell;
}
I want is when the page is loaded, all cells are expanded. I believe for that I need to add indexPath of each cell to expandedArray. But on initial run only, how do I add the indexPath ??? I can't find a way to implement it.
Is it possible, any clue. OR any other way to achieve the goal. Any help is highly appreciated. Thanks.
To add indexPath to your expanded array you should use code below. But much simpler way is to use the same methods that are used in -numberOfSections and numberOfRowsInSection:. Using tableView delegates is not a good way.
- (void)viewDidLoad {
self.expandedArray = [NSMutableArray new];
for (int itSection=0; itSection<[self.tableView numberOfSections]; itSection++) {
for (int itRow=0; itRow<[self.tableView numberOfRowsInSection:itSection]; itRow++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:itRow inSection:itSection];
[self.expandedArray addObject:indexPath];
}
}
}
Btw: the correct way to implement cell from xib:
- (void)viewDidLoad {
....
UINib *nib = [UINib nibWithNibName:NSStringFromClass([VisitorsListsCell class]) bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:#"VisitorsListsCell"];
}
Related
My Custom tableview cell content getting empty after scrolling.So pls help me with this.
My custom cell has 8 buttons and a label.First I'm showing only label that has title and on selection I'm expanding the cell and showing all buttons.So, when I select few buttons and I do scrolling,buttons that I selected getting refreshed or get back to normal state.Here is the code.Pls help me with this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"newFilterCell";
newFilterCell *cell = (newFilterCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"newFilterCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
[cell.contentView.superview setClipsToBounds:YES];
}
cell.QuestionLabel.text=[orderArray objectAtIndex:indexPath.row];
NSArray * arr = [filterInfo objectForKey:[orderArray objectAtIndex:indexPath.row]];
int val = 0;
NSLog(#"%#",cell.subviews);
NSArray * cellViews = cell.contentView.subviews;
if (arr.count>0)
{
for (int i=1; i<=8; i++) {
if (i<=arr.count) {
UIButton * target = (UIButton*)[cell viewWithTag:i];;
[target setTitle:[arr objectAtIndex:i-1]forState:UIControlStateNormal];
[target addTarget:self action:#selector(selectButton:) forControlEvents:UIControlEventTouchUpInside];
}
else
{
[[cell viewWithTag:i] setHidden:YES];
}
}
}
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
return cell;
}
On selection of my cell I'm expanding and reloading the tableview cells.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath==_expandIndexPath) {
_expandIndexPath = nil;
NSMutableArray *modifiedRows = [NSMutableArray array];
[tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationLeft];
}
else
{
selectedRow=indexPath.row;
NSMutableArray *modifiedRows = [NSMutableArray array];
[tableView deselectRowAtIndexPath:indexPath animated:TRUE];
_expandIndexPath = indexPath;
[modifiedRows addObject:indexPath];
[tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationLeft];
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSArray * arr = [filterInfo objectForKey:[orderArray objectAtIndex:indexPath.row]];
if ([indexPath isEqual:_expandIndexPath])
{
if ([[orderArray objectAtIndex:indexPath.row]isEqualToString:#"Height"]||[[orderArray objectAtIndex:indexPath.row]isEqualToString:#"Age"])
{
return 275;
}
else
{
if(arr.count==3)
return 55*arr.count;
else
return 37*arr.count;
}
}
else
return 70;
}
UITableViewCells are getting recycled. That's why its not safe to do it your way. Your data model needs to remember your buttons and other stuff that changed. You need to apply the changes every time the cell gets created.
You need to check in the cellForRowAtIndexPath what button is pressed and then show the view correctly.
You need to remember what happend in the cells with an external data source to apply the changes you want.
In your cellForRowAtIndexPath should be something like a check for a boolean whether or not you show some stuff:
if(button_is_pressed_for_row_5 == true){
button_in_cell.hidden = true;
}else{
button_in_cell.hidden = true;
}
I have a UITableView which I have transformed into horizontal tableview, and a custom UITableViewCell which just has a UIImageView and a UILabel
The problem is, first 5 cells don't show the images, but when I scroll and come back to them, images are shown. No idea what the problem is :(
(picture below, please see the horizontal tableview, ignore the vertical one)
Here's my code in TableViewController Class:
-(void)viewDidLoad
{
//Rotating the tableview angle -PI/2 Rad = -90 Degrees
_friendsTableView.transform= CGAffineTransformMakeRotation(-M_PI_2);
_friendsTableView.translatesAutoresizingMaskIntoConstraints = YES;
CGRect frame = _friendsTableView.frame;
frame.origin.y= _segmentControl.frame.size.height;
frame.origin.x= 0;
frame.size.width = self.view.frame.size.width;
frame.size.height = 105.5;
_friendsTableView.frame= frame;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FriendsTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
if (nil == cell) {
cell = [[FriendsTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier];
}
if(cell)
{
cell.user = cellsArray_[indexPath.row];
cell.transform =CGAffineTransformMakeRotation(M_PI_2);
}
return cell;
}
Now this is my custom cell class code where I set the image and label (_user is a property, so this setter method gets called automatically from cell.user):
-(void)setUser
{
_profileImageView.layer.cornerRadius = _profileImageView.frame.size.width/2;
_user = user;
_nameLabel.text = #"Hello";
[_profileImageView setImage:[UIImage imageNamed:#"placeholder_image"]];
}
Why dont you use Collection View controller instead. Transforming Tableview controller seems not good.
In your code your are not calling your setUser method.
For Custom table view cell you can add following code in
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:strID];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MyCustomCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
}
For my experience this is strictly related to the reusability of the Cells, i had a similar problem once, my solution is before assigning something to an IBOulet, make sure to have it empty, after that, assign it and it should work.
I have a TableView "cannedTv". Each cell contains another TableView "valuesTv". The structure of datasource for cannedTv is { NSString *name, NSArray *valuesArr }. valuesArr is set as datasource for valuesTv. cannedTv is an expandable tableview. Initially just name is displayed, when expanded the valuesTv tableView is displayed. This is my code for didSelectRowAtIndexPath and cellForRowAtIndexPath delegate methods of the tableviews.
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"ROW Selected...TAG = %d", tableView.tag);
selectedValueIndex = -1;
if (tableView == self.cannedTV) {
// USer taps expanded row
if (selectedIndex == indexPath.row) {
selectedIndex = -1;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath ] withRowAnimation:UITableViewRowAnimationFade];
return;
}
// USer taps differnt row
if (selectedIndex != -1) {
NSIndexPath *prevPath = [NSIndexPath indexPathForRow:selectedIndex inSection:0];
selectedIndex = indexPath.row;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:prevPath] withRowAnimation:UITableViewRowAnimationFade];
}
// User taps new row with none expanded
selectedIndex = indexPath.row;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
return;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Table View TAG = %d ROW = %d", tableView.tag, indexPath.row);
if (tableView.tag == 10) { // cell.valuesTv
CannedValueCell *ccell = (CannedValueCell *) [tableView dequeueReusableCellWithIdentifier:#"cannedValueCell"];
if (ccell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedValueCell" owner:self options:nil];
ccell = [nib objectAtIndex:0];
}
// Populate valuesTv
ccell.valueTextView.text = [valuesArray objectAtIndex:indexPath.row];
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(valueTextViewTapped)];
[ccell.valueTextView addGestureRecognizer:gestureRecognizer];
//[ccell.valueTextView sizeToFit];
return ccell;
} else {
// cannedTv
static NSString *cellIdentifier = #"CannedCell";
cell = (CannedCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// Set Datasource & Delegate for valuesTv tableview
cell.valuesTv.dataSource = self;
cell.valuesTv.delegate = self;
if (selectedIndex == indexPath.row) {
// Do expanded cell stuff
[cell.tapInsertLbl setFont:[UIFont fontWithName:#"OpenSans" size:8.0]];
cell.tapInsertLbl.hidden = FALSE;
[cell.contentView setBackgroundColor:[UIColor lightGrayColor]];
[cell.contentView.layer setBorderColor: (__bridge CGColorRef)([UIColor redColor])];
[cell.contentView.layer setBorderWidth:1.0f];
} else {
// Do closed cell stuff
cell.tapInsertLbl.hidden = TRUE;
cell.contentView.backgroundColor = [UIColor whiteColor];
}
if (tableView == self.cannedTV) {
valuesArray = nil;
CannedItem *ci = [candRespList objectAtIndex:indexPath.row];
cell.tagLbl.text = ci.name;
// Set the valuesArray so it be used in populating the valuesTv
valuesArray = [NSMutableArray arrayWithArray: ci.valuesArr];
ci = nil;
} else if (tableView == self.searchDisplayController.searchResultsTableView) {
cell.tagLbl.text = [searchResults objectAtIndex:indexPath.row];
}
[cell.tagLbl sizeToFit];
return cell;
}
}
The problem I am facing is, every time the right datasource is shown shown in valuesTv. At this moment I have 2 arrays in valuesArray with 0th element having 1 object and 1st element having 3 objects (of NSString). Datasource of cannedTv also has 2 rows in it. Sometimes, one time both rows of canndTv shows correct datasource and then both shows same datasource. Sometimes, both rows shows the same datasource. I don't understand why valuesArray can't get the right datasource. While debugging also, I found that at times valuesArray has right datasource but the tableview is showing wrongly, at times the control doesn't go to // Populates valuesTv line and thus previously set valuesArray is only shown. I tried many ways, but can't get the results as expected. Also tried to set valuesArray in didSelectRowAtIndexPath after setting selectedIndex, but that also didn't help.
I am stuck on this since yesterday and can't get thru. Where am I going wrong due to which correct datasource is not shown/reflected on the tableviews. Can you please try to help me out. Any help is highly appreciated. Thanks a lot.
UPDATE :-
Created new datasource & delegate in CannedCell object itself - it contains the valuesTv.
#interface CannedCell : UITableViewCell <UITableViewDataSource, UITableViewDelegate>
#property NSArray *valuesDataSource; // Contains the valuesArray contents
In my VC, removed datasource for cell.values.datasource & cell.valuesTv.delegate lines. In the cellForRowAtIndexPath method for top table, set the datasource like this :-
if (tableView == self.cannedTV) {
valuesArray = nil;
CannedItem *ci = [candRespList objectAtIndex:indexPath.row];
cell.tagLbl.text = ci.name; //[tagsArray objectAtIndex:indexPath.row];
//valuesArray = [NSMutableArray arrayWithArray: ci.valuesArr];
cell.valuesDataSource = ci.valuesArr; // SET DATASOURCE
// This is setting proper array always
ci = nil;
}
Commented the whole if (tableView.tag == 10) { in cellForRowAtIndexPath method.
And in CannedCell :-
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (valuesDataSource != nil)
return valuesDataSource.count;
else
return 0;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CannedValueCell *ccell = (CannedValueCell *) [tableView dequeueReusableCellWithIdentifier:#"cannedValueCell"];
if (ccell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedValueCell" owner:self options:nil];
ccell = [nib objectAtIndex:0];
}
// Populate valuesTv
ccell.valueTextView.text = [valuesDataSource objectAtIndex:indexPath.row];
ccell.valueTextView.tag = indexPath.row;
return ccell;
}
Yet the results are as earlier only. I mean even if array of 3 objects are set as valuedDataSource, the sub-table shows only single object array. What do the think now can be the reason for this ?
As suggested by Paulw11, I created other object to handle TableViewDelegate and datasource for the sub-table. Though that didn't make any difference in the results of the problem.
I set UITableViewDelegate & Datasource in my top table's Cell class. Passed the array to be the datasource of the table.
Importantly, after setting the datasource of sub-table on calling
[cell.valuesTv reloadData]
forced the sub-table to reload its data and that finally did the trick. Till then things weren't working.
I need to show last cell record in tableview cell. Each time i will add new array in table view. After i will reload the tableview cell. Now its showing the record from first cell. Any one please advice to me how will show last cell .
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"TrackDataTimeCell";
TrackDataTimeCell *cell = (TrackDataTimeCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
#autoreleasepool {
[[NSBundle mainBundle] loadNibNamed:#"TrackDataTimeCell" owner:self options:nil];
cell = (TrackDataTimeCell *) cellChallengeFriends;
cellChallengeFriends = nil;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
}
if (!cell) {
cell =(TrackDataTimeCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
cell.accessoryView = activityIndicatorView;
}
NSArray *tableData = [NSArray arrayWithObjects:#"Egg Benedict", #"Mushroom Risotto", nil];
NSArray *tableData1 = [NSArray arrayWithObjects:#"test1", #"test2", nil];
[cell.deleteButton addTarget:self action:#selector(deleteButtonAction:) forControlEvents:UIControlEventTouchUpInside];
cell.deleteButton.tag = indexPath.row;
cell.nameList.text= [tableData objectAtIndex:indexPath.row];
cell.marklist.text= [tableData1 objectAtIndex:indexPath.row];
return cell;
}
- (IBAction)addBillingItems:(id)sender
{
rowVal=rowVal+1;
[nameList addObject: self.codeAdd.text];
[marklist addObject: selectDate];
[tbl reloadData];
}
Please check the example image at http://postimg.org/image/g0dzs0r2p/
I have 5 cells. Now it is showing the first four. After I scroll it shows 5 but I want the last cell to show in first time. Please help me
you can use this to automatically scroll to the last inserted cell.
CGPoint bottomOffset = CGPointMake(0, self.tableView.contentSize.height - self.tableView.bounds.size.height + 150);
if ( bottomOffset.y > 0 ) {
[self.tableView setContentOffset:bottomOffset animated:YES];
}
The 150 value is to add extra space after the cell in tableView. Its optional i.e. you can use it if you want extra space after the cell
You have to set the tableview frame less.If you have iPhone tableview frame then set the frame as cgrectmake(0,0,320,430).If that will not solve your problem then add uitableview:viewforheaderinsection method.Just add uiview and set frame as cgrectmake(0,0,320,1).This will set the frame for the table as well as show all the cells.
There's an easy solution to your problem. Just use the tableView's method
[tbl scrollToRowAtIndexPath:[NSIndexPath indexPathForRow: rowVal inSection:0]
atScrollPosition:UITableViewScrollPositionBottom
animated:YES];
Just call it after [tbl reloadData]
Im using storyboards with prototypecells what looks like:
after that im adding dynamic count of subviews:
white screen
uitableviewcell in app
Problem what sometimes white screen appears on half of uitableviewcell then scrolling.
And cell==nil not called.
Or just explain how to add dynamic count of subviews to uitableviewcell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifer;
cellIdentifer = #"PhotoWorkCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifer];
if (cell == nil){
NSLog(#"is nil");
}
[self addSubviews:cell atIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath];//just setting images and text to views
return cell;
}
-(void)addSubviews:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath{
PhotoWork *work = [self.tableData objectAtIndex:indexPath.row];
PhotoWorkCell *workCell = (PhotoWorkCell*)cell;
for(UIView *view in workCell.contentView.subviews){
if ([view isKindOfClass:[CustomPoints class]])
[view removeFromSuperview];
}
for(int i=0; i <[work.pointsArray count];i++)
{
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"CustomPoints" owner:self options:nil];
CustomPoints *customPoints = [subviewArray objectAtIndex:0];
customPoints.frame = CGRectMake(10, 135+i*customPoints.frame.size.height, customPoints.frame.size.width, customPoints.frame.size.height);
customPoints.label= #"test";
workCell.photoButton.tag = indexPath.row;
[workCell.photoButton addTarget:self action:#selector(onHistory:) forControlEvents:UIControlEventTouchDown];
[workCell.contentView addSubview:customPoints];
}
}
- (CGFloat)tableView:(UITableView *)t heightForRowAtIndexPath:(NSIndexPath *)indexPath {
PhotoWork *work = [self.tableData objectAtIndex:indexPath.row];
return 135+[work.pointsArray count]*57;
}
From Apple's documentation of the dequeueReusableCellWithIdentifier: method:
This method dequeues an existing cell if one is available or creates a new one using the class or nib file you previously registered. If no cell is available for reuse and you did not register a class or nib file, this method returns nil.
This means that, if a cell with that identifier exists, the method will never return nil, but will rather initialise the cell for you through the initializer method initWithStyle:reuseIdentifier:.
Then from the documentation of the UITableViewCell's method prepareForReuse:
The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell.
Which means that you should remove the additional subviews it might have before adding the new ones in your addSubviews:atIndexPath: method.
You could simply do:
NSMutableArray* tmpArray = [NSMutableArray arrayWithArray:workCell.contentView.subviews];
Then, assuming that the only extra subview you add to contentView is this CustomPoints, just do:
[tmpArray removeObject:tmpArray.lastObject];
workCell.contentView.subviews = tmpArray;
If, instead, you want to be safe just do:
__block NSInteger foundIndex = NSNotFound;
[tmpArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[CustomPoints class]]) {
foundIndex = idx;
// stop the enumeration
*stop = YES;
}
}];
if (foundIndex != NSNotFound){
[tmpArray removeObjectAtIndex:foundIndex];
workCell.contentView.subviews = tmpArray;
}
Let me know if this worked for you!