Reference from UITableViewCell to parent UITableView? - iphone

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;

Related

Custom UITableViewCell objects being released - prototype cells in Xcode 4.2

I have a custom UITableViewCell subclass, and a table view controller which I want to insert text boxes into.
What I have done is setup a three dimensional array to store the sections, rows and then in the third dimension I have put the placeholder text I would like to display -
In the custom UITableViewCell subclass I have defined the text box as a weak property (I think this is correct...?)
#property (weak) IBOutlet UITextField *plainTextField;
I then add this text field to my data model array in
cellForRowAtIndexPath:
however, when I try to access the textfield from my array (specifically plainTextField.text) I get
''
I get the feeling that something is being released somewhere and I can't figure out where (the actual textfield is not nil)
I guess, my question is really a design one -- how do you get UITextFields into a UITableViewController and then get the text out of them later....?
Thanks!
I've 'fixed' my problem -- it's really based on the fact that you can't have different content in UITableViewCells, as soon as anything is different you have to load it in cellForRowAtIndexPath. (A note in the UITableViewCell prepareForReuse method)
What I did was reserve a spot in my data model for a UITextField, and then when cellForRowAtIndex path got to the cell it would query the data model, then instantiate a UITextField and add it to the cell.contentView or (here's the tricky part) loop through [cell.contentview subviews] to find any UITextFields and call removeFromSuperview

(iphone) how to refer multiple tableview

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.

Effective way to design and manage iPhone application similar to settings

I would just like to clarify that by 'design', I mean software design, not UI design.
I have an application similar to the native settings app. The problem I have with it is it doesn't follow the same clear-cut MVC style. Other apps tend to focus around displaying one kind of thing. In the case of a periodic table app for example, it's elements. The elements clearly comprise the model, and they share similar properties and behaviours, meaning they can be displayed and interacted with identically. An app like this almost designs itself!
My app, like the settings apps, consists of an arbitrary selection of rows displaying dissimilar data in dissimilar ways. One row might contain a switch, the other might modally present a very specific view when tapped. They're all very different.
How do you design something like this?
At the moment, I'm doing it all in the view controller, and the relevant rows are being tracked via an enum:
enum {
kNameRow,
kGenderRow,
kJobTypeRow,
kLevelOfExerciseRow,
kEmailAddressRow,
kTelephoneNumberRow
};
As I described, these cells are all very different, so displaying cells is handled like this:
// - tableView:cellForRowAtIndexPath pseudocode.
switch (indexPath.row) {
case kNameRow: // create name cell.
case kGenderRow: // create gender cell.
case kJobTypeRow: // create job type cell.
case kLevelOfExerciseRow: // create level of exercise cell.
case kEmailAddressRow: // create email address cell.
case kTelephoneNumberRow: // create telephone number cell.
}
And interacting with cells is handled similarly:
// - tableView:didSelectRowAtIndexPath pseudocode.
switch (indexPath.row) {
case kNameRow: // do name-specific stuff.
case kGenderRow: // do gender-specific stuff.
case kJobTypeRow: // do job type-specific stuff.
case kLevelOfExerciseRow: // do level of exercise-specific stuff.
case kEmailAddressRow: // do email address-specific stuff.
case kTelephoneNumberRow: // do telephone number-specific stuff.
}
This seems hugely unwieldy, and has the added of problem of not working when the table is broken down into multiple sections.
Is there a better way to do this? Are there any design patterns I would benefit from using when working with big tables of largely unrelated data?
Any tips at all are hugely appreciated.
I've become fond of implementing section controllers that pull the logic out of you UITableViewController subclass (or other hosting controller) and move them into self-contained classes.
I ended up implementing a base protocol that defines what a section controller needs to do - for me, that includes the number of rows in a section and a cell for the row (don't need the whole index path since the controller deals with a single section). I've got optional method for returning a section name and row height. That's all I've implemented so far since that's all I've actually needed.
It works for me because my individual sections tend to be homogeneous, but you could easily use the idea to return heterogeneous cells within the same section or refactor the idea to have cell type controllers instead of section controllers. In the end, my UITableViewDelegate and UITableViewDataSource methods just need to figure out which section controller to call instead of embedded all the logic within the UITableViewController subclass.
I think I got the idea from this article, but I also saw a more recent article that describes the same idea.
you might want to look at coreyfloyds project http://github.com/coreyfloyd/Generic-Heterogeneous-Table-Views i think this might have the functionality you need.
Here's my suggestion - handle each cell as a member of the view.
lol, it's been a while since I've used a table, so I could just be talkin' crap here but give it a try.
instead of an enum use:
NSThingyCell *nameRow;
NSThingyCell *genderRow;
#property IBOutlet NSThingyCell *nameRow;
#property IBOutlet NSThingyCell *genderRow;
- (IBAction) nameRowChanged:(id)sender;
- (IBAction) genderRowChanged:(id)sender;
and then instead of a table call with a switch, just wire each individual cell up in Interface Builder.
This has the added benefit of being row-independent, so if you have to put "ageRow" in between name and gender, nothing gets screwed up.
This will also get pretty big, so if your view has several tables, you may want to consider splitting those tables out into separate nibs/controllers and loading the views at run-time.
Have you ever thought of simply having an array of objects for a class which contains a UI element and some other identifiable data?
#interface settingsOption {
NSString *key;
UIView *displayElement;
}
+ (settingsOption *)optionWithKey:(NSString *)key andDisplayElement:(UIView *)displayElement;
#property (nonatomic, retain) UIView *displayElement;
#property (nonatomic, retain) NSString *key;
#end
Where the class method would look like
+ (settingsOption *)optionWithKey:(NSString *)key andDisplayElement:(UIView *)displayElement;
settingsOption *option = [[settingsOption alloc] init];
option.key = key;
option.displayElement = displayElement;
return [option autorelease];
}
Your settings class would have an array of settingsOption instances.
- (void)somewhereInMySettingsClass
mySettings = [[NSMutableArray alloc] init];
[mySettings addObject:[settingsOption optionWithKey:#"age" andDisplayElement:[UIButton buttonWithStyle:UIButtonStyleRect]]];
[mySettings addObject:...];
}
The table's cellForRowAtIndexPath would just do
[cell addSubview:[[mySettings objectAtIndex:indexPath.row] displayElement]];
You were talking about sections, though, which would add another layer to the data. This might simply be a matter of splitting mySettings into an array of arrays instead, where each array in the array is one section.
Not sure if I missed anything above. Feel free to point and poke.
You might simplify the settingsOption class further by adding more helper classes for various types of elements, e.g.
+ (settingsOption *)buttonWithKey:(NSString *)key;
+ (settingsOption *)switchWithKey:(NSString *)key;
+ (settingsOption *)pickerWithKey:(NSString *)key withDataSource:(id <UIPickerViewDataSource>)source withDelegate:(id <UIPickerViewDelegate>)delegate;
etc etc.

How to tell a UITableView to preload all Rows?

Is there a way to tell a UITableView to preload all rows?
The tableView is supposed to show several comments (up to 80 comments).
So my CommentCell uses a Setter to adapt the cell to a specific comment.
-(void)setComment:(Comment *)newComment {
if (newComment != comment) {
[comment release];
comment = [newComment retain];
/*
* set the cells view variables here
*/
}
}
This specific setter takes quite a bunch of processing resources and scrolling gets kinda laggy.
I am using a comment-specific reuseIdentifier instead of a static cellIdentifier when calling
dequeueReusableCellWithIdentifier:
in order to assure, that "newComment" equals the old "comment".
And in fact this does work great when scrolling over cells which have already been loaded.
But when scrolling through the comments for the first time, it still lags like hell.
Which leads me to my question:
Is there a way to tell the tableview to preload all cells? (which I doubt)
or
Do I have to implement my own cache instead of relying on "dequeueReusableCellWithIdentifier:"?
Keep in mind that your comment specific reuseIdentifier could be what is causing everything to go slow (or at least, it isn't helping). The reason we use reuseIdentifier for UITableViewCells is because if you try to allocate a new cell every time you need one it isn't as performant as if you can just reuse one that was already made.
I'd recommend pre-computing your comments so you can just set properties of your cells and reusing cells after they scroll off the tableview.
No, you have to preload your data in your dataSource. Just put everything you need in an array and fill the table's cells from that array of preloaded objects.

Clear tableView cell cache (or remove an entry)

I have the same question problem as described here How to purge a cached UITableViewCell
But my problem can't be solved with "resetting content".
To be precise - I use a custom cell (own class). While running the application it is possible that I have to use a different "cell type".
It's the same class - but it has (a lot of) differnt attributes.
Of course I could always reset all the things at "PrepareForReuse" but that's not a good idea I guess (there are a lot things to reset).
My idea - I do all these things in the constructor of the cell.
And all the rows will use this "type of cell" later.
When the (seldom) situation comes that I have to change the look of all rows I create a new instance of this kind of cell with different settings.
And now I want to replace the queued cell with this new one.
I tried it with simply calling the constructor with the same cellidentifier (in the hope it will replace the existing one) but that doesn't work.
I also didn't find a "ClearReusableCells" or something like this.
Is there a way to clear the cache - or to remove / replace a specific item?
Manfred
Create each cell type (even if they use the same cell class) using a different identifier. So if you have 2 cell types, define 2 identifiers and keep them separate.
I'm not sure where your problem is. You have a bunch of cells, with appearance A, then the user takes some action and they need to become appearance B. If you call reloadData or one of the more granular methods, your datasource will be called again for cellForRowAtIndexPath. Just implement this method to segregate the two cell types.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString* identifier = which mode are we in
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier]; // Will return nil if we haven't got this cell
if( !cell ) {
// Create different cell type based on the identifier
}
return cell;
}
Update
This doesn't work. The memory for the cell that is returned from dequeueReusableCellWithIdentifier is never released.
If you initialize a cell with a non-nil reuseIdentifier, then that cell will NOT be freed until the tableView itself is released. This is true even if [[tableView valueForKey:#"reusableTableCells"] removeAllObjects] is called. Unfortunately, the tableView is retaining the cell in some other private member and the only way to free it is by destroying the tableView.
Short Answer
The answer to the title of the question of how to clear the tableView cell cache is that Apple does not provide the "ClearReusableCells" functionality, but it is reasonably easy to implement yourself. Simply track the time that the tableView's cache was last cleared and track the time that the cell was created. A cell is considered to be dirty if it was created before the tableView's last cache refresh time.
Alternatives
Steven Canfield's answer - This is a good answer to the specific question, but it does not scale well if there are multiple attributes that can be changed independently of each other. In that case, you can very quickly have lots of 'modes' and that can get unmanageable. Also, with respect to memory usage, it is true that Apple code is responsible for managing the memory, but it is also true that library cannot know when a particular identifier might be needed again, so it will likely end up keeping these cached cells around longer than it needs to.
Recreate the tableView - This is a brute force mechanism that is guaranteed to clear the cell cache. Unfortunately, it is usually inconvenient to recreate the tableView.
Jake's Dahl's answer from the alternate question - This is a good mechanism for clearing the cache that probably worked at the time it was written, but it relies on some implementations that Apple does not guarantee, and that have, in fact, already changed.
Implementation
There are multiple ways of tracking the tableView's cache refresh time and the cell creation time. I've shown a mechanism below that uses subclasses. Of course, for this to work, you need to make sure that the code that instantiates the table and cells instantiates the subclasses.
#interface MyTableView : UITableView
#property(nonatomic,assign) NSTimeInterval cellCacheRefreshTime ;
#end
#interface MyTableViewCell : UITableViewCell
#property(nonatomic,assign) NSTimeInterval creationTime ;
#end
#implemetation MyTableView
-(void) refreshCellCache {
self.cellCacheRefreshTime = [NSDate timeIntervalSinceReferenceDate];
}
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier {
MyTableViewCell *cell = (id)[super dequeueReusableCellWithIdentifier:identifier] ;
if( cell.creationTime < aTableView.cellCacheRefreshTime ) {
return nil ;
}
return cell ;
}
#end
#implemetation MyTableViewCell
-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier] ;
self.creationTime = [NSDate timeIntervalSinceReferenceDate];
return self ;
}
#end