I want to use KVO to be notified when the number of selected cells in a UICollectionView changes. When I tried to subclass UICollectionView and add a new property nSelectedCells, I ran into a problem when trying to add the logic that updates nSelectedCells. There are too many places where the selected cells count can change:
Programmatically - View: deselectItemAtIndexPath, selectItemAtIndexPath, reloadData, ...
UI - Controller: didDeselectItemAtIndexPath, didSelectItemAtIndexPath
More?
What would be the best way to keep track of this value. Preferably from within the UICollectionView subclass.
UICollectionViewCell has a selected property. You could override the setter for this method, as it's the only thing guaranteed to be called when a cell's selection status is changed.
Perhaps subclass a UICollectionView with a property to keep a counter of selected cells and register for notifications fired by your UICollectionViewCell subclasses in setSelected: based on whether the cells was selected or deselected.
As a note, just because setSelected: was called doesn't mean the selection status has changed.
- (void)setSelected:(BOOL)selected {
if (super.selected != selected) {
if (selected) {
// cell was unselected and became selected, increase counter
} else {
// cell was selected and become unselected, decrease counter
}
}
super.selected = selected;
}
use a NSMutableSet to track selected cell's index path, when select a cell, add its indexPath to set; deselect a cell, remove its index path from set.
The collection view calls these methods only when the user successfully selects/deselect an item in the collection view. It does not call the method when you programmatically set the selection/deselection.
#property (nonatomic) NSMutableSet *selectedCellIndexPathsSet;
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
//do some things.
[self.selectedCellIndexPathsSet addObject:indexPath];
}
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
//do some thing.
[self.selectedCellIndexPathsSet removeObject:indexPath];
}
Related
When i tap one of UICollectionView's cell more than once-double tap, triple tap-, it's delegate method didSelectItemAtIndexPath also get called more than once. What can be the slickest way to prevent it?
I would appreciate any comments.
You can use your model object to hold selected property in it (or you can create a boolean array for only this purpose) . And check it in shouldSelectItemAtIndexPath method.
#cihangirs code:
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (someModel.isSelected) {
return NO;
} else {
someModel.isSelected = YES;
return YES;
}
}
This is safest way to do your objective:-
(void)collectionView:(UICollectionView *)collectionView
didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if([[collectionView indexPathsForSelectedItems] containsObject:indexPath]) // checking whether cell is already selected or not
{
return;
}
else
{
// do whatever you want to do on selection of cell
}
}
The thing happening here is, whenever you select a cell it automatically get stored "indexPathsForSelectedItems" of Collection view, so the next time you tap on the selected cell again this method [[collectionView indexPathsForSelectedItems] containsObject:indexPath] will check whether that cell is already selected or not , if yes then it will return the method so that it doesnot go any step further.
I would like to call a method when a UITableViewCell is selected/tapped. I could do it easily with a static table view, but it requires a UITableViewController which is not good for me in this case, therefore I'm using a normal vc.
I have 10 specified methods like this:
- (void) methodOne {
NSLog(#"Do something");
}
- (void) methodTwo {
NSLog(#"Do something");
}
....
And I would like to call the methodOne when the first cell was tapped, call the methodTwo when the second cell was tapped and so on..
As a first step I set the numberOfRowsInSection to return 10 cells, but have no idea how could I connect the selected cells with the methods. Is there any quick way to do it? It would be a dirty solution to create 10 custom cells and set the every method manually for the custom cells, and there is no free place for it.
You can create an array of NSStrings with method names in the order they should be called from their corresponding UITableViewCells.
NSArray *selStringsArr = #[#"firstMethod", #"secondMethod", #"thirdMethod];
Then create a selector in tableView:didSelectRowAtIndexPath: from the strings array and call it using performSelector:.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *selString = selStringsArr[indexPath.row];
SEL selector = NSSelectorFromString(selString);
if ([self respondsToSelector:#selector(selector)]) {
[self performSelector:#selector(selector)];
}
}
Of course, there are some limitations to using performSelector: which you can read here.
You can use this method for whenever any cell is tapped on the table view
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger selectedRow = indexPath.row; //this is the number row that was selected
switch (selectedRow)
{
case 0:
[self methodOne];
break;
default:
break;
}
}
Use selectedRow to identify which row number was selected. If the first row was selected, selectedRow will be 0.
Don't forget to set the table view's delegate to your view controller. The view controller also has to conform to the UITableViewDelegate protocol.
#interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
As long as the table view has a data source and a delegate, it doesn't matter what kind of view controller it is on. All a UITableViewController really is is a UIViewController that already has a table view on it and is that table view's delegate and data source.
I have the following UITableViewCell (well, subclassed).
With didSelectRowAtIndexPath it is possible to capture that a cell has been selected in UITableViewController. My problem occurs due to the fact that directly pressing Choose User bypasses the selection of the cell.
How could I allow my UITableViewController to be aware that UITableViewCell foo has been pressed even if the user immediately hits Choose User?
N.B. I don't need the Selection capability per se, this was just by method of knowing that a user had tapped within a cell area.
You could just call the method directly. If we say that for each Choose User button we are setting the row number as the tag and assuming that you don't have sections so everything will happen in section 0 we could do.
- (void)hitChooseUser:(id)sender
{
// Do whatever you want for when a user hits the `Choose User` button
// Code......
// Then do this at the end or whenever you want to do it.
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[sender tag] inSection:0];
// Also assuming you have created you have created you UITableView correctly.
[self tableView:myTableView didSelectRowAtIndexPath:indexPath];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do whatever it is you want.
}
I also found this link that may help you Manually call didSelectRowatIndexPath
You could also disable the user interaction with the cell itself by setting userInteractionEnabled: to NO for each cell in cellForRowAtIndexPath: so didSelectRowAtIndexPath: will only get called when you want to call it manually.
Do not call didSelectRowAtIndexPath: It is a UITableViewDelegate method and, where possible, should be used as such (meaning let the UITableView send messages to it). In addition, it creates an unnecessary dependency on UITableView implementation.
That being said, in order to achieve shared behavior that is performed either on button click, or on row selection, refactor it out into a common method that is not coupled with UITableViewDelegate
For example:
-(void)doSomethingCommon {
//do shared code here
}
-(void)chooseUserButtonPressed:(id)sender {
[self doSomethingCommon];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self doSomethingCommon];
}
And if your UITableView shows more than one of these rows, for which you depend on knowing which corresponding model object is related to the cell, than you can use the tag property on UIView subclasses (usually something in your cell) to mark the row that the object is shown in.
I have a tableview with a few items. I want to get a new view when I click on one of the cells. However, I want the view to be different depending on which cell I clicked.
For example: I have a tableview of buildings. These buildings can be Airports, factories and houses.
When I click on a cell that shows an airport, I want the next view to be different than when I click on a cell that shows a factory.
How do I do this?
The problem can be easily solved by giving each cell a type. If you're implementing a custom cell class add it as an enumerated type (improves readability)
// cell header file
typedef enum {
CellTypeAirports,
CellTypeFactories,
CellTypeHouses
} CellType;
#interface CustomTableCell
// ...
#property(assign) CellType type;
#end
Set the type when you're instantiating (dequeueing) the cell and then in your tableView:didSelectRowAtIndexPath: method check the cell's type
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// ...
if ([cell type] == CellTypeAirports) {
// push airports vc
} else if ([cell type] == CellTypeFactories) {
// factories
}
// and so on
}
If you're using standard UITableViewCells then you can utilize their tag property to store the type.
I'm using GLTapLabel to display my text in my UITableView. It will have some links I can click on as GLTapLabels, but when I click those links the tableView:didSelectRowAtIndexPath: fires. So how can I detect the click action in those links?
Implement tableView:willSelectRowAtIndexPath: and return nil for any row you don't want to be selected.
Return Value
An index-path object that confirms or alters the selected row. Return an NSIndexPath object other than indexPath if you want another cell to be selected. Return nil if you don't want the row selected.
For example, to only prevent selection in the first section:
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
return nil;
}
return indexPath;
}
You can also try setting exclusiveTouch on the GLTapLabel or overriding its hitTest:withEvent: as described in the answer to Why is UIView exclusiveTouch property not blocking?.