Custom UITableViewCell delegating for its subviews? - ios

I have a custom cell with a textfield as a subview. Is it legal to set the cell as the delegate for the textfield?
I want to set the cell as the delegate because it's the parent view, but when I do this, the app crashes. I suppose it's because table view dequeue cells and maybe that's why the delegate reference got lost in between.
So, I tried to set the delegate in cellForRowAtIndexPath to make sure it's freshly set every time, but it still won't work.
Do I really have to move a level up and let the tableview controller be the delegate of cell's subviews? or am I missing something else?
crash report outputs:
>libobjc.A.dylib`objc_msgSend:
>
0x32ed4f78: ldr r3, [r4, #8]
>
initWithStyle I have
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier type: (NSString *)cellType
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"Custom Cell" owner:self options:nil];
self = [nibArray lastObject];
self.cellType = [NSString stringWithString:cellType];
self.contentText.autoresizingMask = UIViewAutoresizingNone;
self.detailLabel.autoresizingMask = UIViewAutoresizingNone;
self.contentText.delegate = self;
}
return self;
}

Having the custom cell as delegate to the text field in the cell is not a problem at all. The table view will reuse some cells as and when required but that will not affect anything. I guess you are missing something else. Maybe I could help if you post the error you get when it crashes and corresponding pieces of your code.

Don't set the delegate in cellForRowAtIndexPath.
In your UITableViewCell subclass add the UITextField in the initWithStyle method and then set the delegate to self in there.

Related

Custom TableViewCell not displaying label

I'm struggling with a problem I encountered while trying to create a custom UITableViewCell.
I subclassed UITableViewCell in SGTableViewCell and added it in the prototype cell in the storyboard.
As you can see the label is connected
and the cell identifier is set correctly
Then I linked the label to the SGTableViewCell.h like this
#property (strong, nonatomic) IBOutlet UILabel *nameLabel;
and in the .m file I have this code
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
[self addGestureRecognizer:recognizer];
self.nameLabel = [[UILabel alloc] init];
_checkView = [[UIView alloc] initWithFrame:CGRectNull];
_checkView.backgroundColor = kGreen;
_checkView.alpha = 0.0;
[self addSubview:_checkView];
self.nameLabel.text = #"Hello";
}
return self;
}
But when I use this cell in my tableview using this code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Episode *episode = [self.selectedSeason.episodeList objectAtIndex:indexPath.row];
SGTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Episode"];
UIView *selectionColor = [[UIView alloc] init];
selectionColor.backgroundColor = kSelectionGrey;
cell.selectedBackgroundView = selectionColor;
cell.backgroundColor = kBackgroundGrey;
cell.nameLabel.text = episode.name;
NSLog(#"%#", cell.nameLabel.text);
cell.nameLabel.textColor = [UIColor redColor];
return cell;
}
I get no text at all.
I tried logging the text from each label in each cell and it gives me the right text.
I tried setting programmatically a different disclosure indicator for the custom cell and it did change so everything is allocated and working but label is not displaying.
I honestly have no idea of what's the problem. Did I miss something?
Thank you
PARTIALLY SOLVED:
OK i tried doing the same thing on an empty project and everything worked flawlessly so I checked again my project and found this line
[self.tableView registerClass:[SGTableViewCell class] forCellReuseIdentifier:#"Episode"];
Seeing it was not necessary for the empty project i commented this line and everything started working.
The only problem i have now is that if i don't use this line i can't use the custom cell as was intended. In fact my custom cell is swipable using a pan gesture recognizer but without registering my custom class to the tableview seems like the swipe doesn't work.
Sorry for the trouble, seems like i messed up again :/
You shouldn't alloc init a label that you created in the storyboard, it is already allocated automatically. When you do self.nameLabel = [[UILabel alloc] init];, you reset the self.nameLabel property to point to a new empty memory location and not to the label created in the storyboard, hence you can change its text property and see the result in NSLog but not in the storyboard because it doesn't refer to that label in the storyboard.
Try removing all initialisation from the initWithStyle method (to make sure nothing is covering it such as that subview you create), and everything related to the label in the cellForRowAtIndexPath method (same reason), and try a simple assignment like self.nameLabel.text = #"Test text" in the cellForRowAtIndexPath method, it should work. Then add all your other initialisation.
And yeah, don't forget to input your cell reuse identifier "Episode" in the storyboard.
Make sure you:
Have linked the delegate and the datasource to the view the tablview is housed in.
Have that view implement UITableViewController and UITableViewDelegate (I'm pretty sure it is both of those).
Implement the necessary methods, which you seem to have done. You need the row size, section size, and the add cell methods
After updating the array linked to your tableview, call [tableView reloadData]
Have a look at this link:Tutorial to create a simple tableview app

Why is UITableViewCell initialization not working inside initWithCoder

I'm trying to clean up my code and use MVC principles by pushing as much view related stuff as i can into the storyboard as well as custom UIView classes (ie as opposed to doing view related stuff on the UIViewController itself)
So I have a custom UITableViewCell (called CustomCell) that has several properties, one of them is my own label. Since I'm loading the cell from the storyboard, I initialize it with initWithCoder rather than initWithStyle:reuseIdentifier:, this is what I have in CustomCell.m which is a subclass of UITableViewCell (for some reason i couldn't figure out how to set a custom font using storyboard.. but that's beside the point of this question):
// CustomCell.m - subclass of UITableViewCell
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
NSLog(#"customizing cell font having text %#", self.label.text);
UIFont *customFont = [UIFont fontWithName:#"Montserrat" size:16];
self.label.textColor = [UIColor redColor];
[self.label setFont:customFont];
}
return self;
}
This simply doesn't work.. the log statement outputs null for the text simply b/c the text hasn't been loaded yet. self.label is also null (I don't know why I thought it should have been inflated from the nib by now) but even if I initialize it here.. it still won't work.
So my work around was to simply put this cell customization part here in the TableViewController:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath {
UIFont *customFont = [UIFont fontWithName:#"Montserrat" size:16];
[((CustomCell *)cell).label setFont:customFont];
}
and it worked just fine.. I'm unhappy with this method and I would like to know how to make it work from within CustomCell.m
update: to make things even more interesting.. if i put customization code for UITableViewCell properties inside initWithCoder, they work! consider this example:
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
UIView *bgColorView = [[UIView alloc] init];
[bgColorView setBackgroundColor:[UIColor blueColor]];
[self setSelectedBackgroundView:bgColorView]; // actually works!
}
return self;
}
which makes this even more weird.
Inspired by andykkt's comment, I found the explanation in awakeFromNib documentation:
The nib-loading infrastructure sends an awakeFromNib message to each
object recreated from a nib archive, but only after all the objects in
the archive have been loaded and initialized. When an object receives
an awakeFromNib message, it is guaranteed to have all its outlet and
action connections already established.
so that explains the weird behaviour above: initWithCoder instantiates a standard UITableViewCell (that already comes pre-baked with a backgroundView etc etc).. however it still doesn't recognize the outlets I've added to it via storyboard.. awakeFromNib does.
I suspect that initWithCoder: method doesn't draw the contents of the cell, it just, initialises the object, and as you have noticed, accessing any of the ui drawing methods of the cell actually 'draws' the cell to apply the changes you have made to the cell. You can see the same behaviour when initing UIViewController's using initWithNibName:bundle: method.

Xcode 5, custom UITableViewCell class for Dynamic Prototype

I'm using Xcode 5, and want to be using the recommended best practices from Apple, which includes dynamic prototype cells and using registerClass:forCellReuseIdentifier.
I have created a storyboard and dropped a UITableView with 1 prototype dynamic cell on it. I've set the class of the cell to ItemCell, and set the reuse identifier to ItemCell.
The ItemCell class contains a nameLabel IBOutlet, which I've connected to the label within the prototype cell by dragging.
In the ViewController, I register the ItemCell class to be used for the ItemCell reuse identifier:
- (void)viewDidLoad {
[super viewDidLoad];
[_tableView registerClass:[ItemCell class] forCellReuseIdentifier:#"ItemCell"];
}
In tableView:cellForRowAtIndexPath, I dequeue the cell and set the properties for the nameLabel. self.items is an NSArray of strings.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ItemCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ItemCell" forIndexPath:indexPath];
cell.nameLabel.text = [self.items objectAtIndex:indexPath.row];
return cell;
}
So: it's being created as an ItemCell, but it's not loading it from the storyboard. I've confirmed this by overriding initWithStyle:reuseIdentifier and initWithCoder, to see which was being called:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
NSLog(#"NOT STORYBOARD");
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code
NSLog(#"Yay, it's working!");
}
return self;
}
Every time, it's initWithStyle that's being called. From everything I've read, this should be working. I can't find anything that indicates I need to somehow register the class differently when it's in a storyboard, but clearly the cell isn't aware that it's got a storyboard associated with it.
I'm sure I'm making a total newbie mistake, but I can't figure it out. Any help would be appreciated.
You don't have to (and should not) call registerClass for prototype cells defined in the storyboard. initWithCoder is called automatically if a prototype cell has to
be instantiated from the storyboard.

Setting up a UITableViewCell subclass in code

I am reading the apple docs on setting up custom subclasses of UITableViewCell - Docs
In this example I need to setup a custom cell which does not have a NIB/storyboard file. The apple docs provide an example of using a predefined style and configuring that but not creating a completely custom layout.
How should the cell be called in.. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath ?
I am looking to have a completely custom layout so is this correct? As the cell is being called initWithStyle...?
MESLeftMenuCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil) {
cell = [[MESLeftMenuCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
In the custom cell subclass how/where should I implement the setup of the views within the contentView?
Which method is called for the init, would it be initWithStyle as above? If so, can I simply create the cell outlets in there once only?
Then in the cellForRowAtIndexPath can I access the outlets as i.e. cell.MainLabel.text ... ?
This is how I have been shown to set up my Collection View Cells in their custom class. I know you are using a tableview but this is threw me for a while so decided to add here. Hopefully it helps you.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)encoder
{
self = [super initWithCoder:encoder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit
{
// set up your instance
}
To access the outlets of that cell I just add outlets to the header file of the custom class and you can easily access them.
As of iOS 6, you no longer need to check whether the dequeued cell is nil.
What you would do is as follows:
In the viewDidLoad method of the view controller containing the table view you could say
[self.tableView registerClass:[MyCellClass class] forCellReuseIdentifier:MyCellIdentifier];
This results in your dequeueReusablecellWithIdentifier call to never return nil. In essence, in the background, initWithStyle is called. So you would set your stuff up when overriding that function.

Adding Gesture to TableViewCell

I'm trying to add a swipe gesture recognizer to a tableViewCell but it doesn't work.
This is the way I create my cell:
CellIdentifier = #"EventsSentCell";
nibObjcet = [[NSBundle mainBundle] loadNibNamed:#"EventsSentCell" owner:self options:nil];
EventsSentCell *cell = [[EventsSentCell alloc] init];
cell = (EventsSentCell *)[nibObjcet objectAtIndex:0];
and this is how my cell is initiated in the .m file:
-(id)init{
self = [super init];
if (self) {
leftSwipe = [[UISwipeGestureRecognizer alloc] init];
leftSwipe.direction= UISwipeGestureRecognizerDirectionLeft;
[leftSwipe addTarget:self action:#selector(swipedLeft)];
[self addGestureRecognizer:leftSwipe];
}
return self;
}
and this is how I declared my gesture recognizer in the .h file:
#property (nonatomic,strong) IBOutlet UISwipeGestureRecognizer *leftSwipe;
But for some reason my method isn't called.
Any ideas?
Thanks
I've tried putting the below code:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
NSLog(#"%#",gestureRecognizer);
return YES;
}
and the result i'm getting after swiping left is:
<UILongPressGestureRecognizer: 0xa9d99a0; state = Possible; view = <UITableViewCellContentView 0xa9d8ce0>; target= <(action=_longPressGestureRecognized:, target=<EventsSentCell 0xa9d8bb0>)>>
Before answering the actual question, let me point out some other issues in your code.
EventsSentCell *cell = [[EventsSentCell alloc] init];
cell = (EventsSentCell *)[nibObjcet objectAtIndex:0];
First of all, there's no point in these two lines. You're allocating and initializing an instance of EventSentCell without a nib. After doing this you're overwriting cell to point to the instance initialized by loadNibNamed:. You could simplify this to EventsSentCell = (EventsSentCell *)nibObject[0];
But even after these optimizations, this still isn't the recommended way to implement cellForRowAtIndexPath:. You should use registerNib:forCellReuseIdentifier: in viewDidLoad and then use
dequeueReusableCellWithIdentifier:forIndexPath: to get a cell and leave out loading the nib yourself completely.
Next,
#property (nonatomic,strong) IBOutlet UISwipeGestureRecognizer *leftSwipe;
You declare this property as an IBOutlet but you're setting it (as far as I know) only in code, more specifically the init method. You could just leave out the IBOutlet altogether.
And this init method is probably also the cause of your problem. When instantiating a view using loadNibNamed, initWithCoder: is called instead of init. Implement your custom initialization code (adding a gesture recognizer in this case) there and it should work just fine.
Your 'init' method is not getting called, so the gesture recognizer does not get set up.
You could try initialising in awakeFromNib instead, but anyway your cell creation looks unconventional.
Assuming you are using a custom cell class with a Xib file, here is how I would do it.
Create your EventsSentCell object .m and .h files
Create a xib file "EventsSentCell.xib"
In the xib file, delete the default top-level view and replace it with a UITableViewCell (you can drag one out from the objects library). In the identity inspector change it's class to EventsSentCell
In you table viewController's viewDidLoad...
UINib* EventsSentNib = [UINib nibWithNibName:#"EventsSentCell"
bundle:nil];
[self.tableView registerNib:EventsSentNib
forCellReuseIdentifier:#"EventsSentCell"];
In your cellForRowAtIndexPath method:
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:#"EventsSentCell"];
if (cell == nil) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"EventsSentCell"];
}
In EventsSentCell.m, trigger your initialisation code from -awakeFromNib
Your initialisation code:
leftSwipe = [[UISwipeGestureRecognizer alloc] init];
leftSwipe.direction= UISwipeGestureRecognizerDirectionLeft;
[leftSwipe addTarget:self action:#selector(swipedLeft)];
[self addGestureRecognizer:leftSwipe];
will work as it is.
You get the UILongPressGestureRecognizer:... response to your gestureRecogniser delegate method because that is a built-in gesture recognizer provided by Apple that has it's delegate set to the cell. When your code is working correctly, if you also set your gesture recongnizer's delegate to the cell (leftSwipe.delegate = self), you would expect to see a simlilar log for UISwipeGestureRecognizer:...
It is also noting that the UILongPressGestureRecognizer's view is NOT the cell, but the cell's contentView. This is the superview for your cell's view hierarchy, to which you should attach all of your cell's content. Although your gesture recognizer works when you attach it to the cell, I would advise following Apple here:
[self.contentView addGestureRecognizer:leftSwipe];
Then the gesture will correctly follow the cell's view hierarchy.

Resources