iOS7: UILabel Text in Custom Cell does not appear without GCD - ios

Text in the Label of a Custom Cell appears with a block. Is there a better way to accomplish this?
CustomCell.h
#interface CustomCell : UITableViewCell
#property (nonatomic, strong) UILabel *label;
#property (nonatomic, strong) UIView *circle;
#end
CustomCell.m
#implementation CustomCell
- (void)layoutSubviews
{
[super layoutSubviews];
self.circle = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 40.0f, 40.0f)];
[self.circle setBackgroundColor:[UIColor brownColor];
self.label = [[UILabel alloc] initWithFrame:CGRectMake(15, 20, 200.0f, 50.0f)];
self.label.textColor = [UIColor blackColor];
[self.contentView addSubview:self.label];
[self.contentView addSubview:self.circle];
//I have also tried [self addSubview:self.label];
}
tableView.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *customCellIdentifier = #"CustomCell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:customCellIdentifier];
if (cell == nil) {
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:inviteCellIdentifier];
}
dispatch_async(dispatch_get_main_queue(), ^{
[[cell label] setText:#"This is Label"];
[cell setNeedsDisplay];
});
return cell;
}
The only way I can get the UILabel to display text is by using the Block above. If I don't use the block and simply use cell.Label.text = #"This is a Label" followed by [cell setNeedsDisplay];, the text does not appear and I have to scroll the tableview causing the cells to reload, and only then the text in the label finally appears.
Is there better way or am I stuck with having to use the block?

You don't create the UILabel for the label property until the cell's layoutSubviews method is called which is long after you attempt to set the label's text in your table view controller.
Move the creation of the labels to the custom cell's initWithStyle:reuseIdentifier: method. Also put the call to self.contentView addSubview: in the init... method. The only thing that should be in the layoutSubviews method is the setting of the label's frame.
Once you do that you won't need the use of GCD in the cellForRow... method.
Do the same for the circle property too.
BTW - your use of GCD solves the issue because it gives the cell a change for its layoutSubviews method to be called, creating the label.

First off, you should not be allocating and placing views in layoutSubviews. The views should be created and placed when the cell is created and you only alter the frames (if required) in the layoutSubviews method. Otherwise you're going to get a tonne of duplicate views on top of each other.
Next, you should not be using dispatch_async inside of tableView:cellForRowAtIndexPath:. You can just set the text label directly. Nor should you need setNeedsDisplay since the system will do that anyway with the new cell.

Related

UICollectionview is not working, I am not getting any Cell

