How to access parent object in Objective C - ios

I am creating a Custom UITableViewCell with a UITextfield in it, I would like to know how to access the the custom UItableViewCell from the UITextField delegate didend?
I am trying this
- (void)textFieldDidEndEditing:(UITextField *)textField {
CustomFinishingCell *updateCell = (CustomFinishingCell *)textField.superview;
but that's not returning a UITableViewCell, I think it's returning the UItableScrollView or something like that.

The textField's superview will actually be the cell's contentView. You could probably get the cell by doing:
CustomFinishingCell *updateCell = (CustomFinishingCell *)textField.superview.superview;
That's a little clunky though.
I also wouldn't recommend using tags as that's even more clunky.
What I would do, is create a subclass of UITextField with a property for your CustomFinishingCell and set that when creating the cell (where you add the UITextField). That will ensure everything keeps working, even if the user resorts the cells, adds/removes cells etc, and will work well with cell reuse.
Update:
In a lot of cases, it will be better to actually use #TimReddy's answer and set the delegate of the UITextField to the CustomFinishingCell and move the - (void)textFieldDidEndEditing:(UITextField *)textField etc code to that subclass. That will save you having to create another subclass for UITextField.

You need to have your custom UITableViewCell be that UITextView's delegate. That way you know for certain when the delegate fires it is that UITableViewCell's text view.
You may need to so some cleanup work before the UITableViewCell gets recycled tho.

You could give each cell view a tag, then call UIView's viewWithTag to get the view with the specified tag.
https://developer.apple.com/library/ios/documentation/uikit/reference/uiview_class/uiview/uiview.html

This is how I do it:
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:[textField convertPoint:textField.frame.origin toView:self.tableView]];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
I prefer this approach because it doesn't rely on tags or maintaining a reference to the container view from the UITextField, or traversing the view hierarchy. All those approaches are brittle for one reason or another.
Tags approach: You need to set the tag in tableView:cellForRowAtIndexPath: and if you have multiple sections, you need to encode the section.
Adding a property to UITextField: This either requires a UITextField category and associated objects, or subclassing UITextField. Both work, but are overkill.
Traversing the view hierarchy is brittle because it depends on implementation details that could change at any time.

Related

Custom UITableViewCell not Showing Reorder Control

I have a UITableViewCell that is a subclass of CEWendell's SWTableViewCell, a type of cell that allows for custom buttons accessible by horizontal swipe. The problem is that even with tableView:moveRowAtIndexPath:toIndexPath: and tableView:canMoveRowAtIndexPath: implemented, as well as explicitly having [cell setShowsReorderControl:true], the reorder control does not show up when editing begins.
I have also tested this with a regular UITableViewCell with the same result.
This was a somewhat foolish mistake. Since I was using a UIViewController subclass, not a UITableViewController subclass, I needed to set [self.tableView setEditing:!self.tableView.editing animated:true], not [self setEditing:!self.editing animated:true] in order for the tableview to begin editing.

UITableViewCell is redrawing on dequeue

