How to access UIButton (or any UIElement) inside UITableViewHeaderFooterView subclass? - ios

I'm learning UITableView in Objective-C. Could you hint me how to access UIButton inside UITableviewHeaderFooterView subclass from my UIViewController class? Programatically, as I don't use IB.
Full code: https://gist.github.com/tomnaz/3d790b308d305af8b98c
[[??? btnEdit] addTarget:self
action:#selector(addNewItem:)
forControlEvents:UIControlEventTouchUpInside];

Don't do this in viewDidLoad, do it in viewForHeaderInSection: where you have a pointer to your header view.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
static NSString *headerReuseIdentifier = #"TableViewSectionHeaderViewIdentifier";
ItemsHeaderView *sectionHeaderView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerReuseIdentifier];
[sectionHeaderView.btnEdit addTarget:self action:#selector(addNewItem:) forControlEvents:UIControlEventTouchUpInside];
return sectionHeaderView;
}

When you initially create the buttons, you can store them in a property or array so you can easily find them later.
Alternatively, you could set a tag on the button, and then call viewWithTag: on your UITableviewHeaderFooterView subclass to find the button.

Related

How to get which UITableViewCell selected without using UITableViewDelegate methods in iOS

I want to know that how to get selected cell's row index on selection of cell. But how to do it without usingUITableViewDelegate methods.
Please tell me.
I have search lot but not getting solution so please if anyone know tell me.
Please shared the viewpoints regarding it.
Thanks in Advance...!!!
In that case, your interviewer wanted to know how can you implement the delegation yourself...
To achieve that, Create a custom class "YourTableViewCell" extended fromUITableViewCell and use this class object to be returned in -tableView:cellForRowAtIndexPath:
Write a protocol "CellSelectionProtocol" with a method -
-(void) cellSelected: (YourTableViewCell *) cell;
Now delegate this protocol to your ViewController that has your TableView
and define the implementation of the method as below -
-(void) cellSelected: (YourTableViewCell *) cell
{
NSIndexPath *selectedIndexPath = [_yourTableView indexPathForCell: cell];
}
My answer would be this if it was an interview, and I am pretty sure it would be accepted.
But for a good architecture... the protocol & delegate implementation should be in two levels, like ->
YourTableViewCell -> delegates -cellSelected: -> YourTableView -> delegates -tableView:didSelectRowAtIndexPath: -> YourViewController
Please see: Your interviewer just wanted to know how you create delegations manually, instead of using default UITableViewDelegates.
EDIT # Unheilig
I mean in 2 levels because, the selection of a UITableViewCell has to be delegated to the UITableView and not directly to the controller for the following reasons
UITableViewCell is subview of UITableView
There can be multiple UITableView in a controller. So if you delegate cell selection directly, how will you tell the controller that cell got selected for which UITableView object?
Also UITableView might have to do other processing with other UITableViewCell, Like if selected and changes backgroundColor, the previous selected should get deselected and get default backgroundColor. Or add to the array of selected cells, if multiple selection is enabled.
There are many such similar architectural necessities that make me say - "But for a good architecture... the protocol & delegate implementation should be in two levels, like ->"
I hope that is pretty explanative now...
There is no way to get selected cell row index with out using tableview delegate methods.
when you click on tableview, didSelectRowAtIndexPath called and get the tableview cell index.
There is one way to do this, but it is not correct procedure to get tableview cell index. Create a button on tableviewcell and pass the indexvalue as sender tag to button action. But need to click only on that button.
Answer edited:
Create a transparent button on tableview cell in cellForRowAtIndexPath method and pass the cell index to button tag.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ;
}
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height);
button.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.0];
[button addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
button.tag = indexPath.row;
[cell addSubview:button];
cell.textLabel.text = [NSString stringWithFormat:#"%#",[numberArray objectAtIndex:indexPath.row]];
return cell;
}
-(void)buttonClicked:(id)sender
{
NSLog(#"%ld",(long)[sender tag]);
}

Adding UIView to UIViewController dynamically

I have an Array of 5 items. This array is dynamic therefore the number of items may vary.
I have aUIVIewController as shown in the following image. In my UIView, there are few components (Like buttons, etc). Based on the number of items in the above array i want to add the UIVIew to my UIViewContorlleras shown in the image.
For example: There're 5 items in the Array, then i need to add 5 UIView's to my UIViewController.
1.) I don't want to use a XIBfile for the UIView but want to use only StoryBoard. How can i design the UIView in StoryBoard ?
2.) How can i add UIView to the UIViewController dynamically as the number of items in the array increased ?
Loop through your array (I have assumed your array contains UIViews, if not you may update accordingly) like:
for(UIView *subView in arrayOfItems){
subView.position = specify the position
[self.view addSubview:subView];
}
1) You need to create a class, where the properties have IBOutlets, like this:
#interface MyView ()
#property (weak, nonatomic) IBOutlet UIView *viewContent;
#end
Than you can design your UIView inside the UIStoryboard and connect them from the class to the ui element. In previous xCode versions there was always a problem if you want to drag from UIStoryboard to the class, have to do it the different way.
2) To add a UIView to your UIViewController you just have to add it as subView inside your UIViewController like this:
[self.view addSubView:anyView]
set the frame of the sub views as per your design
for (int i=0;i<YourViewArrayCount; i++) {
[self.view addSubview:[YourViewArrayCount objectAtIndex:i]];
}
create a UICollectionView in the storyboard , and add a UICollectionViewCell prototype with a UIButton in it , and in the UICollectionViewDataSource method numberOfItemsInSection return your array count:
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return yourArray.count;
}
and in order to handle the button touch event ,you'll have to set the UIButton tag to 999 for example in the UICollectionViewCell prototype in storyboard,and in the UICollectionViewDataSource method cellForItemAtIndexPath get a reference to the button using its tag then add the touch event handler to it programmatically :
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"yourCellIdentifier" forIndexPath:indexPath];
UIButton *button = [cell viewWithTag:999];
[button addTarget:self action:#selector(buttonTouchHandler:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
finally to find out which button was actually touched (at which indexPath) , i suggest subclassing the UIButton class and add a property of type NSIndexPath (don't forget to change the button class in storyboard to your new UIButton subclass) and in the UICollectionViewDataSource method cellForItemAtIndexPath do the following
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"yourCellIdentifier" forIndexPath:indexPath];
MySepcialButton *button = [cell viewWithTag:999];
button.indexPath = indexPath;
[button addTarget:self action:#selector(buttonTouchHandler:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
this way in the buttonTouchHandler: method you can check the button property indexPath.

Can I have a UIButton's action method take a second parameter?

I want to place a button in each cell of a table. I want the target to be a class that I'm using to handle all of the API calls. I want the button's method to be in the friendController class, not in the UITableView's View Controller.
[cell.deleteButton addTarget:friendController
action:#selector(deleteFriend: forID:cell.idNumber)
forControlEvents:UIControlEventTouchUpInside];
Where friendController is the class with the API calls, and cell.idNumber is an instance variable of each cell containing the ID number of the user associated with that cell.
I know (well, I presume) that I could set the cell itself as the target and then have a method which looks like:
-(IBAction)deleteFriend:(id)sender {
[friendController deleteFriend:self.idNumber];
}
Is that correct? It doesn't seem like an elegant way of doing this. Is there a way to do it the way I want to, or is there a better way to do it?
EDIT: I emboldened the last crucial part of my question. I want the button's action to be a method in another class (friendController). friendController is a class I created to house all of the API calls and business logic. If I set the target to friendController, must the action be a method in that class?
You can do this two ways.
Subclassing UIButton
The first would be to subclass UIButton and create a property for the idNumber. So you would first set the property like so:
cell.deleteButton.idNumber = /* set id here */
[cell.deleteButton addTarget:friendController
action:#selector(deleteFriend:)
forControlEvents:UIControlEventTouchUpInside];
and then access it inside your deleteFriend: method:
- (IBAction)deleteFriend:(id)sender {
YourButtonClass *button = (YourButtonClass *)sender;
[friendController deleteFriend:button.idNumber];
}
Retrieve the idNumber directly from the cell
Described in this post you can grab the index path of the button's cell and then access the idNumber from there.
Use the tag property of the button.
cell.deleteButton.tag = cell.idNumber;
[cell.deleteButton addTarget:friendController action:#selector(deleteFriend:) forControlEvents:UIControlEventTouchUpInside];
In the method, you can use the tag to retrieve the cell idNumber
-(IBAction)deleteFriend:(id)sender {
int idNumber = ((UIButton *) sender).tag;
[friendController deleteFriend: idNumber];
}
With 2 parameters indicating the control that sends the message and the event that triggered the message:
action:#selector(buttonAction:Second:)
- (void)viewDidLoad
{
[super viewDidLoad];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn addTarget:self action:#selector(buttonAction:Second:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)buttonAction:(NSString *)firstCharacter
Second:(NSNumber *)second
{
}
Since the button is inside a UITableViewCell, you might consider subclass a UITableViewCell with your property idNumber(I don't like reuse Tag because it can only be an integer) and a delegate:
#protocol MyTableViewCellDelegate;
#interface MyTableViewCell : UITableViewCell
#property (nonatomic, assign) NSInteger idNumber;
#property (nonatomic, weak) id<MyTableViewCellDelegate> delegate;
#end
#protocol MyTableViewCellDelegate <NSObject>
-(void) shouldDeleteFriendById:(NSInteger)idNumber;
#end
And handle the button action inside the MyTableViewCell implementation:
- (void) myInit
{
UIButton* button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
[button addTarget:self action:#selector(onButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
- (IBAction)onButtonClicked:(id)sender
{
[self.delegate shouldDeleteFriendById:self.idNumber];
}
Then implement cell's delegate in your tableViewController:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyTableViewCell *cell = (MyTableViewCell*)[tableView dequeueReusableCellWithIdentifier:#"MyCell" forIndexPath:indexPath];
// Configure the cell...
cell.idNumber = indexPath.row;
cell.delegate = self;
return cell;
}
- (void) shouldDeleteFriendById:(NSInteger)idNumber
{
NSLog(#"should delete by id:%d", idNumber);
}

Implementing a custom table section header via Storyboard and new Xib

Here is what I have done:
I created a custom xib file that has a small UIView used for a custom table section header.
I classed the custom xib file.
I want to add this to a tableView as the header. I have looked at a few resources, but they seem to be either outdated or missing information.
Looking in the documentation, I see a reference to adding a custom header with the following instructions:
To make the table view aware of your header or footer view, you need to register it. You do this using the registerNib:forCellReuseIdentifier: or registerClass:forCellReuseIdentifier: method of UITableView.
When I added a tableView to my storyboard view, it was easy to assign it a reuse identifier within XCode. I was even able to create a custom cell xib file and it also had a spot for a reuse identifier within XCode.
When I created the custom UIView for the section header, it did not have an entry for reuse identifier. Without this, I have no idea how to use registerNib:forCellReuseIdentifier.
More information:
I have a storyboard scene that has a tableView inside. The tableView is of a custom class that is linked and the tableView object has an outlet in the parent view's ViewController file.
The parent ViewController is both the UITableViewDataSourceDelegate and UITableViewDelegate. Again, I was able to implement the custom cells with no issue. I can't even modify the header in any way besides the title.
I tried calling the method [[self tableHeaderView] setBackgroundColor:[UIColor clearColor]]; from the custom tableView class and nothing happens. I tried using this method in the parent ViewController class by using the outlet name like so:
[[self.tableOutlet tableHeaderView] setBackgroundColor:[UIColor clearColor]];
Any help would be greatly appreciated.
EDIT: (Can't change background to transparent)
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
HeaderView *headerView = [self.TableView dequeueReusableHeaderFooterViewWithIdentifier:#"tableHeader"];
// Set Background color
[[headerView contentView] setBackgroundColor:[UIColor clearColor]];
// Set Text
headerView.headerLabel.text = [self.sectionArray objectAtIndex:section];
return headerView;
}
You don't need to set the identifier in the xib -- you just need to use the same identifier when you register, and when you dequeue the header view. In the viewDidLoad method, I registered the view like this:
[self.tableView registerNib:[UINib nibWithNibName:#"Header1" bundle:nil] forHeaderFooterViewReuseIdentifier:#"header1"];
Then, in the delegate methods:
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *headerView = [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:#"header1"];
return headerView;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 100;
}
On the problem with background color (Unless you want transparent):
You can create an UIView which occupies the whole view then change the background color of that.
If you don't want others to know what's happening, you can overwrite the backgroundColor property:
//interface
#property (copy, nonatomic) UIColor *backgroundColor;
//implementation
#dynamic backgroundColor;
- (void)setBackgroundColor:(UIColor *)backgroundColor {
//self.viewBackground is the created view
[self.viewBackground setBackgroundColor:backgroundColor];
}
- (UIColor *)backgroundColor {
return self.viewBackground.backgroundColor;
}

iOS - Adding Target/Action for UITextField Inside Custom UITableViewCell

I have UITableView which uses a custom UITableViewCell containing a UITextField.
I want to call a method (myMethod) when it is clicked (UIControlEventTouchDown) and attempted to wire this up inside the UITableView delegate method cellForRowAtIndexPath by doing the following:
[tf addTarget:self action:#selector(myMethod) forControlEvents:UIControlEventTouchDown];
When the UITextField is clicked, nothing happens.
I attempted to do the exact same thing for another UITextField outside of the UITableView:
[othertf addTarget:self action:#selector(myMethod) forControlEvents:UIControlEventTouchDown];
When I click on othertf the method is called as I would expect.
I'm a bit confused as the code is identical apart from I've swapped tf for othertf.
Below is the complete code for cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *DetailCellIdentifier = #"DetailFieldView";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:DetailCellIdentifier];
if (cell == nil) {
NSArray *cellObjects = [[NSBundle mainBundle] loadNibNamed:DetailCellIdentifier owner:self options:nil];
cell = (UITableViewCell*) [cellObjects objectAtIndex:0];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UITextField *tf = (UITextField *)[cell viewWithTag:2];
tf.text = #"some value";
[othertf addTarget:self action:#selector(myMethod) forControlEvents:UIControlEventTouchDown];
[tf addTarget:self action:#selector(myMethod) forControlEvents:UIControlEventTouchDown];
return cell;
}
Can anyone spot what I'm doing wrong? It's probably something simple as I am new to iOS development.
Use UITextField delegate Methods:
UITextField delegate
//Use this method insted of addTarget:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
if (textField == tf) {
[self myMethod];
return NO;
}
return YES;
}
and dont forget to set the delegate to the textField:
tf.delegate = self;
When you touch a UITextField it swallows the touch itself and tells the keyboard to appear etc. Instead use the following events:
UIControlEventEditingDidBegin
UIControlEventEditingDidEnd
UIControlEventEditingChanged
Further improvements
Tags is a rather loose way of coupling your NIB and your code. Consider Making a DetailCell.h and DetailCell.m file, and set the root view in the NIB file to the DetailCell class, and create reference outlets for all the views you need to access in code, and action outlets for all the actions you need to react on.
This is done by CTRL clicking on a view in the interface builder, and dragging it into the DetailCell.h file. Interface Builder will now ask you wheter you want to create an action or a reference outlet (References are basically pointers, and actions are basically events)
dequeueReusableCellWithReuseIdentifier now calls initWithFrame on your DetailCell class every time is creates a new instance of it. It is now your job to load the NIB file in this function.
Alternatively you could register a NIB file for cell reuse on the tableview with
[tableview registerNib:[UINib nibWithNibName:DetailCellIdentifier
bundle:nil] forCellWithReuseIdentifier: DetailCellIdentifier];
which the automatically loads the nib file every time a new cell is created. In >= iOS 5 (AFAIK) dequeueReusableCellWithReuseIdentifier never returns nil, but creates instances of the registered classes/nib files itself every time.

Resources