I am trying to use a collection view and reuse a collection view cell and I have to get 23 images and name from server which I have not started coz of the problem I am facing
This is my custom cell file collection view file objective c
Below is my custom cell.h
#import <UIKit/UIKit.h>
#interface CustomCell : UICollectionViewCell
{
UIImageView *imageView;
}
#property (nonatomic, retain) UIImageView *imageView; //this imageview is the only thing we need right now.
#end
Here is my custom cell.m file
#import "CustomCell.h"
#implementation CustomCell
#synthesize imageView;
- (id)initWithFrame:(CGRect)aRect
{
if (self = [super initWithFrame:aRect])
{
//we create the UIImageView in this overwritten init so that we always have it at hand.
imageView = [[UIImageView alloc] init];
//set specs and special wants for the imageView here.
[self addSubview:imageView]; //the only place we want to do this addSubview: is here!
//You wanted the imageView to react to touches and gestures. We can do that here too.
UITapGestureRecognizer * tap=[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onButtonTapped:)];
[tap setNumberOfTapsRequired:1];
[self addGestureRecognizer:tap];
//We can also prepare views with additional contents here!
//just add more labels/views/whatever you want.
}
return self;
}
-(void)onButtonTapped:(id)sender
{
//the response to the gesture.
//mind that this is done in the cell. If you don't want things to happen from this cell.
//then you can still activate this the way you did in your question.
}
#end
These are the methods in my normal view controller.m file
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cvCell" forIndexPath:indexPath];
[[[cell contentView] subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
// create a uiview where we can place all views that need to go into this cell
UIView * contents=[[UIView alloc] initWithFrame:cell.contentView.bounds];
[contents setBackgroundColor:[UIColor clearColor]];
[cell.contentView addSubview:contents];
// set tag to the indexPath.row so we can access it later
[cell setTag:indexPath.row];
// add interactivity
UITapGestureRecognizer * tap=[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onButtonTapped:)];
[tap setNumberOfTapsRequired:1];
[cell addGestureRecognizer:tap];
if (cell.selected) {
cell.backgroundColor = [UIColor blueColor]; // highlight selection
}
else
{
cell.backgroundColor = [UIColor redColor]; // Default color
}
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *datasetCell =[collectionView cellForItemAtIndexPath:indexPath];
datasetCell.backgroundColor = [UIColor blueColor]; // highlight selection
}
-(void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *datasetCell =[collectionView cellForItemAtIndexPath:indexPath];
datasetCell.backgroundColor = [UIColor redColor]; // Default color
}
I don't get any cell when I moved to this view, it is just blank with the background image and I have even given a blank picture to see how the view is but I don't get anything please help!!!
The cells are not displaying because you might not have set the datasource and delegate properly.
Since you have added the cells in storyboard you don't need to add the imageview as subview in the collection view cell.
First connect the outlet of imageview that you have added in storyboard to the CustomCell. Then remove
{
UIImageView *imageView;
}
#property (nonatomic, retain) UIImageView *imageView;
You can remove - (id)initWithFrame:(CGRect)aRect this method and you can add the tap gesture initialization in awakeFromNib method
-(void)awakeFromNib {
UITapGestureRecognizer * tap=[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onButtonTapped:)];
[tap setNumberOfTapsRequired:1];
[self addGestureRecognizer:tap];
}
No need to do
imageView = [[UIImageView alloc] init];
[self addSubview:imageView];
Again in cellForItemAtIndexPath
remove the following lines
[[[cell contentView] subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
UIView * contents=[[UIView alloc] initWithFrame:cell.contentView.bounds];
[contents setBackgroundColor:[UIColor clearColor]];
[cell.contentView addSubview:contents];
After making these changes please try again

Objective C - Swipe to delete triggers button in tableview cell

I have a tableview where the user can edit the cell content by tapping the cell using a button. When I use the swipe to delete function the 'edit button' gets triggered and causes the app to crash. How can I make shure other buttons in the cell are disabled when I 'swipe to delete'?
EDIT: example code added.
CustomCell.h:
#interface CustumCell : UITableViewCell {
UILabel *cellText;
UIButton *editButton;
}
#property (nonatomic,retain) UILabel *cellText;
#property (nonatomic,retain) UIButton *editButton;
#end
CustomCell.m:
#implementation CustumCell
#synthesize cellText,editButton;
- (void)awakeFromNib {
self.backgroundColor = [UIColor clearColor];
cellText = [[UILabel alloc] init];
cellText.translatesAutoresizingMaskIntoConstraints=NO;
[self.contentView addSubview:cellText];
//Cell Constraints
NSArray *verticalConstraintsCell =
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-6-[cellText(>=31)]"
options: 0
metrics:nil
views:NSDictionaryOfVariableBindings(cellText)];
NSArray *horizontalConstraintsCell =
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-15-[cellText(>=2)]-50-|"
options: 0
metrics:nil
views:NSDictionaryOfVariableBindings(cellText)];
[self.contentView addConstraints:verticalConstraintsCell];
[self.contentView addConstraints:horizontalConstraintsCell];
editButton = [[UIButton alloc] init];
[self.contentView addSubview:editButton];
}
TableViewController:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustumCell *cell= (CustumCell *)[tableView dequeueReusableCellWithIdentifier:#"CellSection1" forIndexPath:indexPath];
if (cell==nil) {cell = [[CustumCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CellSection1"];}
// add cell content..
//frame editButton:
cell.editButton.frame= CGRectMake(tableView.frame.size.width/2, 0, tableView.frame.size.width/2-50, 43);
[cell.editButton addTarget:self action:#selector(editButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
Now when I swipe to delete, the 'editButtonPressed' function executes.
Thanks.
It sounds like you are creating your button in code and adding it directly as a subview of the cell. This will cause the behavior you're seeing.
When subclassing a UITableViewCell or UICollectionView cell you should not add views directly as subviews of self. Instead, you must add them as subviews of self.contentView.

Custom UITableViewCell views are blank after searching

I believe this question is my exact problem, but I was not able to fix the issue looking at the accepted answer.
UISearchBar: FilterContentForSearchText not working on a UITableView (results not shown on table)
I have a UITableViewController that allows searching. It was working perfectly via the default UITableViewCellStyle, then I decided to implement my own custom layout for the cells. I did not subclass UITableViewCell, but instead added two UILabels to the cell via Interface Builder and set up Auto Layout. I assigned them unique tags so that I can reference those labels in cellForRowAtIndexPath:. It works great - everything appears as expected, until you search. The search does work and it displays the sections and rows, but the labels on the rows have no text so the cell appears completely blank.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"List View Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
UILabel *leftLabel = (UILabel *)[cell viewWithTag:100];
UILabel *rightLabel = (UILabel *)[cell viewWithTag:101];
if (tableView == self.searchDisplayController.searchResultsTableView) {
leftLabel.text = ...;
rightLabel.text = ...;
}
else {
leftLabel.text = ...;
rightLabel.text = ...;
}
return cell;
}
I do know why the labels are blank. When you search, dequeueReusableCellWithIdentifier: returns nil so it inits a new cell with the reuse identifier, but that cell does not have a viewWithTag: and this results in left and right label becoming nil. So it obviously cannot add text to nil.
The answer provided in the link stated you should create the label in the if (!cell) statement and then call [cell.contentView addSubview:left{right}Label];. I did that, and then moved my label configuration code into that if statement as well. But when I do that, the main table's rows only has the default values of my left and right labels from Storyboard - it doesn't set the text of the labels. This is because dequeueReusableCellWithIdentifier: doesn't return nil and instead creates a new cell, so it doesn't ever set the text because that's in the if (!cell) statement.
I could not figure out how to take care of both situations: when cell is nil and when it is not. What do I need to do to fix this?
More comments: I've never used xib files before and I'd prefer to keep it that way. :) I wouldn't mind subclassing UITableViewCell if that's a solution. Of course, I would like to implement this the "proper" way - only create a cell when one is needed etc.
I think the easiest way to do this is to make your cell in a xib file if you want to use the same cell type for both the main table and the search results table. You can make a subclass if you want (you only need to put in IBOutlets to your two labels in the .h file), or do it the same way you already did using tags. In viewDidLoad of the table view controller, register the nib for both tables,
[self.searchDisplayController.searchResultsTableView registerNib:[UINib nibWithNibName:#"CommonCell" bundle:nil] forCellReuseIdentifier:#"CommonCell"];
[self.tableView registerNib:[UINib nibWithNibName:#"CommonCell" bundle:nil] forCellReuseIdentifier:#"CommonCell"];
Then in cellForRowAtIndexPath:, you only need to dequeue the cell with that same identifier, and populate the labels. There's no need to check for cell equals nil, because it never will be.
I modified one of my apps to show how you can implement cellForRowAtIndexPath. I subclassed the cell (CommonCell is the class), only adding IBOutlets to the leftLabel, and rightLabel,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CommonCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CommonCell" forIndexPath:indexPath];
cell.leftLabel.text = ([tableView isEqual:self.tableView])? self.theData[indexPath.row] : self.filteredData[indexPath.row];
cell.rightLabel.text = ([tableView isEqual:self.tableView])? self.theData[indexPath.row] : self.filteredData[indexPath.row];
return cell;
}
This is conceptually wrong: you are instantiate a NEW CELL from code that is not the cell from interface builder. If you want use that on interface builder you need to register the nib for your tableView, and associate it to an identifier (se the same identifier also in the cell on interface builder):
[self.searchDisplayController.searchResultsTableView registerNib:[UINib nibWithNibName:#"NameNib" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:#"Identifier"];
But the question is: why? The best thing to do is create a subclass and add your labels. It is really simple:
1) Create a new file CustomSearchCell object that extends UITableViewCell:
CustomSearchCell.h
#import <UIKit/UIKit.h>
#interface CustomSearchCell : UITableViewCell
#property (strong, nonatomic) UILabel *leftLabel;
#property (strong, nonatomic) UILabel *rightLabel;
#end
CustomSearchCell.m
#import "CustomSearchCell.h"
#implementation CustomSearchCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
_leftLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 10, 200, 45)];
[_leftLabel setFont:[UIFont systemFontOfSize:20.0]];
[_leftLabel setTextColor:[UIColor blackColor]];
_rightLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 45, 200, 25)];
[_rightLabel setFont:[UIFont systemFontOfSize:15.0]];
[_rightLabel setTextColor:[UIColor grayColor]];
[self.contentView addSubview: _leftLabel];
[self.contentView addSubview: _rightLabel];
}
return self;
}
#end
2) In your view controller:
#import "CustomSearchCell.h"
and:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ListCellIdentifier";
CustomSearchCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[CustomSearchCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if (tableView == self.searchDisplayController.searchResultsTableView) {
cell.leftLabel = ...;
cell.rightLabel = ...;
}
else {
cell.leftLabel = ...;
cell.rightLabel = ...;
}
return cell;
}

