Adding subview to cell.contentView - ios

- (UITableViewCell *)cellForInfoWithCellIdentifier:(NSString *)cellIdentifier
forIndexPath:(NSIndexPath *)indexPath
inTableView:(UITableView *) tableView
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
NSLog(#"%d", cell.contentView.subviews.count);
if (cell.contentView.subviews.count > 0)
{
[cell.contentView.subviews[0] removeFromSuperview];
}
[cell.contentView addSubview:self.viewsForOptions[self.selectedIndex]];
NSLog(#"%d", cell.contentView.subviews.count);
return cell;
}
The above code is called for a certain section and row in cellForRowAtIndexPath. This cell is changed whenever a value of the segmented control object is changed.
The method is below:
- (void)testOptionsValueChanged:(id)sender
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:1];
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
I only have two cells; 1 in each section so the cell == nil condition is always false.
Issue:
When the tableView loads the console logs:
0
1
When I change the value of the segmented control, I still get:
0
1
After some more tries, I basically have height of the second view (for the second index) increasing. I can't seem to get a clue why this is really happening.
EDIT:
Other pieces of code:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0)
{
// test detail view is a constant height
if (indexPath.row == 0)
return 180.0;
}
else if (indexPath.section == 1)
{
// based on the view loaded from the viewForOptions array
if (indexPath.row == 0)
{
return ((UIView *)self.viewsForOptions[self.selectedIndex]).frame.size.height;
}
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *DetailCellIdentifier = #"DetailCell";
static NSString *InfoCellIdentifier = #"InfoCell";
UITableViewCell *cell;
if (indexPath.section == 0)
{
// displays views and test button
if (indexPath.row == 0)
{
cell = [self cellForTestDetailWithCellIdentifier:DetailCellIdentifier forIndexPath:indexPath inTableView:tableView];
}
}
else if (indexPath.section == 1)
{
// display the view for information based on segmented control
if (indexPath.row == 0)
{
cell = [self cellForInfoWithCellIdentifier:InfoCellIdentifier forIndexPath:indexPath inTableView:tableView];
}
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
- (void)viewDidLoad
{
// set the selected index for the options segmented control
self.selectedIndex = 0;
// instantiate all the views for test segmented control options
self.aViewController = [[AViewController alloc] ...];
self.bViewController = [[BViewController alloc] ...];
self.cViewController = [[CViewController alloc] ...];
// add all the views to an array that will be used by the tableview
self.viewsForOptions = #[self.aViewController, self.bViewController, self.cViewController];
}

Your 0,1 to 1,1 is caused by the table view reuse mechanism. Basically, the table will not reuse any cell which is already in use. So when you first populate and first refresh the table, new cells will be created. After that, there are enough cells in the reuse queue that aren't being used that no new ones should need to be created (scrolling May create a couple more).
Your height issue could be caused by auto resizing / layout. When you add the subview you should specify what size it should be and how that size should be changed as the superview (the cell) size is changed. And the cell size is changed (log it when you add the subview).
The height of the cell is one part. Usually you would want to set:
UIView *subview = self.viewsForOptions[self.selectedIndex];
subview.frame = cell.contentView.bounds;
[cell.contentView addSubview:subview];
So that when the cell is resized the subview will have the correct size. But this depends on your auto resizing rules. If you set a layout constraint to pin the height and width then you wouldn't need to set the frame.
In either case, you need to specify what happens to the subview frame when the superview frame changes.
Your issue, I guess, is that the cell is resized before being reused and your subview is still attached. So, it gets resized too. Then, in heightForRowAtIndexPath: you use the height of the subview (now invalid, try logging it) to set the height of the row.
I'd look at changing the implementation of heightForRowAtIndexPath: to use a configuration based on the selected segment rather than the subview frame height.

Related

Scroll to the last two rows of table

when the table has more records than the screen let you see, and the user scroll to the bottom for the last records in the table, the last two rows are not accessible (just in iOS7, in iOS8 everything is fine). Once the user take the finger from screen, the table goes up, and the last two rows are hidden.
This is one of the method used for tableView but i dont know if this come from here:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
// Configure the cell...
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
if (selectionCategMsg == 0)
{
if ([self.tableauMsgReceived count] < 1) {
cell.textLabel.text = NSLocalizedString(#"noMessagesYet", nil); //#"No messages yet !";
}
else
{
cell.textLabel.text = [NSString stringWithFormat:#"%#", [self.tableauMsgReceived objectAtIndex:indexPath.row]];
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
}
else
{
if ([self.tableauMsgSent count] < 1) {
cell.textLabel.text = NSLocalizedString(#"noReplyYet", nil);
}
else
{
cell.textLabel.text = [NSString stringWithUTF8String:[NSString stringWithFormat:#"%#", [self.tableauMsgSent objectAtIndex:indexPath.row]].UTF8String];
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
}
[cell setAccessoryType:UITableViewCellAccessoryNone];
return cell;
}
Can someone give an advice ? Thank you.
THIS IS THE SOLUTION THAT I FOUND:
// -> SET TABLE FRAME
CGFloat sideMargin = 0;
CGFloat originX = sideMargin;
CGFloat topBottomMargin = 100;
CGFloat originY = topBottomMargin;
// Width based on view size
CGFloat sizeWidth = self.view.bounds.size.width);
// Height based on view size
CGFloat sizeHeight = (self.view.bounds.size.height - topBottomMargin);
self.myTableView.frame = CGRectMake(originX, originY, sizeWidth, sizeHeight);
// <- END SET tableView frame
UITableView has the method scrollToRowAtIndexPath:atScrollPosition:animated:. That method lets you scroll the table view so a given indexPath is visible. Is that what you're looking for?
EDIT:
#Daij-Djan's post is a good theory on what's wrong. If your table view goes off the screen then it will position the last couple of cells inside it's view, but that view won't be visible. You're going to need to do some debugging to figure out what's wrong. You might try setting the table view's layer's borderWidth to a non-zero value so you can see the bounds of the table view.
In your viewDidLoad, add code like this:
myTableView.layer.borderWidth = 1;
myTableView.layer.borderColor= [UIColor redColor].CGColor;
(Where you'd replace "myTableView" with the name of your table view instance variable or property.)
You'll have to import QuartzCore.h in order for the code above to compile:
#import <QuartzCore/QuartzCore.h>

UITableView Scroll automatically to the top after begin/end updates call for adjust height

I have a TableViewController that initialize my cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FIDPostTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell for this indexPath
[cell updateFonts];
[cell loadDataWithPost:[self.posts objectAtIndex:indexPath.item]];
cell.parentTableViewController = self;
cell.indexPath = indexPath;
[cell draw];
if(self.selectedIndex == indexPath.row){
//Do expand cell stuff
} else{
//DO closed cell stuff
}
return cell;
}
and that responds to heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *reuseIdentifier = CellIdentifier;
FIDPostTableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
if (!cell) {
cell = [[FIDPostTableViewCell alloc] init];
[self.offscreenCells setObject:cell forKey:reuseIdentifier];
}
// Configure the cell for this indexPath
[cell updateFonts];
[cell loadDataForHeightCalculationWithPost:[self.posts objectAtIndex:indexPath.item]];
[cell draw];
if(self.selectedIndex == indexPath.row){
return [cell calculateHeight] + 100;
} else{
return [cell calculateHeight];
}
}
self.selectedIndex is a int local variable of TableViewController
Each custom cell have inside a button, that respond to a selector when touched, this is my CustomViewCell code:
self.expandSocialAction = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.expandSocialAction.backgroundColor = [FIDUIHelper fideniaLightBlue];
[self.expandSocialAction addTarget:self action:#selector(selectRow:) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:self.expandSocialAction];
and then:
-(void)selectRow:(id)sender{
if(self.parentTableViewController.selectedIndex == self.indexPath.row){
self.parentTableViewController.selectedIndex = -1;
} else{
self.parentTableViewController.selectedIndex = self.indexPath.row;
}
[self.parentTableViewController.tableView beginUpdates];
[self.parentTableViewController.tableView endUpdates];
}
The cell have a pointer to parent tableViewContorller: self.parentTableViewController.
All work fine, after beginUpdate and endUpdates call, the method heightForRowAtIndexPath is called ( i put a break point in ) and also che cell have the right height.
If i click the button on the first or second row the cell animate and change height fine, but if i scroll down the table and for example i click on the 6th row, the height change but the tableView scroll automatically to the first or second element.
Any suggestion?
Regards,
I have a feeling that the heightForRowAtIndexPath: method is part of the problem. Instead of performing the calculation, try to just use two constants in that method: one for expanded, one for collapsed. I'm wondering if the method is taking too long to execute and then thinks the height of the rows are 0, then the calculation finally returns, but the UITableView scroll position is not updated.
Another suggestion might be that your estimatedHeight and heightFor methods are returning two vastly different values.
check if you have used ScrollTORowAtIndexPAth someWhere in your code

UILabel upon a few UITableViewCells

Am I able to create a UILabel that is layouted upon many UITableViewCells?
I'm trying to make something like (that is just one section of my UITableView, each section can have one or more rows):
---------------------------------------------
| Multi-lined label | row1 values |
| with some useless | row2 values |
| text | row3 values |
---------------------------------------------
I managed to create a UILabel (in the first row of a section) that is multi-lined and is not clipping to bounds. That works really well (it was a bit tricky to count each sections row heights, but doable) besides one case: when I'm scrolling UITableView from bottom to top - UITableView renders last row (without UILabel) so it has "no evidence" of having UILabel (because it is maintained in the first row of section). Can I force some kind of relayouting first cell in section? I tried reloadRowsAtIndexPaths:withRowAnimation: with first row in each section whenever I layouted not first cell in section but it gave me layouting errors that I really do not understand. Or maybe there is another idea to do so?
-- EDITED
To be clear: I have a custom UITableViewCell with an IB view, it has a few labels that each row consist of and a label named labelName that I want to be "multi-lined" along rows in that section. LabelName.text is empty for each row besides first one in each section.
I am adding somescreenshots:
Good screenshot - when I am scrolling to bottom I'm getting proper effect:
Bad screenshot - when I am scrolling up, UITableView renders last row of section firstly, and afterwards renders upper rows - that gives effect of cut label (because multi-line label is in the first row)
I am not sure if code here will add anything to question - it is rather simple and almost whole logic is in tableView:heightForRowAtIndexPath. I can only present how do I create custom UITableViewCell:
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[CustomTableViewCell reuseIdentifier]];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithOwner:self];
cell.clipsToBounds = NO;
cell.labelName.clipsToBounds = NO;
cell.contentView.superview.clipsToBounds = NO;
}
-- EDIT 2
Here is most of the code:
- (void) reloadData
{
NSUInteger index = 0;
for (NSDictionary *object in self.list) {
CGFloat height = [[object objectForKey:#"name"] sizeWithFont:self.labelFont constrainedToSize:self.labelSize].height;
[self.labelHeights addObject:NSNumberFloat(ceilf(height))];
index++;
}
[self.tableView reloadData];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *object = [self.list objectAtIndex:indexPath.section];
CGFloat height = [[self.labelHeights objectAtIndex:indexPath.section] floatValue];
NSUInteger count = [[object objectForKey:#"list"] count];
CGFloat cellHeight = 30.f;
if((indexPath.row + 1) == count){
cellHeight = MAX(8.f + height - 30.f * indexPath.row, 30.f);
}
return cellHeight;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.list count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[self.list objectAtIndex:section] objectForKey:#"list"] count];
}
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *person = [self.list objectAtIndex:indexPath.section];
NSDictionary *object = [[person objectForKey:#"list"] objectAtIndex:indexPath.row];
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[CustomTableViewCell reuseIdentifier]];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithOwner:self];
cell.backgroundColor = [UIColor clearColor];
cell.userInteractionEnabled = NO;
cell.clipsToBounds = NO;
cell.labelName.clipsToBounds = NO;
[cell.contentView.superview setClipsToBounds:NO];
}
if(indexPath.row == 0){
cell.labelName.text = [person objectForKey:#"name"];
CGFloat height = [[self.labelHeights objectAtIndex:indexPath.section] floatValue];
cell.labelName.numberOfLines = (int)(height / self.fontSizeHeight);
cell.labelName.frame = CGRectChangeHeight(cell.labelName.frame, height);
}
else{
cell.labelName.text = #"";
}
CGFloat cellHeight = [self tableView:self.tableView heightForRowAtIndexPath:indexPath];
cell.borderTop.hidden = YES;
cell.borderBottom.hidden = YES;
cell.borderBottomSmall.hidden = NO;
if(indexPath.row == 0){
cell.borderTop.hidden = NO;
}
if(indexPath.row + 1 == [[person objectForKey:#"list"] count]){
cell.borderBottom.hidden = NO;
cell.borderBottom.frame = CGRectChangeY(cell.borderBottom.frame, cellHeight - 1.f);
cell.borderBottomSmall.hidden = YES;
}
cell.labelDate.text = [object objectForKey:#"date"];
cell.labelPremium.text = [[object objectForKey:#"premium"];
return cell;
}
-- PARTIAL ANSWER
I managed to create a hack, that makes multi-line UILabel visibile when scrolling bottom to up at some point:
- (void) scrollViewDidScroll:(UIScrollView *)scrollView
{
NSArray *cells = [self.tableView visibleCells];
UITableViewCell *cell = [cells objectAtIndex:0];
[cell.superview bringSubviewToFront:cell];
}
I noticed that the part of the UILabel is covered by a row thats below of the UILabels row and that hack makes it would be properly displayed. But it has a drawback, when scrolling slowly from bottom to top it generates a flicker when label is created (part of it should be visible before real creation of UILabel).
Up mentioned answers are not solutions, but "hacks".
In the cell == nil block should be only the initialization.
You should not add any subviews in cell in cellForRowAtIndexPath.
The reason is simple: I will reuse a cell with some labels already added and add a new label.
Either use the default cell.textLabel, either create a subclass for UITableViewCell, with a
-(void)setData:(dictionary or string)object;
and in implementation just set the proper data to proper UI controls.
Add/create controls either in init method in the subclass, or in IB/Storyboard.
Call the dictionary or string should be picked in correspondence to indexPath, so you will always get proper data for proper cell at proper indexPath.
Try This
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellId = #"cellId";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell==nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
}
for (UIView *subview in [cell.contentView subviews]) {
[subview removeFromSuperview];
}
/// your UI on cell goes here
return cell;
}

Make UILabel Fit Height in a Dynamic Sized UITableViewCell

I have the following setup in my app:
I have a UITableView which has animated height change, when a cell is selected. In the cell is I have a label, which text I want to be on one line with truncation, when the cell is not selected and then when it is selected (the cell height get's bigger) I want the text (UILabel) to be on multiple lines, based on the text.
This is my code at the moment:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if(self.selectedIndex != nil && [self.selectedIndex isEqual:indexPath]) {
return kCellHeight * 3.9;
}
return kCellHeight;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
ProductCell *cell = (ProductCell *) [tableView cellForRowAtIndexPath:indexPath];
if (![indexPath isEqual:self.selectedIndex]) {
cell.addButton.hidden = NO;
cell.descLabel.numberOfLines = 0;
cell.descLabel.lineBreakMode = NSLineBreakByWordWrapping;
[cell.descLabel sizeToFit];
tableView.scrollEnabled = NO;
self.selectedIndex = indexPath;
}else {
self.selectedIndex = nil;
cell.descLabel.lineBreakMode = NSLineBreakByTruncatingTail;
tableView.scrollEnabled = YES;
cell.addButton.hidden = YES;
}
[tableView beginUpdates];
[tableView endUpdates];
}
The first part (when the cell is selected, expanded and the height gets bigger) the text fits as I want, but the second part (when the cell is contracted, and the cell height get back to the normal height) the text does not fit to the new height and is truncated as I want it to, it just stays multi lined. How can I fix this? So when the cell is selected the text gets multilined and when the cell is deselected the text gets back to its normal truncated state?
EDIT:
Override
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
and add below line in this function
cell.descLabel.numberOfLines = 1;

Table Cell not always showing Hidden Content after Unhiding

I have a table with a custom cell, in the cells are two UIViews the top one is always displayed and the bottom one is hidden.
When the cellCellForRowAtIndexPath is called the cells are all reduced in height to just show the top UIView.
When the user clicks the cell then that cell is expanded and then I unhide the second UIView. The problem is that sometimes the second UIView does not appear on first click on the cell.
Clicking the cell again makes it all disappear and then another click and it always appears perfectly. Scroll down and then pick another and first click it may not appear or will be in wrong place, two more licks and it is perfect.
I think it is a ReusableCell issue but I have no idea how to get around it.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cellID = #"Cell";
customCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
// Test to see if the Cell has already been built
if (cell == nil)
{
cell = [[customCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
// Check if the selected Cell is being drawn and unhide the pull down view and adjust y origin
if(indexPath.row == selectedCell)
{
// Unhide the view
cell.pullDownView.hidden = NO;
CGRect tempFrame = cell.pullDownView.frame;
tempFrame.origin.y = [[secondViewY objectAtIndex:indexPath.row] floatValue];
cell.pullDownView.frame = tempFrame;
// Image in second view
cell.mainImage2.image = [theImages objectAtIndex:indexPath.row];
}
else
{
cell.pullDownView.hidden = YES;
}
// Image in first view
cell.mainImage.image = [theImages objectAtIndex:indexPath.row];
return cell;
}
So clicking a cell calls the didSelectRow which looks like this
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(selectedCell == indexPath.row)
{
selectedCell = -1;
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
else
{
selectedCell = indexPath.row;
}
// [tableView reloadData];
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
[tableView endUpdates];
}
I have tried both reloadData and BeginUpdates as you can see but this still does not work. Is this because I am hiding/unhiding should I do Alpha to 0 and then 1 when selected? Is this a reusable cell thing?
Ohhh for completeness. You may notice I have a table where all the heights are different which is why I change the Y of the second UIView.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
float returnHeightValue;
if(indexPath.row == selectedCell)
{
returnHeightValue = [[selectedCellHeights objectAtIndex:indexPath.row] floatValue];
}
else
{
returnHeightValue = [[normalCellHeights objectAtIndex:indexPath.row] floatValue];
}
return returnHeightValue;
}
Okay I have changed how I address the items. I have removed the UIView (which they were all embedded in) and instead I now have in my .h of my custom cell.
#property (nonatomic, strong) IBOutletCollection(UILabel) NSArray *pullDownItems;
#property (weak, nonatomic) IBOutlet UIImageView *mainImage;
Rather than
#property (weak, nonatomic) IBOutlet UIView *pullDownView;
Okay so now I have to unhide and move two items rather than one but that is way better than ding them individually would have been a pain (there is around 10 UILabels). I have enumerate the array like so;
for(UILabel *obj in cell.pullDownItems)
{
obj.hidden = NO;
tempFrame = obj.frame;
tempFrame.origin.y = tempFrame.origin.y - tempYOffset;
obj.frame = tempFrame;
}
Any new items I just just attach.
Okay changed it all around as hiding and unhiding content on a cell that changed did not work so well (got artifacts left in the table and sometimes things would not show up).
I am now creating two custom cells one has all the additional objects when the cell is selected the other does not.
So far seems to be much better way of doing it.

Resources