I have a UITableView with custom views for section headers. I added a UITapGestureRecognizer to the customer sections header views to detect when someone has tapped on a section header.
How do I figure out which section the section headers belong to?
Thanks in advance.
The easiest way is to designate a property on your section header view class to hold the section index, then assign the index to that property in -tableView:viewForHeaderInSection: like this:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
// CustomHeaderView *headerView = ...
headerView.section = section;
// ...
return headerView;
}
Then have the gesture callback look at that property.
The action method that you are providing must have the following signature :
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer;
And a gestureRecognizer have the following properties :
Getting the Recognizer’s State and View
state property
view property
enabled property
So basically you can ask for the view that it is attached to and interrogate that view.
in the viewDidLoad section insert your gestureRecognizer:
- (void)viewDidLoad
{
(...)
UITapGestureRecognizer* doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleTapTable:)];
doubleTap.numberOfTapsRequired = 2;
doubleTap.numberOfTouchesRequired = 1;
[self.yourTable addGestureRecognizer:doubleTap];
(...)
}
If you only want to detect single tap change doubleTap.numberOfTapsRequired to 1.
Then add the following method. This will check if the tapped point is inside section header:
-(void)doubleTapTable:(UISwipeGestureRecognizer*)tap
{
if (UIGestureRecognizerStateEnded == tap.state)
{
CGPoint p = [tap locationInView:tap.view];
NSIndexPath* indexPath = [yourTable indexPathForRowAtPoint:p];
if(indexPath){ // user taped a cell
// whatever you want to do if user taped cell
} else { // otherwise check if section header was clicked
NSUInteger i;
for(i=0;i<[yourTable numberOfSections];i++) {
CGRect headerViewRect = [yourTable rectForHeaderInSection:i];
BOOL isInside = CGRectContainsPoint (headerViewRect,
p);
if(isInside) {
// handle Header View Selection
break;
}
}
}
}
}
A bit late to the party here, but this can be a difficult problem to solve, especially if (as #klyngbaek mentions in the comments), you are adding/removing sections. Changing a tag or custom index property on the header UIView by reloading entire sections can result in ugly animations.
Try this as a callback method for the gesture recognizer that's attached to each header UIView (admittedly hackey):
- (void)headerTapped:(UITapGestureRecognizer *)sender{
NSInteger section = 0;
for(int counter = 0; counter < [self.tableViewOfInterest numberOfSections]; counter++){
if([[self.tableViewOfInterest headerViewForSection:counter] frame].origin.y == sender.view.frame.origin.y){
section = counter;
break;
}
}
}
Basically, when asking a UITableView for each section header, it returns an instance of the header with the frame set to the header's position in the table. Comparing this with the frame of the UITapGestureRecognizer's view property will result in a match at some point (no pun intended)!
You can add button to header and set tag to button something like this:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:
(NSInteger)section {
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.height, tableView.frame.size.width)];
UIButton *button = [[UIButton alloc] initWithFrame:headerView.frame];
button.tag = section;
[button addTarget:self action:#selector(detectSection:) forControlEvents:UIControlEventTouchUpInside];
[headerView addSubView:button];
return headerView;
}
-(void)detectSection:(UIButton *)sender {
switch(sender.tag) {
//your code
}
}
Related
I am trying to create a UITableView with a custom UIButton in each table cell. I implemented like this..
#implementation CouponDetailsCustomTableViewCell
...............
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
[self setBackgroundColor:[UIColor whiteColor]];
CGRect frame = self.contentView.frame;
self.radioButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.radioButton setImage:[UIImage imageNamed:#"radio_blank.png"] forState:UIControlStateNormal];
[self.radioButton setImage:[UIImage imageNamed:#"radio_selected"] forState:UIControlStateSelected];
[self.radioButton setFrame:CGRectMake(16, 10, 29, 29)];
[self.radioButton addTarget:nil action:#selector(radioButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:radioButton];
}
#end
and UITableView Delegate as......
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *COUPON_CELL_ID = #"CouponCell" ;
CouponDetailsCustomTableViewCell * couponCell = (CouponDetailsCustomTableViewCell *) [tableView dequeueReusableCellWithIdentifier:COUPON_CELL_ID];
if (couponCell == nil) {
couponCell = [[[CouponDetailsCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:COUPON_CELL_ID] autorelease];
couponCell.selectionStyle = UITableViewCellSelectionStyleNone;
}
[couponCell.radioButton setSelected:NO];
return couponCell;
}
and the radioButtonPressed method is
-(void)radioButtonPressed:(id) sender
{
[sender setSelected:YES];
}
Now i run the program and a custom UIButton is shown in every table row . If i click on a button it gets selected (shows the radio_selected.png).
The problem arises when i scroll down the table (i am showing only 4 rows of the table in the window) . When i scroll up again..what i see is the clicked button is showing the radio_blank.png.
I am new in iPhone development. i dont know why is this happening. The most i can guess is the button property is changing .. setSelected:NO.
Someone please give me suggestions to fix this problem.
Thank you.
When you scroll your UITableView, hidden cells are not rendered anymore and might be reused for cells that are becoming visible. If a new cell becomes visible, tableView:cellForRowAtIndexPath: gets called.
The problem is that you're setting the selected state there:
[couponCell.radioButton setSelected:NO];
Therefore, whenever you scroll your cell out of the visible area and back again, it gets reset to selected = NO.
I suggest you create a NSMutableDictionary where you store the selection state of each row/NSIndexPath, which you then re-apply in tableView:cellForRowAtIndexPath:.
replace [couponCell.radioButton setSelected:NO]; in tableView:cellForRowAtIndexPath: with code that sets the selected property depending on a state from your dataSource.
something along those lines:
/* get the button state from your data source */
FancyCouponObject *coupon = [self.coupons objectAtIndex:indexPath.row];
BOOL buttonState = coupon.buttonState;
[couponCell.radioButton setSelected:buttonState];
The cells of a tableView are reused when they are moved off screen. You can't save state in them.
problem is when you scroll the table at that time your cellForRowAtIndexPath: delegate method called for every row... so here when its called at time your setSelected Method call with NO argument like bellow...
[couponCell.radioButton setSelected:NO];
so when you scroll table at time your setSelected method call and your button turn with radio_blank.png
...
:)
-(void)radioButtonPressed:(id) sender
{
[sender setSelected:YES];
}
in this method you are setting button as selected and for selected button you have set the radio_blank.png image
I'm developing an iPhone app and I have one problem. I have a UITableView with a few editable rows(
-(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{ )
and one not editable row. If I click edit, the editable rows slide a bit to the right and there is a red round button at the left of it, but the not editable row doesn't slide at all. Is there a way to also slide it to the right but without the red button at the left? It doesn't look that nice at the moment. I hope someone can help me with this:)
I am not sure if it's a good idea to change the default behavior of the tableView.
But if you really want to, you could e.g. use indentation.
// Might be target of button
- (void) setEditingMode
{
tableView.editing = YES;
[tableView reloadData];
}
// Might be target of button
- (void) resetEditingMode
{
tableView.editing = NO;
[tableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = ...;
....
Boolean cellIsEditable = ...;
if( tableView.editing && !cellIsEditable )
{
cell.indentationWidth = ...; // (please experiment to find the exact value)
cell.indentationLevel = 1;
}
else
{
cell.indentationLevel = 0;
}
}
Subclass the UITableViewCell and slide the uneditable row yourself.
#interface MyHistoryTableViewCell : UITableViewCell
#end
#implementation MyHistoryTableViewCell : UITableViewCell
#define CELL_SLIDE_WIDTH 32 // found empirically
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
if ( self.editingStyle == UITableViewCellEditingStyleNone ) { // not editable
CGRect frame = self.frame;
UITableView *tableView = ((UITableView *)(self.superview));
if ( tableView.editing ) { // going to editing mode
frame.origin.x = CELL_SLIDE_WIDTH;
frame.size.width = tableView.frame.size.width - CELL_SLIDE_WIDTH;
} else { // ending editing
frame.origin.x = 0;
frame.size.width = tableView.frame.size.width;
}
[UIView animateWithDuration:0.3 animations:^{ // match the tableView slide duration
self.frame = frame;
}];
}
}
#end
If you have subviews that need to be anchored to the right of the cell (e.g., a button that should not slide), then do
mySubview.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; // anchors to the right margin
Also, you have to be creative when setting the subview's frame.origin.x. I experimented with many values until I found something that worked (the value made no sense to me).
when i press the new button i need to add a textfield on last row of tableview.
can any one explain the flow.
Thanks in advance.
Just i tried this scenario.
Create a one Boolean variable like this
BOOL newButtonPress = NO;
then fallow the below code
-(IBAction)newButtonClicked:(id)sender{
if (self.newButtonPress == NO) {
//if new button press then newbuttpress is yes and reload the tableview
self.newButtonPress = YES;
[self .reloadTableview reloadData];
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if(self.newButtonPress == YES)//creating noOfRows when newbutton pressed
{
return [self.itemNames count]+1;
}
else {
return [self.itemNames count];
}
}
Then add the below code into the cellForRowAtIndexPath method
//when new button is pressed adding new cell
if (self.newButtonPress == YES) {
if([self.itemNames count] == [indexPath row]){
cell.selectionStyle = UITableViewCellSelectionStyleNone;
//creating the textfield if new button is pressed
UITextField *extraField = [[UITextField alloc] initWithFrame:CGRectMake(80, 6, 100, 30)];
extraField.borderStyle = UITextBorderStyleNone;
extraField.textAlignment = UITextAlignmentCenter;
[extraField becomeFirstResponder];
[extraField setDelegate:self];
[cell addSubview:extraField];
[extraField release];
}
}
If you just want the text field to appear at the end of the table, why not consider using UITableview's tableFooterView property?
In that case you don't have to ensure that you return the UITextField in your delegate methods.
You should nearly be able to drag drop a new UITextField in your Tableview if you are using InterfaceBuilder or XCode 4.
I am very new to iphone coding,i want to share my thoughts. If that is wrong please ignore.
In cellForRowAtIndexPath method u need to set a textfield in
if(indexPath.row==numberOfRows-1) condition.
In button pressed method u can set textField.hidden=NO and in viewdidload textField.hidden=YES.
I have a tableviewcontroller with a uinavigation bar that has a barbuttonitem, called editBarButton. When editBarButton is pressed I want my tableview to be updated with a new button in each of the cells that says 'Edit'. What is the correct way to implement this?
- (void)onEditBarButtonPressed{
//TODO: update cells
}
You have to overwrite the accessoryView attribute in your UITableViewCell with your Edit button:
Create a custom button to overwrite the current accesoryView:
- (UIButton *) makeDetailDisclosureButton
{
UIButton * button = [UIButton yourEditButton];
[button addTarget: self
action: #selector(accessoryButtonTapped:withEvent:)
forControlEvents: UIControlEventTouchUpInside];
return ( button );
}
Then the button will call this routine when it's done, which then feeds the standard UITableViewDelegate routine for accessory buttons:
- (void) accessoryButtonTapped: (UIControl *) button withEvent: (UIEvent *) event
{
NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint: [[[event touchesForView: button] anyObject] locationInView: self.tableView]];
if ( indexPath == nil )
return;
[self.tableView.delegate tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];
}
You can see a similar question here: Using a custom image for a UITableViewCell's accessoryView and having it respond to UITableViewDelegate
I found a solution to my problem.
You should add a object level BOOL flag called editPressed.
It should be set to NO in viewDidLoad.
When making each cell, add a button to each and set it to hidden if need be:
[button setHidden:!editPressed];
It is important to use the flag so that when new cells are made they will keep the buttons hidden if they should be or visible otherwise.
Then have a object level NSMutableArray * of the buttons in the view controller and add each button to it:
[buttons addObject:button];
When you want to show each button, just change the hidden state:
editPressed = YES;
for(int i = 0; i != [butttons count]; i++){
[[buttons objectAtIndex:i] setHidden:!editPressed];
}
When you want to hide each button, once again, change the hidden state:
editPressed = NO;
for(int i = 0; i != [butttons count]; i++){
[[buttons objectAtIndex:i] setHidden:!editPressed];
}
I have a custom UITableView cell that I've added a textbox to for editing, that shows and hides based on the edit mode. I've also tried adding a vertical line that shows when editing, and it does that, but I'm running into some drawing issues. I just added a green checkmark rightView to start working on input validation feedback, and I'm seeing similar issues.
Here is the code for the cell, and part of my cellForRowAtIndexPath.
#import <UIKit/UIKit.h>
#interface EditableCellStyle2 : UITableViewCell {
CGRect editRect;
UITextField *editField;
UIView *lineView;
}
#property (nonatomic, readonly, retain) UITextField *editField;
#property (nonatomic, readonly, retain) UIView *lineView;
#end
#import "EditableCellStyle2.h"
#implementation EditableCellStyle2
#synthesize editField;
#synthesize lineView;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code.
editRect = CGRectMake(83, 12, self.contentView.bounds.size.width-83, 19);
editField = [[UITextField alloc] initWithFrame:editRect];
editField.font = [UIFont boldSystemFontOfSize:15];
editField.textAlignment = UITextAlignmentLeft;
editField.textColor = [UIColor blackColor];
editField.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[self.contentView addSubview:editField];
self.editField.enabled = NO;
self.editField.hidden = YES;
lineView = [[UIView alloc] initWithFrame:CGRectMake(80, 0, 1, self.contentView.bounds.size.height)];
self.lineView.backgroundColor = [UIColor lightGrayColor];
[self.contentView addSubview:lineView];
self.lineView.hidden = YES;
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state.
}
-(void)layoutSubviews
{
[super layoutSubviews]; // layouts the cell as UITableViewCellStyleValue2 would normally look like
editRect = CGRectMake(83, 12, self.contentView.frame.size.width-self.detailTextLabel.frame.origin.x-10, 19);
editField.frame = editRect;
}
- (void)willTransitionToState:(UITableViewCellStateMask)state {
[super willTransitionToState:state];
if (state & UITableViewCellStateEditingMask) {
self.detailTextLabel.hidden = YES;
self.editField.enabled = YES;
self.lineView.hidden = NO;
self.editField.hidden = NO;
}
}
- (void)didTransitionToState:(UITableViewCellStateMask)state {
[super didTransitionToState:state];
if (!(state & UITableViewCellStateEditingMask)) {
self.editField.enabled = NO;
self.editField.hidden = YES;
self.lineView.hidden = YES;
self.detailTextLabel.hidden = NO;
self.editField.text = self.detailTextLabel.text;
}
}
- (void)dealloc {
[editField release];
[lineView release];
[super dealloc];
}
#end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// handling every section by hand since this view is essentially static. Sections 0, 1, 2, and 4 use a generic editable cell.
// Section 3 uses the multiline address cell.
static NSString *CellIdentifier = #"Cell";
EditableCellStyle2 *cell = (EditableCellStyle2 *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (indexPath.section == 0 || indexPath.section == 1 || indexPath.section == 2 || indexPath.section == 4) {
if (cell == nil) {
cell = [[[EditableCellStyle2 alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier] autorelease];
}
}
// Configure the Odometer
if (indexPath.section == 0) {
NSArray *array = [sectionsArray objectAtIndex:indexPath.section];
NSDictionary *dictionary = [array objectAtIndex:indexPath.row];
cell.textLabel.text = #"Odometer";
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#", [dictionary objectForKey:#"Odometer"]];
cell.tag = kOdometer;
cell.editField.text = cell.detailTextLabel.text;
cell.editField.placeholder = #"Odometer";
cell.editField.tag = kOdometer;
cell.editField.keyboardType = UIKeyboardTypeNumberPad;
// Create a view for the green checkmark for odometer input validation and set it as the right view.
UIImage *checkImage = [UIImage imageNamed:#"tick.png"];
UIImageView *checkImageView = [[[UIImageView alloc] initWithImage:checkImage] autorelease];
cell.editField.rightView = checkImageView;
cell.editField.rightViewMode = UITextFieldViewModeAlways;
}
return cell;
}
There is more to it but all the cells are built the same way.
The problems are that, when in edit mode, the vertical lines will display properly. When I leave edit mode, any cells that were off screen when I go to normal mode still have the vertical line (it doesn't get hidden). Also, now that I've added the imageView for the checkmark indicator, any cells that are off screen when switching modes gain the checkmark. (only section 0 sets it up).
I've also noticed that if i do cell.setNeedsDisplay, the text label and detail text label won't update if the data source has been updated. I have to do [self.tableView reloadData] which skips any active animations.
I'm sure these issues are related to me using a custom cell + dequeueReusableCellWithIdentifier, but I can't find exactly what.
Any feedback or a push in the right direction would be appreciated.
Edit:
Not using reusable cells seems to have resolved the above issues. I'm still open to feedback on the cell code.
I forgot one other issue that may or may not be related. One of my cells has a "tap to view list" button. If I enter data into the cells while in edit mode, then hit that button to choose some info from a list (it displays a modal table view), when I dismiss the modal view, all of the cells' edited data has reverted to their original state. I'm not calling reload data when I dismiss the modal view controller. I thought this might be fixed by not using reusable cells but it isn't.
You need to prepare the cell for reuse. Try adding this to the EditableCellStyle2 implementation:
- (void)prepareForReuse {
[super prepareForReuse];
[self didTransitionToState:UITableViewCellStateDefaultMask];
}
Maybe you trimmed too much for your post, but in the posted code your reusable cell handling is all wrong.
First of all, each different type of cell needs its own CellIdentifier. In your case (judging from your code comment), that means at least a different identifier for section 3 versus sections 0, 1, 2, and 4. You may also want to do a separate identifier for section 0, so you don't have to keep removing and readding that checkmark. The different identifier needs to be used for both the dequeueReusableCellWithIdentifier: and initWithStyle:reuseIdentifier:` for the appropriate sections.
The second problem is that you are not resetting the cells correctly. There are two "kinds" of initialization that must be done to a UITableViewCell: initialization that is the same for every cell of its type, and initialization that depends on the specific row being displayed. The first kind can (and should) only be done once, when a new cell is allocated. The second kind must be done every time through tableView:cellForRowAtIndexPath:. You seem to be doing the first correctly for your EditableTableCell2 class in its init method, but I see nowhere in there where you do the per-row initialization: you never reset selected, or the cell state, or the contents of the edit field, or remove the checkImageView since you are using the same kind of cell for section 0 versus the other sections. If you want, the reset selected, state, and clearing out the checkbox image and field contents can be done in prepareForReuse on your EditableTableCell2 class.
The third problem, which is almost certainly due to over-trimming, is that you never create this "multiline address" cell for section 3. You'll end up maybe reusing a random EditableTableCell2, or maybe crashing on an exception from the framework when you return nil from tableView:cellForRowAtIndexPath:.