Can't resize UIImageView's frame in subclassed UITableViewCell with dynamic height

In my UITableView, all the cells will be a different height. All of them have a background image, which is a bubble picture. All of the cell's TextLabels need to be different widths as well, so I've subclassed UITableViewCell and have given it two properties:
#property (strong, nonatomic) UILabel* messageTextLabel;
#property (strong, nonatomic) UIImageView* bubbleImageView;
I have my custom UITableViewCell's initWithStyle set up like so:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
//stretchable bubble image
UIImage *rawBackground = [UIImage imageNamed:#"text_bubble"];
UIImage *background = [rawBackground stretchableImageWithLeftCapWidth:13 topCapHeight:22];
_bubbleImageView = [[UIImageView alloc] initWithImage:background];
//textlabel
_messageTextLabel = [[UILabel alloc] init];
[_messageTextLabel setFont:[UIFont systemFontOfSize:14.0f]];
[_messageTextLabel setNumberOfLines:0];
[_messageTextLabel setBackgroundColor:[UIColor clearColor]];
[_messageTextLabel setTextColor:[UIColor blackColor]];
[self.contentView addSubview:_bubbleImageView];
[self.contentView addSubview:_messageTextLabel];
}
return self;
}
In my TableView's cellForRowAtIndexPath, I've already tried to just alloc a UIImageView there and set it to the cell's frame, and that works, but when I scroll up and down then the screen gets cluttered with UIImageViews being re-alloced over and over. I've tried the if(cell == nil) technique, but that just makes my entire UITableView blank. So, this is what I have right now:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
Cell *cell = (Cell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//works just fine
[cell.messageTextLabel setFrame:CGRectMake(15, 0, 245, cell.frame.size.height -10)];
//this doesn't work at all
[cell.bubbleImageView setFrame:CGRectMake(10, 0, cell.frame.size.width, cell.frame.size.height)];
[cell.messageTextLabel setText:[self.randomData objectAtIndex:indexPath.item]];
return cell;
}
So my UITableView ends up looking like this when run (cell selected so you can see the frame):
Note that I can set the frame of the bubbleImageView in my UITableViewCell subclass, but I don't know what the cell's height is going to be then. I'm more baffled by the fact that I can set my messageTextLabel's frame and not my bubbleImageViews. I may be doing something that is very elementary wrong, it's a simple problem though for some reason I'm getting tripped up.
Thank you!
I have seen 2 problems here:
I think that you have the auto-layout enabled.
In this case the initial frame and the autoresizingMask are translated to auto-layout constraints automatically.
After this no frame changes will change the view's size and location - only the constraints...
You haven't set the contentMode of the image view to UIViewContentModeScaleToFill (this is the default, but it's better define this kind of properties anyway).
Just add this line: _bubbleImageView.contentMode = UIViewContentModeScaleToFill; right after its initiation.
I think that the solution here is auto-layout - setting constraints properly will solve this issue.
I suggest doing it in the storyboard, with table view controller and dynamic cells...

