Hey, I'm looking for useful resources about Delegates. I understand that the delegate sits in the background and receives messages when certain things happen - e.g. a table cell is selected, or data from a connection over the web is retrieved.
What I'd like to know in particular is how to use delegates with multiple objects. As far as I know, specifying the same delegate for an object (e.g. table cell) would cause the same events to be called for both the cells at the same time. Is there anything equivalent to instantiating a delegate for a particular object?
Thanks in advance!
In Cocoa, objects almost always identify themselves when calling a delegate method. For example, UITableView passes itself as the first parameter of the delegate message when calling it:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
If you wanted the same delegate to handle multiple UITableViews, then you just need a some conditional on the tableView object passed to the method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.myFirstTableView) {
// do stuff
} else if (tableView == self.mySecondtableView) {
// do other stuff
}
}
}
If you don't want to compare the object pointers directly, you can always use the tag property to uniquely identify your views.
Usually, if you have a delegate method that might have to receive messages from many different objects, you simply have the calling object pass itself to the delegate in the message (method call).
For example, if you wanted a delegate method to extract the text from a tableviewcell's label, the method definition would look something like:
-(void) extractTextFromLabelOfTableCell:(UITableViewCell *) theCallingCell{
...
NSString *extractedText=theCallingCell.textLabel.text;
}
You would call the method from a tableviewcell thusly:
[delegate extractTextFromLabelOfTableCell:self];
Each instance of the tableviewcell would send itself to the delegate and the delegate would extract that instance's text. In this way, a single delegate object could handle an arbitrarily large number of cells.
A delegate is a way of adding behaviors to a class without subclassing or for attaching a controller to a class.
In the table view example you gave, the delegate is extending or controlling the table, not the cell. The table is designed to have a controller, the cell is not. This design choice is why you can't specify cell-specific delegates.
However, delegate methods will always announce the source object (the one to which the delegate is attached) and relevant parameters (like the cell involved) so you should always be able to handle the action fully.
In your case, if you have a cell and you would like the cell to manage itself, then the delegate method (which will probably be implemented on your UITableViewController) can simply fetch the cell from the source table using its NSIndexPath (passed as a parameter to the delegate method) and invoke a method on the cell subclass to do its work.
I always liked Chris Sells' ".NET Delegates: A Bedtime Story"
Related
I'm looking to have a single - (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath method that all UITableView's will use for formatting, etc.
What is the best way to accomplish this?
Right now I'm thinking I could use a separate class or put delegate methods in my app delegate and hook the UITableView's file's owner to that. But then I'd have to provide those classes/methods access to the data that would normally otherwise be right in the ViewController subclass.
How have others accomplished this sort of thing?
EDIT: To clarify, I'm not looking for a way to return a common cell itself, but rather a way to have a single cellForRowAtIndexPath method in my entire app.
If you just want to centralize all of the formatting but have the data sources separate in each class you could create a factory class for your table view cells which provides a method
+ (UITableViewCell* ) tableViewCellForTableView:(UITableView *)tableView withType:(int)type;
By creating type costants which get passed in, you can create a set of basic table view cells that can then be returned by this class. It is important to pass in the tableView to make use of reusable cells. A cellForRowAtIndexPath could now fetch cells from there and configure them depending on the data
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [MyCellFactory tableViewCellForTableView:tableView withType:kMyTypePlain];
cell.textLabel.text = ....;
}
You may consider using an objective-c category on your ViewController which implements the cellForRowAtIndexPath method.
If you're doing something very complex in this method however, I suggest you just create a nib file which contains your cell and then load it in cellFoRowAtIndexPath. You could even create a class that inherits from UITableViewCell and stick your own methods/properties in there. I highly recommend this approach.
You can load the nib, say "MyCustomTableCell.nib" which contains your customised UITableViewCell like so:
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"MyCustomTableCell"
owner:self
options:nil];
MyCustomTableCell* cell = [topLevelObjects objectAtIndex:0];
I think you can use the answer from #TriPhoenix for most of this.
The only additional thing I would add is that you could potentially send the data along with the call to the factory.
[MyCellFactory cellForTableView:tableView withType:kMyTypePlain data:data];
You would of course have to ensure that the data responds to a common interface to ensure that you can set things easily e.g. making sure each object responds to a similar method like
cell.textLabel.text = data.textLabelText;
You could do this using a protocol if you like. It's up to you how you structure your data.
I have a tab view that loads a table view for each of the tabs
First tab interface declares UITableView *tableView;
Second tab interface declares UITableView *favTableView;
When declaring the number of rows for the second table this works:
- (NSInteger)tableView:(UITableView *)favTableView numberOfRowsInSection:(NSInteger)section {
return [favList count];
}
But if I change it to:
- (NSInteger)favTableView:(UITableView *)favTableView numberOfRowsInSection:(NSInteger)section {
return [favList count];
}
the app crashes when I try to load the second tab
Is my mistake (a) not understanding which is a variable/reserved word, (b) giving each table a unique identifier ie favTableView, instead of reusing tableView.
Also the second table doesn't have a title bar
The delegate method you must implement is
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
In this method prototype, tableView: (note the colon) is a fixed name you cannot change. The second instance of tableView is simply a local variable name that has meaning within the method. The following would also be valid:
-(NSInteger)tableView:(UITableView *)tv numberOfRowsInSection:(NSInteger)section
The delegate method names are what they are. You cannot decide that you want the delegate method names to be something else, or how would UITableView know what methods to call when it needed information from its delegate?
So, for your table favTableView, if you specified the object that implements the above delegate method as favTableView's delegate, then when called the local variable tv would in fact be the same as favTableView.
I can see where you'd be confused about this. The SDK uses 'tableView' for a lot of things: method prototype placeholder names, variable names, and who knows what else. It boils down to being able to read and understand Objective-C method signatures. :-) It's a little strange, until you get used to it.
The delegate probably uses pre-set method names, as you point out. There shouldn't be a functional problem with not reusing tableView, as you probably reuses favTableView anyway. The title bar-thing should be solved by manually setting the properties for the title in your custom tableView. If you are looking for headers, you have to set the properties for height and size.
I am learning how to use the UITableView and UITableViewController in the iOS and I think I may have confused myself. I have created a simple TableView and I have 2 sections. Nothing complicated.
I have the following defined and it builds fine:
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
// Section is going to be either 0 or 1, high or low
if (section == 0) {
return 1;
}
else {
return 2;
}
}
However what I don't understand is the definitions of the methods. Both methods have to return an integer so I understand the starting (NSInteger). The numberOfRowsInSection starts with tableView:(UITableView *)tableView and I don't understand why?
I'm am new to programming the iOS so be gentle :-) All help greatly appreciated.
Mike
The method name is "tableView:numberOfRowsInSection:". The first argument is the instance if UITableView which is asking the data source for the number of rows in a particular section. This is a useful convention as you might have a single object act as the data source for many table views or want to update the table view in some way when a delegate method is called. By passing the calling object to the delegate you avoid needing to have the delegate maintain an additional reference to that object.
Take a look at the NSURLConnection delegate methods dealing with authentication for an example of where this is really necessary.
tableView:(UITableView *)tableView is helpful if you need to know which tableView sent that delegate method.
This is Apple's naming convention for delegate and data source methods. numberOfSectionsInTableView: has no arguments other than the table view, so that argument is added at the end. tableView:numberOfRowsInSection: takes another argument, the index of the section in question. Apple has decided that, when there are other arguments, the calling object should go first, and the arguments come after that.
Check out the UITableViewController Class Reference
- (NSInteger)tableView:(UITableView *)tableView
The first part, NSInteger lets you know that you need to return a number return 1;, the second part (UITableView *)tableView lets you know that you are dealing with the UITableView class.
Hi I have a view that contains 2 tableviews.
For a single view I know this delegate method can help me fill content of each row
(void) tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
But since I have 2 tableviews, how can I refer to each of them and fill content separately?
Thanks for helping!
The "current" tableview will pass a pointer to itself along as an argument when calling its delegate's methods like the one you mentioned.
So, all you need to do is to compare the pointer (tableView) to references of the two tableviews that you stored or added as property previously.
Proceed like so within your delegate methods:
if (tableView == myFirstTableView) {
//code to handle first tableview
} else if (tableView == mySecondTableView) {
//code to handle second tableview
}
Edit: Both tableviews need to share the same delegate for this to work, which would make sense anyway since they appear on the same view.
since you have two table view so you need implement proper if else condition where you need which table view is going to display.Ok
Make IBOutlet for both table.
then now in viewWillAppear
make datasource for the display(Array of data)
and
if(...)
{
firstTable.hidden=NO;
secondTable.hiden=YES;
[firstTable reloadData];
}
else
{
secondTable.hidden=NO;
firstTable.hidden=YES;
[[secondTable reloadData];
}
Now dont worry with every condition you would not require any condition in CellForRowAtIndexPath or didSelectRowAtIndexPath.
Further to Toastor's answer above, you may of course set different delegates and data sources for each table, although most often it is simpler to use the same delegate/data source for all NSTableViews in the same view.
I prefer to use property tag for distinguishing the desired UIView. The UITableView is a subclass of UIView, so it have this property too.
Is there any way to access the owning UITableView from within a UITableViewCell?
Store a weak reference to the tableView in the cell, which you'd set in -tableView:cellForRowAtIndexPath: of your table's dataSource.
This is better than relying on self.superview to always be exactly the tableView is fragile. Who knows how Apple might re-organize the view hierarchy of UITableView in the future.
Here's a nicer way to do it, which does not rely on any particular UITableView hierarchy. It will work with any future iOS version, provided that UITableView does not change classname altogether. Not only this is extremely unlikely, but if it does happen you will have to retouch your code anyway.
Just import the category below and get your reference with [myCell parentTableView]
#implementation UIView (FindUITableView)
-(UITableView *) parentTableView {
// iterate up the view hierarchy to find the table containing this cell/view
UIView *aView = self.superview;
while(aView != nil) {
if([aView isKindOfClass:[UITableView class]]) {
return (UITableView *)aView;
}
aView = aView.superview;
}
return nil; // this view is not within a tableView
}
#end
// To use it, just import the category and invoke it like so:
UITableView *myTable = [myTableCell parentTableView];
// It can also be used from any subview within a cell, from example
// if you have a UILabel within your cell, you can also do:
UITableView *myTable = [myCellLabel parentTableView];
// NOTE:
// If you invoke this on a cell that is not part of a UITableView yet
// (i.e., on a cell that you just created with [[MyCell alloc] init]),
// then you will obviously get nil in return. You need to invoke this on cells/subviews
// that are already part of a UITableView.
UPDATE
There is some discussion in the comments about whether keeping a weak reference is a better approach. It depends on your circumstances. Traversing the view hierarchy has some small runtime penalty as you are looping until the target UIView is identified. How deep are your views? On the other hand, keeping a reference on every cell has a minimal memory penalty (a weak reference is a pointer after all), and generally adding object relationships where they are not needed is considered a bad OO design practice for many reasons, and should be avoided (see details in the comments below).
More importantly, keeping table references inside cells adds code complexity and can lead to errors, because UITableViewCells are reusable. It is no coincidence that UIKit does not include a cell.parentTable property. If you define your own you must add code to manage it, and if you fail to do so effectively you can introduce memory leaks (i.e., cells live past the lifetime of their table).
Because typically you'll be using the category above when a user interacts with a cell (execute for a single cell), and not when laying-out the table in [tableView:cellForRowAtIndexPath:] (execute for all visible cells), the runtime cost should be insignificant.
Xcode 7 beta, Swift 2.0
This works fine for me, in my opinion it has nothing to do with the hierarchy or whatever. I had no trouble with this approach so far. I've used this for many async callbacks (ex. when an API request is done).
TableViewCell class
class ItemCell: UITableViewCell {
var updateCallback : ((updateList: Bool)-> Void)? //add this extra var
#IBAction func btnDelete_Click(sender: AnyObject) {
let localStorage = LocalStorage()
if let description = lblItemDescription.text
{
//I delete it here, but could be done at other class as well.
localStorage.DeleteItem(description)
}
updateCallback?(updateList : true)
}
}
Inside table view class that implements the DataSource and Delegate
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: ItemCell = self.ItemTableView.dequeueReusableCellWithIdentifier("ItemCell") as! ItemCell!
cell.updateCallback = UpdateCallback //add this extra line
cell.lblItemDescription?.text = self.SomeList[indexPath.row].Description
return cell
}
func UpdateCallback(updateTable : Bool) //add this extra method
{
licensePlatesList = localStorage.LoadNotificationPlates()
LicenseTableView.reloadData()
}
Ofcourse you can put any variable in the updateCallback and change it's function in the tableView accordingly.
Someone might want to tell me if it is save to use though, just to be sure.
You have to add a reference back to the UITableView when you construct the table view cell.
However, almost certainly what you really want is a reference to your UITableViewController... that requires the same thing, set it as a delegate of the cell when you build the cell and hand it to the table view.
An alternate approach if you are wiring up actions is to build the cells in IB, with the table view controller as the files owner - then wire up buttons in the cell to actions in the table view controller. When you load the cell xib with loadNibNamed, pass in the view controller as the owner and the button actions will be wired back to the table view controller.
If you have custom classes for your UITableViewCells, you can add an id type variable in your cell's header, and synthesize the variable. After you set the variable when you load the cell, you are free to do what you please with the tableview or any other higher view without much hassle or overhead.
cell.h
// interface
id root;
// propery
#property (nonatomic, retain) id root;
cell.m
#synthesize root;
tableviewcontroller.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// blah blah, traditional cell declaration
// but before return cell;
cell.root = tableView;
}
Now you can call any of the tableview's methods from within your cell using the root variable. (e.g., [root reloadData]);
Ah, takes me back to the good old days of flash programming.
The two methods in other answers are: (A) store a reference to the table, or (B) walk up the superviews.
I'd always use something like (A) for model objects and (B) for table cells.
Cells
If you are dealing with a UITableViewCell, then AFAIK you must either have the UITableView at hand (say you are in a table delegate method), or are dealing with a visible cell that is in the view hierarchy. Otherwise, you may well be doing something wrong (please note the "may well").
Cells are liberally reused and if you happen to have one that is not visible then the only real reason that cell exists is because of iOS UITableView performance optimization (a slower iOS version would have released and hopefully dealloc'd the cell when it moved off screen) or because you have a specific reference to it.
I guess this is probably the reason that table cells are not endowed with a tableView instance method.
So (B) gives the right result for all iOS's so far, and all future ones until they radically change how views work.
Though in order to avoid writing generalizable code over and over, I'd use this:
+ (id)enclosingViewOfView:(UIView *)view withClass:(Class)returnKindOfClass {
while (view&&![view isKindOfClass:returnKindOfClass]) view=view.superview;
return(view);
}
and a convenience method:
+ (UITableView *)tableForCell:(UITableViewCell *)cell {
return([self enclosingViewOfView:cell.superview withClass:UITableView.class]);
}
(or categories if you like)
BTW, if you are concerned about the effect of a loop with 20 or so iterations of that size on your app performance,.. don't.
Models
If you are talking about the model object that is displayed in the cell, then definitely that model could/should know about its parent model, which may be used to find, or trigger changes in, the table(s) that the cell's model might be displayed in.
This is like (A), but less brittle with future iOS updates (eg one day they might make the UITableViewCell reuse cache exist per reuseidentifier, rather than per reuseidentifier per tableview, on that day all the implementations that use the weak reference method will break).
Th model method would be used for changes to the data displayed in the cell (i.e. model changes) since changes will propagate wherever the model is displayed (eg. some other UIViewController somewhere else in the app, logging, ...)
The cell method would be used for tableview actions, which would likely always be a bad idea if the cell isn't even a subview of a table (though it's your code, go nuts).
Either way, use a unit test rather than assuming that seemingly cleaner code just works when they update iOS.
UITableView *tv = (UITableView *) self.superview.superview;
UITableViewController *vc = (UITableViewController *) tv.dataSource;