Spacing between cells on UITableView with Custom UITableViewCell - iphone

I have a UITableView loading a custom UITableViewCell from a XIB file. Everything is working fine, but my layout requires that the cells (all inside one single section) have spacing between them.
Any chance this can be done without having to raise the ROW height?
how it is now
how it's supossed to be
EDIT:
this is how the code is today
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [[self.cards valueForKeyPath:#"cards"] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
[ccTableView setBackgroundColor:[UIColor clearColor]];
cardsCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cardsCell"];
if(cell == nil){
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"cardsCell" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
NSString *nmCard = [[self.cards valueForKeyPath:#"cards.name"] objectAtIndex:indexPath.row];
cell.descCardLabel.text = nmCard;
return cell;
}

There's actually a pretty easy solution to it that I found, if you're using custom uitableviewcell classes.
In the cell class, add this code:
- (void)setFrame:(CGRect)frame {
frame.origin.y += 4;
frame.size.height -= 2 * 4;
[super setFrame:frame];
}
This will give you a 4pt buffer within the cell height you put into the uitablview class you are calling this cell in. Obviously, you now have to compensate for that less space in the cell when putting in labels and images, but it works. You can also do the same thing on the x-axis to make the width of the cell smaller. I've done this in my app to show background images behind my cells.

If you can't change the cell's height, the solution is to use invisible intermediate
cells of the required height. You'll need to recalculate indexes at table view delegate and datasource in that case.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CELL_ID2 = #"SOME_STUPID_ID2";
// even rows will be invisible
if (indexPath.row % 2 == 1)
{
UITableViewCell * cell2 = [tableView dequeueReusableCellWithIdentifier:CELL_ID2];
if (cell2 == nil)
{
cell2 = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CELL_ID2];
[cell2.contentView setAlpha:0];
[cell2 setUserInteractionEnabled:NO]; // prevent selection and other stuff
}
return cell2;
}
[ccTableView setBackgroundColor:[UIColor clearColor]];
cardsCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cardsCell"];
if(cell == nil){
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"cardsCell" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
// Use indexPath.row/2 instead of indexPath.row for the visible section to get the correct datasource index (number of rows is increased to add the invisible rows)
NSString *nmCard = [[self.cards valueForKeyPath:#"cards.name"] objectAtIndex:(indexPath.row/2)];
cell.descCardLabel.text = nmCard;
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
// two times minus one (invisible at even rows => visibleCount == invisibleCount+1)
return [[self.cards valueForKeyPath:#"cards"] count] * 2 - 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row % 2 == 1)
return 40;
return 162;
}
You will also need to recalculate the indexPath.row for :didSelectRowAtIndexPath: and other methods where it is used.

Although #A-Live is technically correct, in order to prevent other issues, ex: you forget to put a indexPath.row/2 somewhere, you can do the following (which involves no programming):
Say for example your UITableViewCell height normally is 90 points and you want a 10 point spacing in between each cell. Make your UITableViewCell 100 points high and just make the bottom 10 points of the UITableViewCell blank (no objects of any sort). Then click the UITableViewCell in interface builder and set the backgroundColor to whatever you want the spacing area's color to be.
Voila! You got spacing in between each cell with a little work that is much easier than programming /2 everywhere!

If you are looking for a solution in swift, this answer from reddit worked perfectly for me.
class CustomTableViewCell: UITableViewCell {
override var frame: CGRect {
get {
return super.frame
}
set (newFrame) {
var frame = newFrame
frame.origin.y += 4
frame.size.height -= 2 * 4
super.frame = frame
}
}
}

Related

UITableView Custom Cell is showing only one line of text initially for all the cells

I am using a custom cell to show the post of Facebook pages using Facebook Graph API. I am getting the JSON data, and i am converting all the post into the msgArray, then passing this msgArray to my view Controller in which i have my table view and then using this msgArray to populate the data in the custom cell. But when i am showing the data in the custom cell it is loading only one line of text in the label(postText: labelName) for all the cells of the table view and when i am scrolling it up and down then few of the cell are showing complete text and few are only showing one line of text which depends how much i am waiting for the text to get loaded.
Below is my code for the table view:
(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [msgArray count];
}
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier=#"Cell";
CustomCell *cell= [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
//UITableViewCell *tablecell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell==nil)
{
NSArray *nib=[[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
cell=[nib objectAtIndex:0];
}
//if(cell==nil)
// {
// cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
//}
cell.postText.lineBreakMode=NSLineBreakByWordWrapping;
cell.postText.numberOfLines=0;
[cell.postText sizeToFit];
cell.postText.text=[msgArray objectAtIndex:indexPath.row];
return cell;
}
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//This function is calculating my height for each row depending upon text length which is correct.
NSString *text=[msgArray objectAtIndex:indexPath.row];
CGSize constraint=CGSizeMake(300,20000.0f);
CGSize size=[text sizeWithFont:[UIFont systemFontOfSize:17.0] constrainedToSize:constraint lineBreakMode:NSLineBreakByWordWrapping];
CGFloat height=MAX(size.height,44.0f);
return height+(100*2);
}
One more thing, If i am using Default UITableViewCell, and set the text as cell.textLabel.text=[msgArray objectatIndex:indexPath.row];, it is showing data properly.
Please help me in getting out of this. Thanks in advance.
Did you try to change its numberOfLines property to 0? By default it's 1.
cell.textLabel.numberOfLines = 0;

iOS - UITableView Changing Datasourse

I was wondering if anyone knows how to solve this problem:
I have a UITableview, where the datasource can continue to grow. The issue is when I add elements to the data source, the table cells get messed up.
Each table cell has a text field where the user can enter data into it, but whenever I add data to the datasource the comments replicate to other cells.
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"ImageTableCell";
ImageTableCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
Picture *aPicture = (Picture *) [self.imageData objectAtIndex:[indexPath row]];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"ImageTableCell" owner:self options:NULL];
cell = (ImageTableCell *)[nib objectAtIndex:0];
}
NSLog(#"comment %# index %d", aPicture.comment, [indexPath row]);
cell.cellImage.image = aPicture.picture;
cell.commentField.delegate = self;
cell.commentField.text = aPicture.comment;
cell.index = [indexPath row];
cell.tag = [indexPath row];
return cell;
}
self.imagedata is a NSMutabaleArray.
EDIT
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.imageData count];
}
-(void) reloadImages:(NSNotification *) aNotification {
self.imageData = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).currentUserData.images;
[self.imageTableView reloadData];
}
a screenshot http://www.mikerizzello.com/pic.png
Thanks
EDIT:
I think you might have a bad MVC pattern. Here's what I would do:
Take your custom cell, ImageViewCell, and make that class your textField delegate. Add a property for your custom Picture object, and set that in cellForRow...
Then, in your textFieldDelegate methods inside the custom cell class, you can change set the contents of your PictureObject there. I have a feeling that you're handling text input in the viewController, and the index of the data object in your array is getting mixed up and you're applying the comment field text to the wrong item in the array.
EDIT
I think it has something to do with cell reuse. The contents of your cell are not getting wiped out upon reusing a cell.
Try this block before you return the cell:
if (!aPicture.comment) {
cell.commentField.text = #"";
} else {
cell.commentField.text = aPicture.comment;
}
Have you try with
if (cell == nil) {
cell = (ImageTableCell*)[[ImageTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
Anyway you can also set different reuseIdentifier for each of cells but it will be bad idea, I would like to help you but i think need to run this project.
Please let me know if code above give better solution.

How to calculate heightForRowAtIndexPath for cells which setup via switch in cellForRowAtIndexPath.

I setup my cells like this:
So, a couple of switches, define the cells, because the data is not in the list of objects, but a set of information which should be displayed in a tableView in some different ways.
-(UITableViewCell *)value1CellForTableView:(UITableView *)tableView {
static NSString *CellIdentifierValue1 = #"Value1Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierValue1];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifierValue1];
cell.detailTextLabel.textAlignment = UITextAlignmentLeft;
cell.textLabel.textAlignment = UITextAlignmentLeft;
}
return cell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = nil;
switch (indexPath.section) {
case 0:
//Coupon
switch (indexPath.row) {
case 0:
//Couponcode
cell = [self value1CellForTableView:tableView];
cell.textLabel.text = #"Code";
cell.detailTextLabel.text = presentedCoupon.couponNr;
break;
case 1:
//Coupondescription
cell = [self value1CellForTableView:tableView];
cell.detailTextLabel.text = presentedCoupon.couponDescription;
cell.detailTextLabel.numberOfLines = 0;
cell.textLabel.text =#"Ihr Vorteil";
break;
}
break;
case 1:
//Productinfo
switch (indexPath.row) {
case 0:
cell = [self defaultCellForTableView:tableView];
cell.imageView.image = [UIImage imageWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: [presentedCoupon.refProdName stringByAppendingString:#".png"]]];
cell.textLabel.text = presentedCoupon.refProdName;
break;
}
break;
case 2:
//Shopinfo
switch (indexPath.row) {
case 0:
cell = [self defaultCellForTableView:tableView];
cell.textLabel.text = ((Shop*)presentedCoupon.refShop).name;
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
break;
}
break;
}
if (cell == nil) {
cell = [self defaultCellForTableView:tableView];
cell.textLabel.text = #"Stanni";
}
[cell layoutIfNeeded];
return cell;
}
And i calculate the height like this.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"height");
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
CGFloat height = 24 + [cell.detailTextLabel.text sizeWithFont:cell.detailTextLabel.font constrainedToSize: CGSizeMake(cell.detailTextLabel.frame.size.width, 1000.0f) lineBreakMode:cell.detailTextLabel.lineBreakMode].height;
return MAX(height, 44.0f);
Problem:
The problem is, as mentioned in many threads, and also visible in my log, that the height of each cell, (visible or not) is asked at the initialization of the table view. So in bigger 100+ lists, also 100+ cells are created --> wasted at startup.
Ist there another possibility to get this information when the cell is set up like this? Is it really neccessary to built the switch case sturcture again in heightForRowAtIndexPath to avoid these calls and though get the right heights for each cell?
Would it be better to hold a "datasource-list" with the single information of each cell?
But how to handle the different cell styles, custom cells.
The method
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
is called for each cell before the method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
So in the first method, cells are not yet created, and you should not try to access them.
When you create a tableView, the data source is first asked for the number of row. Then for each row, the data source is asked for the height of the row, so that the tableView know the total height of it's content, and then finally, the data source is asked for the cell (actual view) to display.
In your case, i would built the switch case structure again. Concerning the "datasource-list", I have never done it before, so maybe it's a better solution.
Two possibilities:
How many cells you have? If you have a small number of cells (which seems to be the case here), you don't need cell reusing! In general, cell reusing is overused.
Just create cells when you are creating your controller or when you have updated your data and put them into a NSArray.
Then you can return them from tableView:cellForRowAtIndexPath: or measure their height.
Create a separate method that returns cell text/font and use it in both delegate methods instead of reading the information from the cell directly.
If you have an array of strings in objects and are using the standard table cell then try this iOS 7 compatible magic:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString* text = [self.objects objectAtIndex:indexPath.row];
NSAttributedString * attributedString = [[NSAttributedString alloc] initWithString:text attributes:
#{ NSFontAttributeName: [UIFont systemFontOfSize:18]}];
//its not possible to get the cell label width since this method is called before cellForRow so best we can do
//is get the table width and subtract the default extra space on either side of the label.
CGSize constraintSize = CGSizeMake(tableView.frame.size.width - 30, MAXFLOAT);
CGRect rect = [attributedString boundingRectWithSize:constraintSize options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];
//Add back in the extra padding above and below label on table cell.
rect.size.height = rect.size.height + 23;
//if height is smaller than a normal row set it to the normal cell height, otherwise return the bigger dynamic height.
return (rect.size.height < 44 ? 44 : rect.size.height);
}
As I see – you have only two cell types.
So you might have a #property for each type of cell (please take a look to an example below):
static NSString * const kCellIdentifier = #"kCellIdentifier";
#interface ...
#property (nonatomic, retain) UITableViewCell *cell;
#end
#implementation
#synthesize cell = cell_;
...
- (UITableViewCell *)cell {
if (!cell_) {
cell_ = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kCellIdentifier];
}
return cell_;
}
- (UITableViewCell *)cellForTableView:(UITableView *)tableView {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
if (!cell) {
//put it to autorelease pool to avoid EXC_BAD_ACCESS
cell = [[self.cell retain] autorelease];
self.cell = nil;
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = self.cell;
CGFloat height = 24 + [#"text" sizeWithFont:cell.detailTextLabel.font constrainedToSize: (CGSize){cell.detailTextLabel.frame.size.width, CGFLOAT_MAX} lineBreakMode:cell.detailTextLabel.lineBreakMode].height;
return MAX(height, 44.0f);
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self cellForTableView:tableView];
cell.detailTextLabel = #"text";
return cell;
}
So the cell would be initialized only once in the beginning.

Multiple reuse identifiers in a UITableView

I have a table view where the cells have a variable height. This causes problems with having a reuse identifier but I would really like the cache for UITableViewCells that Apple gave me. Therefore I tried making a variable reuse identifier and it seems but I'm not sure if it's the right way.
Can anyone tell me if I'm handling multiple reuse identifiers right?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
float height = [self calcCellHeight:indexPath];
NSString *CellIdentifier = [NSString stringWithFormat:#"TextCell_%f", height];
TextCell *textCell = (TextCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (textCell == nil) {
textCell = [[TextCell alloc] initWithHeight:height reuseIdentifier:CellIdentifier];
}
return textCell;
}
The best way to do this is probably to set the height every time you get the cell, and then recalculate all the internal cell frames in that setter. Here's an example:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
float height = [self calcCellHeight:indexPath];
TextCell *textCell = (TextCell *)[tableView dequeueReusableCellWithIdentifier: #"AlwaysTheSame"];
if (textCell == nil) {
textCell = [[[TextCell alloc] init] autorelease];
}
[textCell setHeight: height];
return textCell;
}
Also, note you forgot your autorelease the first time through.
// TextCell
- (id) init {
if ([super initWithStyle: UITableViewCellStyleDefault
reuseIdentifier: #"AlwaysTheSame"]) {
self.myInternalStuff = [[[MyInternalStuff alloc] initWithFrame: CGRectZero] autorelease];
// I don't know what size I am yet!
}
return self;
}
- (void) setHeight: (CGFloat) height {
self.myInternalStuff.frame = CGRectMake(0, 0, 100, height);
// I know what height I am now, so I can lay myself out!
}
You can do that, but if there's much variability in the height of the cells it's going to pretty much defeat the purpose of caching cells. After all, you won't save much time or memory if you have fifty different sizes of cell in your cache.
If you're going to do that, I'd suggest using an int rather than a float to construct the identifier.

Show blank UITableViewCells in custom UITableView

I am trying to customize a UITableView. So far, it looks good. But when I use a custom UITableViewCell sub-class, I do not get the blank table cells when there's only 3 cells:
alt text http://img193.imageshack.us/img193/2450/picture1zh.png
Using the default TableView style I can get the repeating blank rows to fill the view (for example, the mail application has this). I tried to set a backgroundColor pattern on the UITableView to the same tile background:
UIColor *color = [UIColor colorWithPatternImage:[UIImage imageNamed:#"score-cell-bg.png"]];
moneyTableView.backgroundColor = color;
...but the tile starts a bit before the TableView's top, so the tile is off once the actual cell's are done displaying:
alt text http://img707.imageshack.us/img707/8445/picture2jyo.png
How can I customize my tableview but still keep the blank rows if there's less rows than fill a page?
Did you by chance remove the background color and separator style? If you did, that could be why there are no extra cells. I would think the default UITableView doesn't add more cells really, it just has the separator style to create that illusion and because it has a white background, they look like cells.
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
If that's not the case, you could always try adding extra cells that can't be selected:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return ([source count] <= 7) ? 7 : [source count];
}
- (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] autorelease];
}
// Set all labels to be blank
if([source count] <= 7 && indexPath.row > [source count]) {
cell.textLabel.text = #"";
cell.selectionStyle = UITableViewCellSelectionStyleNone;
} else {
cell.textLabel.text = [source objectAtIndex:indexPath.row];
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
}
return cell;
}
I believe the slight misalignment of the top row is caused by the vertical scroll bounce of the tableview. If you turn that off, the top row should align properly.
Also, you can just return a cell height that will encompass the tile in tableview:cellHeightForRow:. That works on the default cells nicely.