My setup
I have a UITableViewCell that is in my main storyboard in a UITableViewController. It gets populated with some JSON data pulled from a REST API that will cause each cell to be a variable height. There are UIImageViews, UILabels all of different heights and styles, think Instagram-esque.
My problem
When I scroll to maybe the 5th or 6th cell, then go back up, they start redrawing and overlapping, so text gets mixed, lines get redrawn, etc.
What I've tried
This seems like a common problem on SO, so I've tried several posted solutions. It seems like my issue is probably the same problem as others face, which is, I am calling addSubview on my cell every time it dequeues, but I've tried checking to see if the cell already exists. I came across another post somewhere (sorry, I can't remember where), that suggests that because I am creating this in the storyboard, it is already initialized and if ( !cell ) will already return false, so I don't know how to prevent it from redrawing.
When I try removing the cell from the storyboard, and creating it programmatically, I get an error saying it can't find a cell with my identifier #"Cell".
I've also tried someone's solution of removing all subviews when I dequeue, so I used:
for ( UIView *view in cell.contentView.subviews ) {
if ([view isKindOfClass:[UIView class]]) {
[view removeFromSuperview];
}
}
and it doesn't find anything.
#rdelmar's comment is correct. You shouldn't do what you're doing. Might work, but it's bad form and you don't want to get into bad habits.
First, take advantage of object oriented programming. A cell should be able to configure itself based on the data you ask it to display. The table view shouldn't be designing the cell.
UITableViewCells need to be optimized for speed. Creating and adding subviews is a slow process. It's OK to do it once, but the cell will be reused (a system optimization) and you should just reuse the existing views that were added the first time the cell was created.
For example, you can hide subviews if they're not needed. You might want to do this in -prepareForReuse. You can move them around in -layoutSubviews. Or change the position of subviews in -updateConstraints.
Typically you just want to pass the data to display to the table view cell subclass from the data source (often the view controller). Let the cell do the display work.
When you add your subview after dequeueing uour cell, give a tag to your subview. This way, when you dequeue a cell, you can first check for the presence of a subview with your tag, and if it exists, remove it before adding your new view:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
// try to dequeue a cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:<yourCellIdentifier>];
if( !cell )
{
// create a new cell if necessary
}
static int _myViewTag = 1000987 // give it a high int : low value are used by the system in cells
UIView *v = cell.contentView viewWithTag:_myViewTag];
if( v ) // subview with such tag already exists, so remove it.
[v removeFromSuperview];
// now add your new subview
[cell.contentView addSubview:<yourView>];
// adjust height of cell to your view.
...
}
Try to add a new method in your cell class to reset cell to its default style and call this method after dequeueCell.
The most efficient way to manage this is to subclass UITableViewCell and adding all your required Views as properties. So now when a cell comes up for "recycling", you know where to find the old views, like :
[cell.myTextLabel setText:#""];
aaaand you're done.
UPDATE creating a subclass makes sense if you have only a small number of "TYPES" of cells. create a subclass for each. How much complicated it gets depends on your specific scenario. But i've done it and found it to be the most effective method.
UPDATE 2 or you could make multiple cells in the storyboard, and dequeue the appropriate one based on the data source, save all the coding.

Check if a UITableViewCell is selected, and the cell went off screen

I have a custom UITableViewCell that dequeueReusableCells. I have an int called selectedRow which gets the selected rows number in the method of didSelectRowAtIndexPath. I then pass selectedRow to an int called rowNumber which is in the class of my customCell.
In customCell.m, I have the method prepareForReuse. In that I made an NSLog of rowNumber.
What I want to do is: if a row is selected and that row went off screen, then perform some code. I would probably have to use prepareForReuse, but I don't know what to do in it.
I know it's a bit complicated, but if you have any questions, then I'd be happy to answer
Actually, you don't need to call prepareForReuse directly as it would be called automatically:
this method is invoked just before the object is returned from the
UITableView method dequeueReusableCellWithIdentifier:.
and as you don't know what to do in it, note:
For performance reasons, you should only reset attributes of the cell
that are not related to content, for example, alpha, editing, and
selection state
UITableViewCell Class Reference
You can use - (void)tableView:tableView didEndDisplayingCell:cell forRowAtIndexPath:indexPath; in UITableViewDelegate to know which cell is scrolled off screen.
However, this method is iOS6+ only.
You're over complicating things. You don't have to do prepareForReuse the in the custom cell.
Take a look at this.
http://www.icodeblog.com/2009/05/24/custom-uitableviewcell-using-interface-builder/
Its pretty similar for storyboards.

How to enable didSelectRow on selecting an element included inside the cell?

Well I have gone through web to search an easy implementation of this question but all I heard is coincide with what I thought at beginning which is not easy enough.
I was wondering if anybody could provide a better and easier implementation to solve this issue.
Well looking at the picture below, (Please neglect the crudeness of the picture)
I have a table view with multiple cells.
And based on the data, there will be different element(s) inside each cell.
If I touch inside the scrollview in cell 2 as shown in the picture. The delegate method for the tableView "didSelectRowAtIndexPath" will NOT be called, which is a problem for me.
So my first thinking is either to trigger the [tableView selectRowAtIndexPath] within the Scrollviews delegate method or I attach another tapGesture to these subviews and trigger it from there. But it already doesn't sound like a intuitive implementation and hard to maintain.
Anybody got any idea how could this be solved in an easier way?
Thanks
There are a wide variety of objectives you might be trying to accomplish here, so I won't speculate on the ultimate goal which you haven't told us, but in general:
If you would like two different UIElements to execute the same chunk of code, then you encapsulate that code in a stand-alone method, and call that method from both places.
In your UIScrollView as a subview of a UITableViewCell example, you would call it from within -(void)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath and you would also call it from the gesture recognizer that you attached to your scrollView.
I will speculate that the part you're having trouble with is knowing which scrollView was touched, or which row the scrollView was in. One possible approach would be to send the UIScrollView or the UITableViewCell as a parameter for the standalone method you created. -(void)doSomethingSpecialWith:(UITableViewCell*)cell or -(void)doSomethingSpecialWith:(UIScrollView)view
Post some more code, or feel free to explain your agenda further, but I have a strong feeling that the best way for you will be to subclass UITableViewCell, add the UIScrollView as a property of the custom cell, and wire up the gesture recognizer to call a method of your subclassed UITableViewCell.
i think the better way to solve this problem is :
creat custom method , - (void)tableviewSeletedIndexPath:(NSIndexPath *)indexPath
you can call this method in TableViews delegate -(void)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath , and call this method in your scrolls method.
Make the tag of scrollView is equal to the current cell row:
scrollView.tag = indexPath.row;
Make your ViewController to be the delegate of UIScrollView and in didScroll get the index and call didSelect:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSIndexPath *index = [NSIndexPath indexPathForRow:scrollView.tag inSection:0];
[self tableView:self.tableView didSelectRowAtIndexPath:index];
}

