This question already has answers here:
How to know the UITableview row number
(10 answers)
How to get UITableView from UITableViewCell?
(21 answers)
Closed 8 years ago.
I've used this code for to get an UIButton from inside my UITableViewCell but it didn't work. So I've debugged it like half an hour to know what superview it in is.
UIButton *button = (UIButton *)sender;
UITableViewCell* cell = (UITableViewCell*)button.superview.superview.superview;
UITableView* tableView = (UITableView*) cell.superview.superview;
NSIndexPath* indexPath = [tableView indexPathForCell:cell];
The question for me is now: How can I know in the future in what hierarchy it is for a UITableView or others? Isn't there a list where I can see how much superviews I need to go without testing it 100 times and set BreakPoints to see what class it refers to?
Regards
You may just end up going with the superview hierarchy route, but know this is not a good idea because Apple could change their table view hierarchy any time. There are much better ways to get the index path for a table view cell given a button inside one of the cells.
If all the buttons are in one section of the table view, simply set each button's tag to the cell's index path row. Then you can recreate the index path knowing which row the button is in.
You can always subclass UITableViewCell and implement the action method for the button inside the subclass. The subclass might call a delegate method to whichever object wanted to know about the button tap.
So hopefully this gives you an idea of the alternatives to what you were doing before. As you said, it's time-consuming to figure out the superview hierarchy and it could change at any time.
If you want to check multiple superview hierarchy then check like that below:-
UIView * view=(UIView*)sender;
while ((view= [view superview])) {
if(viewisKindOfClass:[UITableView class]) {
//your code
return;
}
}
Related
I have a collection view, and I am trying to get the index of the cell that I am peeking and poping from.
Issue
Currently I am using indexPathForItemAtPoint: however this always returns 0 no mater where I tap on the screen.
Code
collection view controller:
- (void)viewDidLoad {
[super viewDidLoad];
[self registerForPreviewingWithDelegate:self sourceView:self.collectionView];
}
- (UIViewController *) previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
CellEditViewController *CEVC = [self.storyboard instantiateViewControllerWithIdentifier:#"detail"]; //The view I want peek/pop to
NSLog(#"Location: %f,%f", location.x, location.y);
NSLog(#"Index of: %lu", [[self.collectionView indexPathForItemAtPoint:location] row]);
[CEVC setPreferredContentSize:CGSizeMake(0.0, 320.0)];
return CEVC;
}
- (void)previewingContext:(id <UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit {
[self showViewController:viewControllerToCommit sender:self];
}
What I have tried
Creating a new location to identify the index cell.
Moving registerForPreviewingWithDelegate:sourceView: to where I create each cell.
Moving previewingContext:viewControllerForLocation: and previewingContext:commitViewController: to the cell view method, this did not work for other reasons.
I do not think this is an issue with previewing, because when I implemented the same thing with a UITapGestureRecognizer, I got a similar output:
Tap recognizer:
- (void) processDoubleTap:(UITapGestureRecognizer *)sender {
NSLog(#"Got tapped twice");
if (sender.state == UIGestureRecognizerStateEnded) {
CGPoint point = [sender locationInView:self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:point];
NSLog(#"Index was: %lu", [indexPath row]);
if (indexPath) {
NSLog(#"Index was double tapped");
}
}
}
Output:
2017-12-25 10:48:13.990523-0800 quickthings[3052:356150] Got tapped twice
2017-12-25 10:48:13.990843-0800 quickthings[3052:356150] Index was: 0
Source
Github Repository
Screenshot
Here is what does happen, this is exactly what I want. The only other thing I would like to do is when the cell is tapped also be able to get the index of the tapped cell.
Collection View In Story Board
The (blue) UIView is "linked" to the Collection View Controller (the top view controller in the second screenshot below).
The Cause:
The main cause of the problem is how the CollectionViewController is setup in the storyboard. In summary these are the main issues with it:
The CollectionViewController was added as a UIViewController and then the class changed to CollectionViewController but the problem is that is a subclass of a UICollectionViewController. So there is a miss match between what the storyboard thinks it is and what it actually is. You should have added it as a UICollectionViewController to begin with.
The CollectionViewControllers top view has had it's class changed from UIView to UICollectionView which I assume was to match how the UICollectionViewController is setup. So again there is a miss match here meaning you can't see any of the correct properties in the Interface Builder.
There is then an additional UICollectionView added as a sub view of the main CollectionViewController main view. This has its data source and delegate linked into the CollectionViewController class and is the one that is actually being displayed. It's all setup correctly with a prototype cell. However it's not linked into the class as an IBOutlet so you can't reference it.
So why is this causing the problem you are seeing. In the CollectionViewController when you refer to self.collectionView you are referring to the top level one which is not the one that is displaying the cells. That's not the one displaying the cells and in fact has no displayed cells so when you ask for the cell at a particular point it will always return nil and hence you get a row value of zero. If you were able to get access to the UICollectionView actually displaying the cells you could get the correct value.
Other than that there will be other issues that you haven't come across yet.
The Solution:
There are basically two main solutions to this problem...
Remove the CollectionViewController from the storyboard and add it again as a UICollectionViewController. This of course is the ideal way to resolve it as it will all be setup correctly however it will require re-creating the entire controller in the storyboard again and is not absolutely necessary as you can do method 2...
You can modify your storyboard and setup to correctly reflect the setup you have and get it working.
I have tried method 2 and this is what I had to do to get it working correctly:
Change the CollectionViewController from subclassing UICollectionViewController to subclassing UIViewController as it will just become a holder for the UICollectionView. You also need to make it support UICollectionViewDataSource and UICollectionViewDelegate directly as the UICollectionViewController already does this but the UIViewController doesn't. So change this line in CollectionViewController.m:
#interface CollectionViewController : UICollectionViewController
to this:
#interface CollectionViewController : UIViewController <UICollectionViewDelegate, UICollectionViewDataSource>
Next in the storyboard change the top level view of the CollectionViewController back to a UIView (i.e. delete the UICollectionView in the class property). It's now just a holder for the real UICollectionView.
Next link the remaining UICollectionView that is setup in the storyboard to an IBOutlet in CollectionViewController.h called 'collectionView' so that all the references to self.collectionView are now referring to the correct thing.
One extra thing I had to do but I'm not sure why is change the cell identifier for the UITableViewController and UICollectionViewController so that they are not both 'Cell'. I just used 'TableCell' and 'CollectionCell'. If you don't do this they both get confused for some reason.
(I think that is all I did but if not I'm sure you will be able to handle any other issues)
Once all that has been done then the self.collectionView will be referring to the correct thing and you will get the correct index path in the - (UIViewController *) previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location method.
Bonus Round:
Now some extra bonus comments. You appear to also have the same kind of issue with the TableViewController although it doesn't appear to be causing any issues at the moment. You may want to fix it though.
When I first ran the project I could not see anyway to add anything to the table because the text field and 'add' button were missing. This turned out to be because I was using an iPhone 8 simulator not an iPhone X simulator and once I switched everything was ok and I could see the text field and 'add' button. What this means is your interface is not adapting to the different device sizes correctly (at all really) and you should really address that.
Phew that ended up longer than I thought but hopefully will clear up the problem. Also as I had access to the git repository I could clone it and make my changes. I obviously haven't committed them as it's your repository but if you have any trouble understanding what I have done to get it to work I can commit them ether into your main branch or as a new branch.
What I'm doing?
I'm adding dynamic views to my UITableViewCell (which isn't subclassed).
Cell Hierarchy :
UITableView > UITableViewCell > cell.contentView > MainView > ([Number Of PointView] + [Options View]).
Here it is:
When a user will tap "Add Another Point", I'll add a PointView (which will be same as above) :
Y position [ ] X position [ ]
which will be look like this,
What is my logic to get this done?
I'm taking MainView from cell.contentView.
Then fetch last two views (Point View & Options View) added into MainView.
Add Another PointView in MainView.
Update frames for newly added PointView based on last PointView and also update frame of OptionsView.
Resizing height of MainView
And reloading particular cell.
I'm able to get it work, to confirm I've logged frame and subviews for that MainView. But once I tap on "Add Another Point" button again, I found that MainView isn't updated at all?
My simple question is, if I have a MainView (which I fetch from a Cell), can I update it directly or not?
P.S. I already have a poor solution for this is, to remove MainView and recreate new – which I found unnecessary. Any thoughts?
Short answer, yes you can.
cell.contentView is a placeholder. What you are likely running into is an entirely different problem: UITableViewCells are cached, then recycled and reused by the system.
Anytime you respond to cellForRowAtIndexPath, you basically need to reset the content.
I think what you should do. Just make this one cell class
This one other cell subclass
Use this method to insert your rows and also update your data source for table view that is the number of rows in section part
Your + button on click method can be like this..
//Use your own logic what you want to do.
Update your data source
NSIndexPath* index=[NSIndexPath indexPathForRow:[table count]-1 inSection:0];
NSMutableArray* indexPathArray=[[NSMutableArray alloc]init];
[indexPathArray addObject:index];
//index will be your new added point cell. Updating your datasource
[_myTableView insertRowsAtIndexPaths:indexPathArray
withRowAnimation:UITableViewRowAnimationTop];
Make this YES
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return YES if you want the specified item to be editable.
return YES;
}
I have UITableView which cells contain one UITextField in it. My UITableViewController is a delegate of all this text fields. Now, when UITableViewController gets deallocated, I want to set delegate of all that text fields to nil. The text field has a tag, so I can get it by it's tag once I have a cell.
The question is how to get all created cells? Asking UITableView for visibleCells returns only visible cells, but it can happen, that there is a row which is not visible, bit it still has my UIViewController as a delegate. So I really need to get all created cells somehow. cellForRowAtIndexPath does the same, so it wouldn't work for me either. The only way I see here is to store all text fields in array, but may be there is a better way?
Here is some code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:#"reuseId"];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"reuseId"];
UITextField *nameTextField = [[UITextField alloc] initWithFrame:nameTextFieldRect];
nameTextField.tag = TEXT_FIELD_TAG;
nameTextField.delegate = self;
[cell.contentView addSubview:nameTextField];
}
return cell;
}
-(void)dealloc
{
// todo: get all text fields and set theirs delegate to nil
}
Well, most answers suggest that I don't need to set delegate to nil, but as I'm paranoid, I suspect that the following scenario is possible:
User taps 'Back' button, so dealloc of my view controller is called. In dealloc my view controller releases it's table view, but the tableView still exists at this point, as well as all the text fields. And if somehow one of text fields would call it's delegate method at this point, the app would crash, because the delegate is not a valid object anymore.
If someone can explain why this scenario is not possible, than it would convince me that I don't need to set delegate to nil.
You do not need to do that. All of the cells will be deallocated as well, so they won't have a reference to the delegate.
By default the delegate is a weak reference, so it will not retain your object.
I am not expecting to have this answer marked as accepted, but this won't fit into a comment window.
So, rather than us trying to convince you, you could start one of the most important tool for an iOS developer, which is the profiler. And see for yourself by playing with the interface that you should get no more than the number of cells necessary to fill the screen are kept allocated, and then when you tap back, all are getting deallocated.
If they are not, they probably have a strong reference to something else, but this can easily detected with the profiler.
I also like to add that when working with cells the act of scrolling UITable, tap back enter again into table, scroll tap back (repeated at least 10 times) it's a mandatory practice to detect memory leak.
Also, I don't know the purpose of assigning a tag to the cell, I maybe wrong but with this:
nameTextField.tag = TEXT_FIELD_TAG;
consider that you have more than one cell with the same tag, therefore you cannot simply recall the desired one. I remember that the rule is the first placed on screen 'win' the TAG (or kind of).
[UPDATE]
Just a guess, I have never proved this, but to stay on the question, if your problem is to have a cell first for having the UITextView, have you tried to loop the main view and just ignore the cell:
UITextView textView = [mainView viewWithTag:TEXT_FIELD_TAG];
while(textView!=nil){ // or whatever loop or criteria you like
// deallo, nil, etc...
textView = [mainView viewWithTag: TEXT_FIELD_TAG];
}
Create a new delegate/protocol for that cell, and implement that delegate in the view controller, like PersonTableViewCellDelegate. Apart from that, your cell implements the UITextField delegate and in that code, call [self.delegate onKeyPressed] or whatever. I recommend you pass also a reference of the cell, so in the view controller you can use indexPathFromCell (or something like that) to know the position of the cell. If you are interested tell me about it and will copy some code.
I'm trying to create a view similar to add/edit contact in iOS and there are a few things that are happening, and I'm not sure how they are implemented. Any help in understanding is greatly appreciated.
For each section in contacts i.e. name, phone number, email, etc are these each their own tableview or are these sections within a larger tableview?
When clicking done when adding or editing a contact, the unused tableview cells disappear. Is this using deleteRowsAtIndexPaths:withRowAnimation: or is there a hide method I haven't found? If it is using that method, then when clicking the edit contact button, how does the view brings back these unused tableview cells?
When clicking on a cell in the tableview cell when editing a contact, you are able to change the text. Is this a textfield within a tableview cell or is it actually modifying the label of the tableview cell?
I am not looking for any specific code, as a fairly new programmer I am just trying to understand the strategies/best way to implement these features.
I tried a lot different ways to implement that. the easiest one: Subclass UITableViewCell and overwrite setFrame:. note that this is easy to achieve for grouped tables, but hard for plain ones. in the datasource's tableView:cellForRowAtIndexPath: create an object of this custom cell for the first section. use another identifier for cells of that section, so that only the correct cells will be re-used.
yes, I assume that. The controller has some sort of definition how many cells has to be show in edit mode and how many are actually used with some sort of information. you can easily create a array of indexPaths that must be deleted.
I would do it in tableView:didSelectRowAtIndexPath: by fetching the cell via tableView:cellForRowAtIndexPath:, hide the label and unhide or add a textfield and make this first responder.
code for 1.
the cell
#interface InsetCell : UITableViewCell
#property(nonatomic)CGFloat inset;
#end
#implementation InsetCell
- (void)setFrame:(CGRect)frame {
CGFloat inset;
if (_inset == 0) {
inset = 70; //default value
} else {
inset = _inset;
}
frame.origin.x += inset;
[super setFrame:frame];
}
-(void)setInset:(CGFloat)inset
{
_inset = inset;
[self setNeedsLayout];
}
#end
a project that uses similar code
I am looking at the TableViewSuite example code from Apple. In Suite 5 - they have a UITableViewCell which has another UIView within it. That view is responsible for drawing the view. My question is about how it handles highlighting. In the UIView they have the following:
- (void)setHighlighted:(BOOL)lit {
// If highlighted state changes, need to redisplay.
if (highlighted != lit) {
highlighted = lit;
[self setNeedsDisplay];
}
}
My question is - how does this get called? I searched for highlight and there aren't many matches in the project. Does a UITableViewCell call setHighlighted on all of its subviews when it has setHighlighted called on itself? I'm assuming this is what is happening but can't find any documentation that states this.
I'm pretty sure the table view cell recurses into its subviews. I recall having a subview in a custom table cell that would highlight if the cell was highlighted.