Populating DetailController from UITableViewCell via segue - iphone

I am populating cells in a UITableView from CoreData and I have a question about how best to transfer the data I need to my DetailController (i.e. the viewController that shows when the cell is tapped).
Currently when populating the UITableViewCells I use the indexPath passed to the cell to retrieve the appropriate managedObject from Core Data. At this stage I only update the cell with a few bits of information from the managed object (i.e. name, age, sex).
After the UITableView has been fully populated I want to allow users to select a UITableViewCell and present a DetailController showing more in-depth information (i.e. name, age, sex, occupation, weight, height etc.) When the cell is tapped I am using -prepareForSegue to transition to the newly presented DetailController.
My question:
The "sender" for the segue is the UITableViewCell (subclass) but I have only populated this with the information I needed to originally display in the smaller cell. Should I
add more iVars to the UITableViewCell subclass and store all the data
I need,
keep a pointer iVar on the cell to the model managedObject
something else that I might have missed?

With the Model View Controller pattern we use in objective-c Your views should never know about your data model. This means that your UITableViewCells should not have any properties that store objects from your data model. Cells should only have labels and views as properties which your view controller fills in with data from the model in its -tableView:cellForRowAtIndexPath method or similar
You get the UITableViewCell object as the sender in prepare for segue. You can find out the index path for the cell with the following method:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
NSIndexPath * selectedIndexPath = [self.tableView indexPathForCell:sender];
...
You can then use this index path to pull your model object out of core data again, and set it as a property on your next view controller, or whatever you need to do to pass the model object forward

Related

Does the UITableView function visibleCells return custom cell data?

I have a number of custom cell objects (subclasses of UITableViewCell) with a couple of values in them to allow for user interaction within individual cells (like steppers or something). These values are stored within the custom cell class, since calling up to the owner of the table view seemed like a bad idea at the time.
I know of the function (NSArray *) visibleCells. Will that allow me to access the data within the cell objects?
If not, how?
I'm assuming that I can use the built-in functions of the UITableView to pull returned UITableViewCells, but is that sufficent when I'm talking about a subclass of that called, say CustomizerCell?
The function:
- (NSArray *)indexPathsForVisibleRows
Answers an array of index paths. Those index paths can be used the same way your cellForRowAtIndexPath uses the passed index path to access your model.
MyObject *myCustomDataSupportingACell = [myDatasourceArray objectAtIndex:indexPath.row];

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

Can't access UITableView Datasource methods using delegate of UISegmentedControl

I am using a UISegmentedControl in my viewController. With one segment selected, I want the Table's data to be loaded from the NSFetchedResultsController (already setup), and from the other segment, I want data to be loaded from an array.
I am using the segment's delegate method here
- (void)segmentedControl:(SVSegmentedControl*)segmentedControl didSelectIndex:(NSUInteger)index
The problem here is I can't seem to acces the tableView data source methods using the segment's delegate.
For example, it would be good if in the didSelectRowAtIndexPath method, I could do something like if (selectedSegment == 1), then xxxxx.
Does anyone know how I can do this?
I want to push to a different view when a row is selected depending on the segment.
Assuming you've synthesized your segmented control as a property, you can certainly access it from your tableView data source methods.
For example, if your segment is called userSegment you would just do
self.userSegment.selectedSegmentIndex
And then proceed accordingly. e.g. if(self.userSegment.selectedSegmentIndex == 0) {...} else {...}. There are a variety of methods you can do with this index, too. Check out the documentation. One easy example:
NSString *selectedTitle = [self.userSegment titleForSegmentAtIndex:self.userSegment.selectedSegmentIndex];
That's just a simple example, but the point is you can easily use the state of your segment to conditionally populate your tableView data source.
Edit: See the UISegmentedControl Class Reference for more info on selectedSegmentIndex and other properties and methods you can use.

Fetching value of label in custom cell

In custom cell I have a UILabel and UISwitch. I want to fetch the text of label when the switch is on. I have kept a method on switch's value changed event but the application is getting crashed.
I'm assuming your mean a custom UITableViewCell. There are a number of ways of getting to the associated label when the switch changes:
If your table is not variable length (so that the cell with the label/switch is unique), when you create the cell cache the UILabel * object and the switch object in your UITableViewController subclass as ivars and associate them together.
If your table is variable length, you need to either maintain arrays of UILabel * and switch *, or you can also subclass UITableViewCell (note that you can provide a custom interface for a UITableViewCell via nib without having to necessarily subclass, you don't mention whether you've subclassed or not), hook up the switch event to go to your UITableViewCell subclass, then read the corresponding label, and/or forward the event to the UITableView subclass (this is a "push" model rather than the "pull" model), sometimes this organization is easier to manage than trying to maintain arrays of objects in your UITableViewCell that track objects in individual UITableViewCells.

Reference from UITableViewCell to parent UITableView?

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;