I have a view controller
in that having a table view in that a Custom cell.
I have a button in a custom cell
MyViewController
---View
------TableView
-------------Custom cell
-------------------UIButton
I want to implement the button action for that button in the custom cell, in the Custom cell class.
I want to present another viewcontoler called mailPage by clicking on button for thet
-(IBAction)webButtonClicked:(id)sender
{
[self presentModalViewController:mailpage animated:YES];
}
But here self means CustomCell, even i tried with superview i didn't get my view contoller to represent in place of self
I tried like this but no use.
MyViewController *myViewController =self.superview
How to get my View controler containing the current custom cell
I would strongly recommend you place your view controller presentation logic in a view controller, rather than a UITableViewCell.
As you're already using a custom cell, this will be fairly straightforward. Simply define a new protocol for your custom cell, and have your view controller act as a delegate. Or alternatively, as per this answer here you can forgo the delegate entirely, and simply have your view controller act as the target for your button.
Your custom UITableViewCell really shouldn't have any dependencies or knowledge of the view controller it's being displayed in.
well the easy way is to set an unique tag for the button in the cell and in cellForRowAtIndexpath method you can get the button instance as
UIButton *sampleButton=(UIButton *)[cell viewWithTag:3];
and set the action as
[sampleButton addTarget:self action:#selector(sampleButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
and set up the action in the viewcontroller
-(void)sampleButtonPressed:(id)sender
{
}
try this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"OpenHouseListCustomCell";
OpenHouseListCustomCell *cell = (OpenHouseListCustomCell *)[tblOpenHouses dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
NSArray* nib = [[NSBundle mainBundle] loadNibNamed:#"OpenHouseListCustomCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
cell.showsReorderControl = NO;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor=[UIColor clearColor];
[cell.btn1 addTarget:self action:#selector(ButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
}
cell.btn1.tag = indexpath.row;
return cell;
}
-(void) ButtonClicked {
//your code here...
}
Related
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]);
}
I have created a custom UITableViewCell using the nib file. Then when I select the my custom UITableViewCell, I want to perform a segue and pass the data to the detail view controller. I have already set the segue identifier in the storyboard, and I put the prepareForSegueWithIdentifier in the - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath However, when I perform the segue, only the first row of the table view can be shown on the detailed page.
I have three questions:
1. How to set the segue identifier for the custom UITableViewCell?
Since I create the custom UITableViewCell in the xib file, it is difficult to connect the xib file with the main storyboards with other view controllers.
2. If I change the class name of the table view cell in the storyboard, will it be my custom UITableViewCell?
Specifically, in order to replace the default UItableViewCell in the storyboard to my custom UITableViewCell, I change the name of the class of the default UITableViewCell in the storyboard, to the name of my custom UITableViewCell. Then I Control + Drag to create a push segue and set the segue identifier. But it seems this one doesn't work either.
3. Why every time I perform segue, the destination view controller only show the specific content of the first row of my table view?
That is, whenever I select the first cell, the second cell, the third cell......, all I get after segue to the destination view controller is the content of the first cell. I am wondering whether the sender is my custom UITableViewCell? Or is the indexPath is nil? I am not sure.
My code is as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
customTableViewCell *cell;
cell = (customTableViewCell *) [tableView dequeueReusableCellWithIdentifier:#"myCell"];
if (!cell) {
NSArray *nib = [[NSBundle mainBundle]loadNibNamed:#"customTableViewCell" owner:nil options:nil];
cell = [nib objectAtIndex:0];
}
NSDictionary *photo = self.recentPhotos [indexPath.row];
dispatch_queue_t queue = dispatch_queue_create("fetch photos", 0);
dispatch_async(queue, ^{
NSURL *imageURL = [FlickrFetcher URLforPhoto:photo format:FlickrPhotoFormatSquare];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
dispatch_async(dispatch_get_main_queue(), ^{
NSString *title = [photo valueForKeyPath:FLICKR_PHOTO_TITLE];
cell.cellLabel.text = title;
UIImageView *cellImageView = [[UIImageView alloc]initWithImage:image];
[cellImageView setFrame: CGRectMake(10, 5, 45, 45)];
[cellImageView.layer setCornerRadius:cellImageView.frame.size.width/2];
cellImageView.clipsToBounds = YES;
[cell addSubview:cellImageView];
});
});
return cell;
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"go" sender:indexPath];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NSDictionary *photo = self.recentPhotos [indexPath.row];
NSURL *imageURL = [FlickrFetcher URLforPhoto:photo format:FlickrPhotoFormatLarge];
NSString *title = [photo valueForKeyPath:FLICKR_PHOTO_TITLE];
if ([segue.identifier isEqualToString:#"go"]) {
if ([segue.destinationViewController isKindOfClass:[ViewImageViewController class]]) {
ViewImageViewController *vivc = (ViewImageViewController *) segue.destinationViewController;
vivc.imageURL = imageURL;
vivc.title = title;
}
}
}
Did you try debugging the method and looking at the value of sender? You provided the indexPath in the call to performSegueWithIdentifier:sender: but the you use the sender as a cell in indexPathForCell:.
I already see problems in your tableView:cellForRowAtIndexPath: method as well. While scrolling the cells get reused, so when you perform a async call the block can and will be executed on the wrong cell. To prevent that call the dequeueReusableCellWithIdentifier: again inside the dispatch_async callback.
Also you are adding a subview directly to the cell instead of using the contentView property.
Regarding your questions, as far as I know you cannot connect a segue from a nib file. To make it work properly you need to connect the segue from the prototype cell in the storyboard. You created a manual segue and you're calling it when the user touches the cell. I think that it'll be easier for you to understand what's happening if you don't use segues at all and just call showViewController:sender: yourself.
I have a ViewController wich have a UItableView.
Inside every cell in this TableView I have a subview.
In the first row of my TableView I have a subview that contains 2 buttons.
I want that when one of those buttons are pressed, my UINavigationController pushes my secondViewController on the screen.
But nothing happens, I don't get any errors, just don't happen!
I already try everything I now, but nothing seems to work.
Here is my code at my AppDelegate.m
firstViewController * firstView = [firstViewController new];
UINavigationController * navViewController = [[UINavigationController alloc]initWithRootViewController:firstView];
self.window.rootViewController = navViewController;
In my firstViewController I have my TableView:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * CellIdentifier = #"Cell";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if (indexPath.section == 0 && indexPath.row == 0) {
[cell.contentView addSubview:_actionBar];
}
else {
for (UIView * view in cell.contentView.subviews) {
[view removeFromSuperview];
}
_feed = [[Y_feedViewController sharedFeed]getViewWithThisInfo:[_arrayOfFeeds objectAtIndex:indexPath.section]];
[cell.contentView addSubview:_feed];
}
return cell;
}
So, the Buttons are in my _actionBar (that I add as a subview in my first row), and when I press the Button01 I want to call my secondViewController
Man, I already try everything, but can't work with this.
I already try to call the pushViewController: from my _actionBar, my TableViewController, my firstViewController, etc.
Thanks.
It's not clear how you are assigning an action to the buttons in _ActionBar. I think it would be easier and cleaner to use two different cell types, one of which has the buttons in it (rather than adding a subview in code). Just dequeue the one you want based on the indexPath. When you dequeue the one for the first row, add the action and target for your buttons (with the target being the view controller).
In a VC I have a tableView with custom Cell "ChartListCell". When the user is on any particular cell I want to open a view with 2 buttons in it on top of the cell and let user click on any button and appropriate action be taken for that cell. What would be the best way to implement this feature ? I am stuck at :-
Event to find cell/row is selected.
Place the view at the cells' position - so it stays on top of the cell.
Create the view with 2 btns - would be better to create it programmatically so events can be mentioned in the same VC only or to create a xib would be better option ?
Hide the view again when selection is lost.
UPDATE :-
So I tap the cell, the view (like below with 2 buttons) appears. If I scroll the table, the view stays on the cell. Once I tap an option on the new view , or tap on another cell or somewhere outside it disappears.
UPDATE :-
I created a xib file and in didSelectRowAtIndexPath am trying to show the view, but it's not appearing.
// Get the SELECTED CELL
ChartListCell *cell = (ChartListCell*)[tableView cellForRowAtIndexPath:indexPath];
NSLog(#"ABOUT TO SHOW VOV");
NSArray *visitorOptView = [[NSBundle mainBundle] loadNibNamed:#"VisitorsOptionsView" owner:self options:Nil];
VisitorsOptionsView *vov = [visitorOptView objectAtIndex:0];
vov.frame = cell.frame;
[self.view addSubview:vov];
[self.view bringSubviewToFront:vov];
Nothing appears. Where am I going wrong ? To give the same location as of the cell, I tried vov.frame = cell.frame , but I think I may be wrong at that step. What else should be at that place ???
What would be the best methods to implement the above ? Can you please help me guide out. Any help is highly appreciated.
Below image shows what I am looking to meet up the design :-
I made this demo project for you:http://goo.gl/Y6GFmj , you can download it
You can add the view into your table cell instead of your view. You need to subclass your table view cell and add a property to hold the overlay view.
#interface MyTableViewCell : UITableViewCell
#property (weak, nonatomic) UIView * overlayView;
#end
Note that the overlay view should be a weak property, because the cell's view will have the strong reference to it, just like an IBOutlet.
You need to add another property in your table view controller to hold the last selected index path, so that when you select a new row, you can remove the overlay view in the old one.
#property (strong, nonatomic) NSIndexPath * lastSelectedRow;
Use the following function to remove the overlay view from a cell
- (void)removeViewInCellOfIndexPath:(NSIndexPath *)indexPath
{
if (!indexPath)
return;
MyTableViewCell * cell = (MyTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
if (cell.overlayView)
{
[cell.overlayView removeFromSuperview];
}
}
Now you can deal with the selection:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// deselecte the row
[tableView deselectRowAtIndexPath:indexPath animated:NO];
// remove the overlay view from the last selected row
[self removeViewInCellOfIndexPath:self.lastSelectedRow];
// add overlay view to this row
MyTableViewCell * cell = (MyTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
// note the origin of the frame is (0, 0) since you are adding itto the cell instead of the table view
UIView * view = [[UIView alloc]initWithFrame:CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height)];
view.backgroundColor = [UIColor greenColor];
[cell.contentView addSubview:view];
// remember this view and this indexpath
cell.overlayView = view;
self.lastSelectedRow = indexPath;
}
I had done something similar to this, What I did was while creating my custom cell I had placed another view at the bottom which contained the two buttons, make the two buttons as IBOutlets of their classes.
When I load it in my table view with the usual method in CellForROwAtIndexpath I defined as following...
- (UITableViewCell *)couchTableSource:(CBLUITableSource*)source
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellid=#"Cell";
CustomTableCell* cell=(CustomTableCell*)[_Mytableview dequeueReusableCellWithIdentifier:cellid];
if(cell==nil)
{
NSArray *nib=[[NSBundle mainBundle] loadNibNamed:#"CustomTableCell" owner:self options:nil];
cell=[nib objectAtIndex:0];
}
[cell.firstButton addTarget:self action:#selector(firstButtonClicked:event:) forControlEvents:UIControlEventTouchUpInside];
[cell.secondButton addTarget:self action:#selector(SecondButtonClicked:event:) forControlEvents:UIControlEventTouchUpInside];
}
I maintained a global integer flag as SelectedIndex and used it as follows
In didSelectRowAtIndexPath I have done something Like this..
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(selectedIndex == indexPath.row)
{
selectedIndex = -1;
[_Mytableview reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
return;
}
//First we check if a cell is already expanded.
//If it is we want to minimize make sure it is reloaded to minimize it back
if(selectedIndex >= 0)
{
NSIndexPath *previousPath = [NSIndexPath indexPathForRow:selectedIndex inSection:0];
selectedIndex = indexPath.row;
[_Mytableview reloadRowsAtIndexPaths:[NSArray arrayWithObject:previousPath] withRowAnimation:UITableViewRowAnimationFade];
}
//Finally set the selected index to the new selection and reload it to expand
selectedIndex = indexPath.row;
[_Mytableview reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
also and the most important step in heightForRowAtIndexPath
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row == selectedIndex)
{
return (return the actual size of the cell, Including the view with two buttons);
}
else
{
return (return the size of cell -(the size of view containing the two buttons));
}
}
In viewWillAppear make Sure to assign this so that the cell would be normal whenever the is presented
-(void)viewWillAppear:(BOOL)animated
{
selectedIndex=-1;
[myTableView reloadData];
}
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.