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];
}
Related
I have created a View Controller with a Navigation bar and UiCollectionView. UI Collection View contains custom UICollectionViewCell. Navigation bar contains two UIBarButton items, one is on the left corner - prepared segue to previous page and other item is on the right corner - arranged to delete cell(s) in the UI CollectionView as show in the picture below:
Main Screen
Now I want to remove the selected UICollectionViewCell when UIBarButtonItem in the right corner, is tapped.
This how my cellForItemAtIndexPath method look like:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath{
self.GlobalIndexPath = indexPath;
MessagesCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"messagesCell" forIndexPath:indexPath];
cell.MessageHeading.text = [self.Message_Heading objectAtIndex:indexPath.row];
cell.MessageSubject.text = [self.Message_Subject objectAtIndex:indexPath.row];
cell.MessageContent.text = [self.Message_Details objectAtIndex:indexPath.row];
[cell.Checkbox setHidden:YES];
[cell.Checkbox setChecked:NO];
}
I have tried a solution like Declaring Indexpath as global variable and use it in the button event as below:
#property (strong,nonatomic) NSIndexPath *GlobalIndexPath;
some other code .......
//When Bin Icon(UIBarButtonItem) Clicked
- (IBAction)DeleteMessages:(id)sender {
[self.view makeToast:#"You clicked delete button !"];
NSIndexPath *indexPath = [self.MessageCollectionView.indexPathsForVisibleItems objectAtIndex:0] ;
BOOL created = YES;
// how to get desired selected cell here to delete
MessagesCollectionViewCell *cell = [self.MessageCollectionView cellForItemAtIndexPath:self.GlobalIndexPath];
if([cell.Checkbox isHidden])
{
[cell setHidden:YES];
}
else{
[cell.Checkbox setChecked:NO];
[cell.Checkbox setHidden:YES];
}
}
It's not worked.
For showing the UICollectionViewCell selected as checked, i'm using #Chris Chris Vasselli's solution
Please help me with this. Thanks in Advance.
There are a few steps. First, determine the selected indexPath, but don't assume there is a selection when the method is run....
// in your button method
NSArray *selection = [self.MessageCollectionView indexPathsForSelectedItems];
if (selection.count) {
NSIndexPath *indexPath = selection[0];
[self removeItemAtIndexPath:indexPath];
}
There are two more steps to remove items from a collection view: remove them from your datasource, and tell the view it has changed.
- (void)removeItemAtIndexPath:(NSIndexPath *)indexPath {
// if your arrays are mutable...
[self.Message_Heading removeObjectAtIndex:indexPath.row];
// OR, if the arrays are immutable
NSMutableArray *tempMsgHeading = [self.Message_Heading mutableCopy];
[tempMsgHeading removeObjectAtIndex:indexPath.row];
self.Message_Heading = tempMsgHeading;
// ...
Do one or the other above for each datasource array. The last step is to inform the collection view that the datasource has changed, and it must update itself. There are a few ways to do this. The simplest is:
// ...
[self.MessageCollectionView reloadData];
OR, a little more elegantly:
[self.MessageCollectionView deleteItemsAtIndexPaths:#[indexPath]];
} // end of removeItemAtIndexPath
Let's start right off with some code :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"forIndexPath:indexPath];
Produit *object = self.objects[indexPath.row];
[cell.contentView addSubview:object];
return cell;
}
In a cell, I add a subView of type Produit, which is a subclass of UIView. This is how it looks like:
Editing all the stuff works fine except for when there are more cells than the size of the screen can allow. When that is the case, if I try and modify some info in one of the cells, it's as if the new info is added on top of the old one like this:
In this image, only the Button acts spooky but sometimes the text fields also appear on top of each other. What's more is that if I modify the cell on top, then if I scroll to the bottom of the table view, the last cell also gets modified. Last thing: when I add more cells after having produced this glitch, some of the new cells get the same 'Category' as the glitched one, it's like it's making a copy of it and puts in 'Category' the glitched title...
Can someone explain what's happening? How can I fix it? Here is some more code( not all of it, just the table view configuration)
-(void) addNewProduit:(UIBarButtonItem*) item {
if (!self.objects) {
self.objects = [[NSMutableArray alloc] init];
}
Produit* product = [[Produit alloc] init];
CGRect frame = CGRectMake(0, 0, self.frame.size.width, 44);
[product setFrame:frame];
[product initView];
[self.objects insertObject:product atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return NO if you do not want the specified item to be editable.
return YES;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[self.objects removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
dequeueReusableCellWithIdentifier: forIndexPath: gives you a cell, which might be a new cell, or it might be an old cell that's previously been shown, but has scrolled off the screen.
One quick fix is:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"forIndexPath:indexPath];
Produit *object = self.objects[indexPath.row];
[cell.contentView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
[cell.contentView addSubview:object];
return cell;
This will resolve your issue in the least efficient way possible. It is likely to cause jittery animation when you scroll really fast, especially on older devices. I just intend it as an illustration of the problem you need to solve.
A more appropriate solution would reuse the view if it's already there, instead of creating a new one each time.
self.objects appears to contain views, which defeats the purpose of UITableView's really fast scrolling setup. You should just include data objects there, and then configure the views for an individual cell when it's time to show that one cell. IE, you don't want a view for each data object, you want 6 views that adapt to which data object currently needs to be displayed.
You are always adding more views when you re-use a cell by [cell.contentView addSubview:object];. One solution might be to tag the view when you add it and then remove any subview with the appropriate tag before adding another one.
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).
I created a custom table view which has custom cells,
Each cell contains an image and a button (amongst other text)
I want to be able to change the image in the UIImageView when the button is clicked,
I am able to get the click event. I however am having a difficult time changing the image.
I have attempted to get the entire cell in order to change the image using that :
UIButton *senderButton = (UIButton *)sender;
NSIndexPath *path = [NSIndexPath indexPathForRow:1 inSection:senderButton.tag];
LocationCardCell* cell = (LocationCardCell *) senderButton.superview.superview.superview;
if ([cell.class isSubclassOfClass:[LocationCardCell class]]) {
NSLog(#"cell is RIGHT CLASS!!!");
}else{
NSLog(#"cell is not THE RIGHT CLASS!!!");
}
UIView *cellContentView = (UIView *)senderButton.superview;
LocationCardCell *cell1 = (LocationCardCell *)cellContentView.superview;
if ([cell1.class isSubclassOfClass:[LocationCardCell class]]) {
NSLog(#"cell1 is RIGHT CLASS!!!");
}else{
NSLog(#"cell1 is not THE RIGHT CLASS!!!");
}
Can somebody please let me know how to go about getting the cell from the button click?
Keep this method in your arsenal...
- (NSIndexPath *)indexPathWithSubview:(UIView *)subview {
while (![subview isKindOfClass:[UITableViewCell self]] && subview) {
subview = subview.superview;
}
return [self.tableView indexPathForCell:(UITableViewCell *)subview];
}
Then,
- (IBAction)pressed:(id)sender {
NSIndexPath *path = [self indexPathWithSubview:(UIButton *)sender];
LocationCardCell* cell = (LocationCardCell *)[self.tableView cellForRowAtIndexPath:path];
// carry on happily
But not too happily. You question doesn't state what you plan to do with that cell once you have it. The generally right approach to modifying tableview cells is to modify your model, reload the cell, and then account for that change in the datasource (tableView:cellForRowAtIndexPath:).
A more conventional pattern would be to use the indexPath we just found to dereference your model, modify it, then reload.