Should I removeTarget before I addTarget - objective-c

UIControl - changing assigned selectors: addTarget & removeTarget
States that you should remove the target before changing to another. However what if I am setting the target in cellForRowAtIndexPath? Should I remove the target before adding it again even if it is not changing? Will it call the method twice if I don't remove it or will it just overwrite it?
[cell.cellSwitch removeTarget:self action:#selector(notifySwitchChanged:) forControlEvents:UIControlEventValueChanged];
[cell.cellSwitch addTarget:self action:#selector(notifySwitchChanged:) forControlEvents:UIControlEventValueChanged];

Instead of adding/removing targets, I've found that if I'm already subclassing the UITableViewCell, I'll add a new delegate and set the delegate to the view controller. That way, any methods called on the delegate can pass in the entire cell and therefore I can get the index path of the cell by calling UITableView's indexPathForCell method.

Following my experience, it will be called only once.
But IMO, it is better to use removeTarget always because code can be changed in the future. And someday, you may need to add multiple targets and selectors.
Be a code in safe, scalable, and maintainable.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = // code with reuse identifier ...
if(cell == nil)
{
// making view for cell ....
}
// myAction will be called ONLY ONCE after many times of scrolling
[cell.myButton addTarget:self action:#selector(myAction:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}

Related

Getting the header view height of a section

I've implemented the
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
delegate method and within that I implement this piece of code
UITableViewHeaderFooterView *header = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:#"paymentFormHeader"];
header = (UITableViewHeaderFooterView *)view;
Then I try the and assign a UIView from the header with
UIView *header = [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:#"paymentFormHeader"];
and get a null value in return. This is my first time using this method so I'm probably not understanding it correctly and I noticed that it didn't ask for an indexPath. Any help with this would be greatly appreciated. Thanks.
There are a few issues here. The first is that you're not using the right method to set up the header. The method you're using (..willDisplayHeaderView..) is there to let you know when a header you've already set up is about to be displayed so you can do any additional setup or tracking after that point.
You will want to implement 2 methods to get this working:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
and
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
Your next issue is how you are trying to dequeue the view. You either need to check for a nonexistent dequeued view and initialize one manually with that reuse identifier, or just register the appropriate class in advance. I recommend registering it in advance. So to fix this, in viewDidLoad or at some point after your UITableView has been initialized, register the class like this:
[myTableView registerClass:[UITableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:#"paymentFormHeader"];
That will make sure you will always get a valid initialized view of that type for that reuse identifier when you try to dequeue it.

tableView numberOfRowsInSection calls tableView viewForHeaderInSection on iOS4

I got a big app containing a lot of dependencies. For this case I implemented a class called RootTableViewController to handle all the stuff that has to be done everytime a table view controller is required.
Now I discovered an endless loop and I dont know how to fix it. I got the following code in RootTableViewController:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
NSString *sectionTitle = [self tableView:tableView titleForHeaderInSection:section];
int numbersOfRowInSection = [self.tableView numberOfRowsInSection:section];
if (numbersOfRowInSection > 0)
{
// ...
}
else
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 28.0f)];
return view;
}
}
This works perfect on iOS 5 and iOS 6, but on iOS4 it causes an endless loop, because [tableView numberOfRowsInSection] is calling [tableView viewForHeaderInSection]. How can I fix this using the table view api? Its no solution for me to work with the [ count] of internal data arrays because I got a lot of table view controllers extending this RootTableViewController with different data sources.
This is simply not good style. You are supposed to subclass or rahter implement the related delegate method but you shoudl not call UITableView.numberofRowsInSection:
However, you have certainly implemented tableView:numberOfRowsInSection. Move all of its functionality to the new method myNumberOfRowsInSection: In there do the same. It is mainly a copy of your current numberOfRowsInSection.
Then here in your code sniplet call [self myNumberOfRowsInSection:...];
And within tableView:numberOfRowsInSection:section just do:
return [self myNumberOfRowsInSection:section];
Apply the same pattern to all delegate methods that you may want to call yourself. Move all its business logic into your own method and then only call your own method from the delegate method and from your own code.
If you want to get the number of rows in a section of the data source without accessing the internal data array, you could query the dataSource delegate for it, something like
int numbersOfRowInSection = [self.tableView.dataSource tableView:self.tableView numberOfRowsInSection:section];
(not compiler checked)

Updating subviews in cells on a UITableView

I'm developing an application in iPad 6.0 using Storyboards.
Let me first explain my goal. I'm trying to achieve a Master-Detail (SplitViewController-like) View Controller using 2 UITableViewControllers.
The first UITableView("Master"), let's call this HeaderTableView, as the name implies, lists down the Headers for the...
...Second UITableView("Detail"), let's call this the EncodingTableView, which contains a programmatically changing CustomTableViewCell (subviews contained within each cell may be a UITextField, UIButton or UISwitch).
See EncodingTableView.m
- (void)updateEncodingFields:(NSArray *)uiViewList
{
// Add logic for determining the kind of UIView to display in self.tableView
// Finally, notify that a change in data has been made (not working)
[self.tableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *encodingFieldsTableId = #"encodingFieldsTableId";
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:encodingFieldsTableId];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:encodingFieldsTableId];
}
// Change text in textView property of CustomTableViewCell
cell.encodingFieldTitle.text = uiViewList.title;
// added methods for determining what are to be added to [cell.contentView addSubView:]
// data used here is from the array in updateEncodingFields:
}
My HeaderTableView.m, contains the didSelectRowAtIndexPath to update the EncodingTableView
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (![selectedIndexPath isEqual:indexPath]) {
selectedIndexPath = indexPath;
[self updateDataFieldTableViewForIndexPath:indexPath];
}
}
- (void)updateDataFieldTableViewForIndexPath:(NSIndexPath *)indexPath {
[self.encodingTableView updateEncodingFields:self.uiViewList];
}
Question
- Data is all ok but why doesn't EncodingTableView "redraw"ing the fields? My
suspicion is that reusing cells has something to do with this but I just can't figure out why.
Screenshots on the result:
Initial Selection in HeaderTableView
Second Selection in HeaderTableView
What I've tried :
I kept seeing suggestions such as [UITableView setNeedsDisplay],
[UITableView reloadData] and [UITableView setNeedsLayout] but none of
them worked.
Removing the reuse of tableViewCells works fine but this causes parts of my
CustomTableView.encodingFieldTitle to disappear. Not to mention that this might cause performance issues if I were to drop reusing cells.
Restrictions:
I know that a good idea is to use a SplitViewController but this is just a subpart of my app (hence not the RootViewController).
Finally, thanks for reading such a long post. ;)
It looks like you are most likely adding subviews inside tableView:cellForRowAtIndexPath:.
The issue is that if you use cell reuse then are not always starting from a blank slate inside tableView:cellForRowAtIndexPath: instead you can possibly be given a cell back that has already been configured once. This is what you are seeing, a cell that has previously had labels added to it is handed back to you and then you add some more labels over the top.
There are a few way to deal with this:
(My preferred option) Create a subview of UITableViewCell with these extra sub views available as properties.
Ensure the cell setup is only done once
A great place to do this is when you actually create a cell when one does not already exist e.g. inside the if (cell) check
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:encodingFieldsTableId];
// add subview's here and give them some way to be referenced later
// one way of doing it is with the tag property (YUK)
UILabel *subView = [[UILabel alloc] initWithframe:someFrame];
subView.tag = 1;
[cell.contentView addSubview:subView];
}
UILabel *label = (id)[cell.contentView viewWithTag:1];
label.text = #"some value";
One problem i can see in your code is that the cell identifiers used are different in tableView cellForRowAtIndxPath function.
While dequeueing you are using this identifier - > "encodingFieldsTableId"
&
while creating a cell you are using this identifier - > "dataFieldUiGroupTableId".
Ideally these two identifiers should be same !!!
Try adding,
cell.encodingFieldTitle.text = nil;
Before if(cell == nil)
So that whenever your cellForRowAtIndexPath method is called, the string already present in the cell you are going to reuse will get deleted and the new text in uiViewList.title will be displayed.