Adding rows to UITableView built from Storyboard

I have a static UITableView built from a Storyboard that works well. I want to fill the first category programmatically, though, from a user-defined file
Simply put, I want to go through all the strings in an array and add them as cells for the rows of the first category. For the second category, I have a series of mildly complex cells (containing a number of labels, textfields, buttons and other controls), defined in the storyboard, that I don't feel like recreating in code.
As far as I understand, the default behaviour for a UITableView built from a storyboard is to use the nib file as an implicit datasource. If I use a custom class as datasource, my second section doesn't work. I have thought of two possible ways to fix this:
Fill my first category from the datasource and delegate the rest to the nib file. Is this possible? Is there some method to programmatically ask the nib to fill my UITableView?
Export my storyboard-built cells into code and paste this code into my datasource. This method has the disadvantage of making my second category harder to modify.
Is one of those two options feasible? Is there another option?
I would use dynamic prototype cells. Then, I would set up the ViewController as the delegate and the dataSource. I would then create a custom subclass of UITableViewCell and connect the elements of the second section to IBOutlets in the custom UITableViewCell.
If the first section wasn't something that could be done with one of the generic cell types, I would also create a custom subclass of UITableViewCell for that section as well.
I would then use the cellForRowAtIndexPath: method to set up the cells with the information that I want in them. So if my first section used FirstSectionCell and my second section used SecondSectionCell as custom subclasses of UITableViewCell my cellForRowAtIndexPath: would look like this:
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.section==0)
{
FirstSectionCell *firstCell = [tableView dequeueReusableCellWithIdentifier:#"First Cell Prototype"];
//Set up the first cell.
return firstCell;
}
else if(indexPath.section ==1)
{
SecondSectionCell *secondCell = [tableView dequeueReusableCellWithIdentifier:#"Second Cell Ptototype"];
//Set up second cell.
secondCell.someLabel.text = #"whatever";
//etc.
return secondCell;
}
else
{
//if you have another section handle it here.
}
}
There are two kinds of table views when you use Storyboards:
Static
Dynamic
You're currently using the former. You define everything in the Storyboard and have very little code.
But you need to change to the latter.
You can still keep your UITableViewCells in the Storyboard; there's no need to do that in code (though you can if it makes things easier). You can refer to the template cells using the "reuse identifer."
Otherwise you've pretty much got it. You'll need to write code to implement the data source and (possibly) more methods of the table view delegate.
It's kind of fiddly switching from static to dynamic. I keep meaning to raise a Radar because I'm sure Xcode could be making it easier to do...

Resources