Where can I declare custom initialization for custom table cells? - objective-c

If I have a custom table cell that is used by:
tableView dequeueReusableCellWithIdentifier
Where inside of that custom cell can I set up custom initialization parameters? The auto-generated .m file for my custom table cell included:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
NSLog(#"INITWITHSTYLE!");
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
I added the NSLog call to see if that initWithStyle is ever called, but that NSLog is never reached, which means that that particular initWithStyle is also never called. So when a custom table cell is initialized using dequeueReusableCellWithIdentifier, which init method of that custom cell is actually used?

Your cell will get initialized once in -(void)awakeFromNib (even when it's from a Storyboard).
Then, there's no way to know where it will be dequeued, but it can receive a message when it's removed from screen and enqueued to the reusable cells pool: at this time - (void)prepareForReuse is called.

If you use storyboard you can use awakeFromNib, otherwise the initWithStyle:reuseIdentifier: should be called
- (void)awakeFromNib {
}
But you should remember that the cell will be reused so let's say if your cell is called first time awakeFromNib is called but later on the cell could be reused and this method won't be called again, it this scenario, if it doesn't work for you, you can use custom setter when you pass the data
//Extended
In comment you asked what method is called when the cell is reused.
You can use prepareForReuse, please read discussion (taken from Apple)
- (void)prepareForReuse
Discussion If a UITableViewCell object is reusable—that is, it has a
reuse identifier—this method is invoked just before the object is
returned from the UITableView method
dequeueReusableCellWithIdentifier:. For performance reasons, you
should only reset attributes of the cell that are not related to
content, for example, alpha, editing, and selection state. The table
view's delegate in tableView:cellForRowAtIndexPath: should always
reset all content when reusing a cell. If the cell object does not
have an associated reuse identifier, this method is not called. If you
override this method, you must be sure to invoke the superclass
implementation.

Related

initWtihFrame: method for custom textfield not being called when created in a TableCellView

So I'm trying to create a custom NSTableView by subclassing NSTableCellView and a NSTextField inside of the cellview. I'm trying to write some init code in the initWithFrame: method for the NSTextField subclass (TableTextField), but it looks like the initWithFrame: method isn't being called at all, even when I create new rows for the NSTableView, which each contain an instance of the TableTextField. Here's the code in the TableTextField.m file:
#import "TableTextField.h"
#implementation TableTextField
- (id)initWithFrame:(NSRect)frame//This isn't even being called.
{
self = [super initWithFrame:frame];
if (self) {
self.ad = [[NSApplication sharedApplication] delegate];
}
return self;
}
- (void)mouseDown:(NSEvent*) theEvent{
NSLog(#"A");//TEST
self.ad = [[NSApplication sharedApplication] delegate];//This works. Why isn't it happening in init?
[self.ad.classViewController addHomeworkItem];
}
#end
I've been able to solve the problem by just putting the code i need in the mouseDown: method, which is really the only thing I need, but I feel like it's bad practice to redeclare self.ad every time i need to use it, rather than just declaring it once and accessing it for each use, and I can't seem to figure out why initWithFrame: isn't being called. I'm assuming it has to do with the way objects inside NSTableCellViews are initialized, but I haven't found a real explanation. Any suggestions on how to solve this, or explanations as to why it's happening would be welcome. Thanks!
Here's an image of how the NSTableView is set up (with 3 rows):
If you are using XIB files to layout the views, the initialiser method being called is initWithCoder:

How to overwrite textDidChange: method correctly?

I subclassed NSTextField and overwrite the textDidChange: as:
- (void)textDidChange:(NSNotification *)notification
{
// ... My own operation
}
But when I drag a input text box into my .xib file and control drag another class to assign the delegate I found the delegate's controlTextDidChange: method was never called.
Now tryingto solve this problem, I tried two ways al below:
I. calling super:
- (void)textDidChange:(NSNotification *)notification
{
// ... My own operation
[super textDidChange:notification];
}
But I got an error in runtime: attempt to insert nil object from objects[0]
II. calling delegate's method
- (void)textDidChange:(NSNotification *)notification
{
// ... My own operation
if ([self.delegate responseToSelector:#selector(controlTextDidChange:)])
{
[self.delegate ...]; // <--- Opps, something not happerned here.
}
}
What not happerned? I expected that the auto-complete should display the controlTextDidChange: method at the position of ... above. But it did not, actually. I typed the method directly, compilation fail because method not found.
How should I make my sub-class call the delegate normally? How should I overwrite the textDidChange: method correctly?
Further question for Vytautas:
I am sure I was using NSTextField. And I set a break point inside controlTextDidChange: method. As it was called, I should have known.
I did control-drag the text field to the delegate object, and I print delegate object in the textDidChange: method, it was sure that the delegate was set correctly.
The other delegate methods, such as controlTextDidBeginEditing: were called correctly. But controlTextDidChange: not called
I tried comment out the over-written in the subclassed NSTextField class, then controlTextDidChange: was called.
Therefore I was quite sure that I am not overwritting the textDidChange: right. But I do not known how to fix it.
What made me confused mostly was that why auto-completion did not show the controlTextDidChange: method when I attempted to call it.
About the auto-completion, here is how it showed:
No - controlTextDidChange: method.
2nd further reply for Vytautas:
I tried calling '[self controlTextDidChange]' but it did not work, and error occurred (as highlighted below):
I can say that - controlTextDidChange: is called for sure.
Maybe there is something wrong with you bindings in your *.xib.
Also it can be that in *.xib you are using NSTextView, not NSTextField.
In this case - controlTextDidChange: won't be called for sure.
If that is the case then you should take a look to NSTextView, NSTextViewDelegate and NSTextDelegate. NSTextView delegate has an alternative method for this - textDidChange:

Setting .reuseIdentifier on a UICollectionViewCell

I have a particular UICollectionViewCell that I want to instantiate myself, and add to a UICollectionView. In order for this to work, the UICollectionViewCell instance needs its .reuseIdentifier property to be set.
Normally, the class or Nib that describes the cell is registered with the collection view, and the collection view instantiates the cell with its .reuseIdentifier already set, with these methods:
- registerClass:forCellWithReuseIdentifier:
- registerNib:forCellWithReuseIdentifier:
However, since I am constructing this cell outside of the collection view, these do not apply.
When I create the cell myself, there appears to be no way to set its .reuseIdentifier (because it is a readonly property, and there are no init... methods that initialize it).
When .reuseIdentifier is not set, the UICollectionView throws an exception when the cell is added. This behavior is different from UITableView, where reuse identifiers were optional.
An easy workaround to set the collection view cell's reuse identifier is to embed it in a .xib file and use the Identifier box, then create an instance of the cell with
[NSBundle.mainBundle loadNibNamed:#"MyCellName" owner:self options:nil][0];
I can then pass the above-instantiated UICollectionViewCell and everything works fine.
...but that seems like a pretty silly and arbitrary hoop to jump through. Is there some other way to get this property set on the cell instance without the .xib-wrapper detour?
Update: Apple's documentation says this:
To simplify the creation process for your code, the collection view requires that you always dequeue views, rather than create them explicitly in your code.
...which is actually not true, because it doesn't require this (i.e., externally-instanced cells work fine as long as their identifier is set somehow, e.g. if loaded from a .xib), and it also doesn't "simplify the creation process for my code" in my particular use case (rather requires an extra file; further it would be messy to require that the collection view create these few complex one-offs).
But the above does seem to imply that the answer to the question is no: it's intentionally "difficult" to create a usable cell except by having the collection view dequeue it.
I'm sorry to say it, but I think you're going to have to accept the fact that UICollectionView is the sole Apple-branded factory for producing UICollectionViewCells. So if you have two collection views in which you want to display the exact same object, you're out of luck!
The good news is that if you have two collection view in which you want to display a cell that looks exactly the same as in the other, then you can accomplish your task. It's easy:
#interface MyModelClass : NSObject
// a bunch of data
#end
#interface MyCollectionViewCell : UICollectionViewCell
-(void)refreshCellWithObject:(MyModelClass *)object;
#end
Meanwhile, in whichever classes own the UICollectionView cv1 and cv2
[[self cv1] registerClass:[MyCollectionViewCell class] forCellWithReuseIdentifier:#"MyCollectionViewCell"];
and
[[self cv2] registerClass:[MyCollectionViewCell class] forCellWithReuseIdentifier:#"MyCollectionViewCell"];
And their data sources can look the same
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyModelObject *obj = [self theObjectIWant];
MyCollectionViewCell *cell;
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"MyCollectionViewCell" forIndexPath:indexPath];
[cell refreshCellWithObject:obj];
return cell;
}
Use these two methods.
– registerNib:forCellWithReuseIdentifier:
– dequeueReusableCellWithReuseIdentifier:forIndexPath:
See apple's UICollectionView Class Reference

why does the init-method of a custom nib-based table cell not get called

I have a nib-based table view cell which I created in Interface builder. I set the class of the table view cell to FooTableViewCell which extends from UITableViewCell.
In FooTableViewCell I override the init method like this:
-(id)init{
if ((self = [super init])){
// My init code here
}
return self;
}
I now expected that my gets called, when it is being instantiated. However the table view gets displayed but the method is never called.
I could work around this but I would like to fully understand it and for me it's not clear how an object can come to live without the init method being called.
When being unarchived initialization goes through a slightly different path.
Instead of -(id)init being called -(id)initWithCoder:(NSCoder*)aDecoder will be called. At this point outlets aren't hooked up, if you need access to the outlets you can override -(void)awakeFromNib which will be called after the object has been hooked up.
When object is being loaded from nib file then its -awakeFromNib method is called - you can put your initialisation code there:
- (void)awakeFromNib{
// Init code
}

NSTextFieldCell Delegate?

I have a text field cell in a table view, from which I need to be made aware when it ends editing. I thought I would set my Controller class as the text field cell's delegate, and then use NSTextField's delegate method textDidEndEditing:, but realized that the text field cell doesn't seem to have delegate methods? Why is this, and what can I do (other than subclassing) to be informed when editing is finished?
Thanks
NSTextFieldCell inherits from NSCell (well, technically from NSActionCell which inherits from NSCell). The NSCell class is used to (from the docs):
The NSCell class provides a mechanism for displaying text or images in an NSView object without the overhead of a full NSView subclass.
Notably, The cell class is used for "displaying text or images", and not dealing with interaction with the user. Similarly, with the NSTextField class:
The NSTextField class uses the NSTextFieldCell class to implement its user interface.
The NSTextField deals with the actual user input, whilst using the text field cell to simply implement its user interface, and similarly, the delegate methods to provide notification when the editing of text has ended is provided through the NSTextField class and not through the NSTextFieldCell class.
If you want to be notified of when editing ends in an NSTableView, then you need to register yourself as an observer of the NSTextDidEndEditingNotification (you might want to read the NSNotificationCenter class reference if you are unfamiliar with notifications). To do this, place the following in your controller class; the awakeFromNib function is a good place to include it to ensure that it is called upon your application's startup:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(textDidEndEditing:)
name:NSTextDidEndEditingNotification
object:tableView];
Where tableView is the pointer to your NSTableView object. Then, simply implement the method as follows:
- (void)textDidEndEditing:(NSNotification *)aNotification
{
// Do what you want here
}
Don't forget to remove yourself as an observer upon deallocation:
- (void)dealloc
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self];
}
The reason that you set the object that you are observing to be the NSTableView instance (and not the cell itself) is that under the hood, when you edit a cell in the table, the cell that you are dealing with isn't being edited directly; it is the window's (or a custom) field editor. When editing ends, the field editor then passes the new value for that cell on to the table view. However the table view will post a notification to say that a cell has finished being edited.
Implement the tableView:setObjectValue:forTableColumn:row: method in the NSTableViewDataSource protocol. Put it next to the tableView:objectValueForTableColumn:row: method that you've already implemented.
- (void)tableView:(NSTableView *)aTableView
setObjectValue:(id)anObject
forTableColumn:(NSTableColumn *)aTableColumn
row:(NSInteger)rowIndex
{
[mutableArrayWithStrings replaceObjectAtIndex:rowIndex withObject:anObject];
}