tableView:cellForRowAtIndexPath: never being called

I have a UITableView, named tblParentComments in a UIView, of class CBox.
I have definitely set my view as the datasource and delegate of my table view, and my view does implement those protocols. The method tableView:numberOfRowsInSection: does get called and returns a non-zero value. But the function tableView:cellForRowAtIndexPath: is never called.
I noticed that if I put the method tableView:cellForRowAtIndexPath: in comments, Xcode does NOT stop compiling with an error like "tableView:cellForRowAtIndexPath: is required" -- the app just runs and show a empty table.
I don't understand. Any ideas? This is the code for my view:
Interface CBox.h
#interface CBox : UIView <UITableViewDelegate, UITableViewDataSource>
And in the implementation file:
- (id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
tblParentComments = [[UITableView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.frame.size.width, frame.size.height)];
tblParentComments.delegate = self;
tblParentComments.dataSource = self;
//tblParentComments.userInteractionEnabled = NO;
tblParentComments.backgroundColor = [UIColor clearColor];
tblParentComments.separatorStyle = UITableViewCellSeparatorStyleNone;
tblParentComments.bounces = NO;
[self addSubview:tblParentComments];
}
return self;
}
#pragma mark - UITableViewDelegate + UITableViewDatasource
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"num of rows = %d", parentComents.count);
return 1; // I set a non-zero value for test
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
.... // I set a breakpoint here, never been called here
}
YES..i have the same problem... and I just found out the solution.
In my class i use different inits with different parameters.
In my -(void)viewDidLoad i use to alloc the table view with CGRectZero, and ONLY in this case IF u DONT set up the FRAME of the UITableView then:
the numberOfRowsInSection will BE CALLED
the cellForRowAtIndexPath will NEVER BE CALL
I just set up my UITableView frame and it's works.
As I read above comments I can figure out couple of things:
You probably have messed up a bit structure of your code. You should always conform to protocols in your view controller - ! not view. Alternatively, what I like to do (as it gives me better control over my code and it keeps things clean), separate protocols out of view controller - means create new object (model object) that will handle everything what table requires and it will conforms to table delegate and datasource.
If you organise your code wisely, you should avoid situation you described.
Also I believe you may have 2 objects conforming to table protocols, and thats where the things get ugly.

Why is this UIButton not being initialized at the moment I call the init method at its super view?

I'm working with a view controller that I created (MyViewController) that has a UIButton called "button", this is the code that I've been trying to get it to work:
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
MyViewController *myViewController=[[[MyViewController alloc] init] autorelease];
[myViewController.button addTarget:self action:#selector(action) forControlEvents:UIControlEventTouchUpInside];
return footerController.view;
}
I'm not sure what's wrong with it, but apparently I can fix it if I do this:
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
MyViewController *myViewController=[[[MyViewController alloc] init] autorelease];
[myViewController.view addSubview:nil];
[myViewController.button addTarget:self action:#selector(action) forControlEvents:UIControlEventTouchUpInside];
return footerController.view;
}
Can someone please tell me why is this happening? And is there a way around this, because I don't think it's ok to do that..
The nib is not loaded until it is needed to save memory, as signaled by the view property being accessed. If possible, it would be better to do that sort of initialization in MyViewController's viewDidLoad method.
What Anomie said is right.. I found the way around it.. I can't access the button directly with myViewController.button, instead I have to use the method: [myViewController.view viewWithTag:buttonTag]. That way I'm making sure the button has already been initialized.