Resizing custom UITableViewCell's subviews on creation

I'm attempting to create a custom UITableViewCell with its own XIB, of which contains two important, editable components:
UILabel
UIImage with 'end-cap insets' - this image sits behind the label, and resizes with it
When I create my UITableViewCell I can set its label's text easily, but in the same method I also try to change the UIImage's frame to match that of the UILabel's. However, this is not happening until I 'reload' the cell by scrolling the table view to dequeue it and bring it back into view.
I create the custom table view cell in my view controller's .m:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *UITableViewCellIdentifier = #"msgCell";
RMMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:UITableViewCellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"MessageCell" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
[cell customInit]; //This sets up the ImageView's image etc
}
[cell setMessageValue:[_messages objectAtIndex:indexPath.row]]; //SEE BELOW
return cell;
}
...And in my custom cell's .m, I deal with setting the text label and resizing the image with this method:
- (void)setMessageValue:(NSString *)message {
_testLabel.text = message;
// Assume the text label's frame has already been set to the message's length
// (will add this in once I figure out how to change the imageView's
// frame correctly)
// Set the image view's frame to the label's
// THIS DOES NOT TAKE EFFECT UNTIL THE TABLE VIEW IS RE-SCROLLED
CGRect frame = _imageView.frame;
frame.size.width = _testLabel.frame.size.width + 8;
_imageView.frame = frame;
}
This is caused because a table view cell is created at one size, and then it is resized when it is displayed after being added to the table. If you log the bounds or frame of your cell, you will see that it is one size (smaller) when you load it from the nib, and another size (bigger) when you "refresh" the cell by scrolling it off of the screen and back on.
The simplest approach would be to make sure that the label and image both have the same auto-sizing rules, so that when the cell is resized, they both grow together.
you can use UITableView delegate method,
– (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
inside this you can set the height of row dynamically like..
// This is only for Example that you can set your cell height
-(CGFloat)tableView:(UITableView *)aTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *text;
text = [_messages objectAtIndex:indexPath.row];
CGSize constraint = CGSizeMake(280, 2000);
CGSize size = [text sizeWithFont:[UIFont boldSystemFontOfSize:12] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
return size.height + 5;
}
You should be able to control the size of the subviews if you programmatically create them and add them to your tableview cell (i.e.,so that they are not set in storyboard, and thus are not subjected to explicit auto-contraints).
In my implementation, I created a customCell subclass. In the .h:
#property (strong, nonatomic) UILabel *customLabel;
Then in the .m:
- (id)initWithCoder:(NSCoder *)aDecoder {
METHOD;
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code
self.customLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 40)];
[self addSubview:self.customLabel];
}
return self;
}
In your tableViewController's .m file, in the method tableView:cellForRowAtAtIndexPath:
theCell.customLabel.frame = CGRectMake(0, 0, 320, 40);
theCell.customLabel.text = #"Whatever text you want";
This will give you instant and complete control over your custom label right when the tableview and cell loads.
In response to #inafziger, I tried programmatically removing the constraints on the label but it did not resolve this auto-resizing issue.
Update: making a temporary reference to the subview, then removing it from superview, and then adding it back to the subview, this worked just as well, with the slight exception that the in my case where I'm using a label, the height of the label was fit to the height of the text it contained (whereas programmatically creating the label in my example above, the label will have whatever height you assign it.) The advantage being that you don't have to create any custom labels, or even touch the subclass tableviewCell. Just use whatever you created in storyboard. Here's tableView:cellForRowAtIndexPath:
UILabel *theLabel = theCell.theLabel;
[theCell.theLabel removeFromSuperview];
[theCell addSubview:theLabel];
theCell.theLabel.frame = CGRectMake(0, 0, 320, 40);

Resources