Which method should I use to do something between UICollectionViewCells? - ios

I have implemented an UICollectionView image gallery. Each cell has some views and I would like to hide or show that views when I change the current cell, at least when the event starts. is there any method? or should I do something by delegate? I have paging enabled and custom cell and FlowLayout.
I have done everything almost like this tutorial

One way would be to keep the current selected cell index in a local variable in your viewController, and use that index to perform any actions when selecting another cell:
#property (nonatomic) NSIndexPath *selectedCellIndexPath;
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (![self.selectedCellIndexPath isEqual:indexPath]) {
UICollectionViewCell *lastSelectedCell = [collectionView cellForItemAtIndexPath:selectedIndexPath];
// Perform any change to lastSelectedCell before deselecting it
[collectionView deselectItemAtIndexPath:lastSelectedIndexPath animated:YES];
}
self.selectedCellIndexPath = indexPath;
UICollectionViewCell *selectedCell = [collectionView cellForItemAtIndexPath:indexPath];
// Change what you want in the newly selected cell;
}

Related

Ios10 UICollectionView prefetching enabled causes selectItemAtIndexPath behave incorrectly

i was trying to create a collection view with a list of cells. And when user hits a button it will random select one of the cell.
Now suppose there are only 10 cells. i.e numberOfItemsInSection delegate return 10.
The view controller is the data source for the collectionView. The collection view is called myCollectionView. And it has a property called selectedIndexPath
So ViewController:
#interface ViewController () < UICollectionViewDataSource>
#property (nonatomic, strong) NSIndexPath * selectedIndexPath;
#property (strong, nonatomic) IBOutlet UICollectionView *myCollectionView;
#end
Here's my random select code in the view controller:
-(void)chooseRandom{
NSInteger randomShadeIndex = arc4random_uniform((uint32_t)10);
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:randomShadeIndex inSection:0];
self.selectedIndexPath = indexPath;
[self.myCollectionView selectItemAtIndexPath:indexPath animated:YES scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
}
And here's my cellForItemAtIndexPath in my view controller
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
MyCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"MyCell" forIndexPath:indexPath];
cell.selected = self.selectedIndexPath == indexPath;
return cell;
}
And here's setSelected: method in MyCell:
- (void)setSelected:(BOOL)selected{
[super setSelected:selected];
if (selected){
self.backgroundColor = [UIColor redColor];
} else {
self.backgroundColor = [UIColor greenColor];
}
}
So now when i call chooseRandom from the button press. If the random cell to be selected is not visible(not in current screen), then there's a high chance that it doesn't end up having setSelected:YES called during the selectItemAtIndexPath:.(Or it gets called but sets the cell.selected to NO instead of YES). Meaning the resulting screen has none of the cell selected.
And the interesting thing is, when i tried touching the screen (without selecting any cell). It will called the setSelected: on the cell to be selected. So i think selectItemAtIndexPath: is bugged.
And this only happens, when prefetching enabled in interface builder is set to be enabled.(which is the default for ios 10).
And i've tried following ways to solve this, but none of them works:
Add [self.myCollectionView cellForItemAtIndexPath:indexPath].selected = YES; at the end of chooseRandom.
Use scrollToItemAtIndexPath along with method 1 instead of selectItemAtIndexPath:
I think either this is a bug or I ignore something completely. I've been stuck on this for hours and couldn't figure out why. Now i think it is most likely a bug for selectItemAtIndexPath with prefetching enabled is set.
Please help me and tell me if you encounter the same issue. Thanks!
EDIT:
not sure if the same question. this link has similar issue but with deselect
A UICollectionView, just like a UITableView, caches its cells. Meaning, that if you have 100 cells, and only 10 of them are visible, it will only create 10 cells and reuse them as you scroll, setting the model/data of the visible cells as you are scrolling, reusing the ones that are not visible anymore (to save memory). So the reason you are not able to select a cell that is not visible, might be because it hasn't been created.
Please check this if it helps you
Can download sample project
https://www.dropbox.com/s/oeo9p1h3e8xfpfj/CollectionView.zip?dl=0?
-(void)chooseRandom{
NSInteger randomShadeIndex = arc4random_uniform((uint32_t)50);
NSLog(#"%ld",(long)randomShadeIndex);
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:randomShadeIndex inSection:0];
self.selectedIndexPath = indexPath;
[self.myCollectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:true];
[_myCollectionView reloadData];
}

Access the UICollectionViewCell Components in custom method

I have some components in UICollectionViewCell and i can access all inside the cellForItemAtIndexPath. I need to access the component in a custom method,
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
// UIButton
cell.btn1.frame =CGRectMake(380,100, 150, 40);
[cell.btn1 addTarget:self action:#selector(btnClicked) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
In custom method,
-(void)btnClicked{
MycollectionclassView *cell =[[MycollectionclassView alloc]init];
cell.btn1.backgroundColor =[UIColor greenColor];
}
Here I cannot change the color of the button.
You must create a protocol and create a IBAction to a method. When your action method is called, you can notify the protocol and its screen that has the UICollectionView protocol will receive this event.
On your cell header file.h define the protocol:
#protocol YourCellNamelDelegate <NSObject>
#required
/**
* Notifies that current ITEM was clicked on icon.
*
* #param cell The current cell that was clicked.
*/
-(void)bookmarkClicked:(YourCellName *)cell; // OR without argument
#interface YourCellName : UITableViewCell
/**
* Reference to 'YourCellNameDelegate' delegate.
*/
#property (assign,nonatomic) id<YourCellNamelDelegate>delegate;
...
On your cell file.m
...
- (IBAction)bookmarkClickAction:(id)sender {
// change or update screen element here ...
if (self.delegate) {
[self.delegate bookmarkClicked:self];
}
}
On your ViewController implement the your protocol
#interface SMGListViewController () <YourCellNameDelegate>
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// get your cell here, for example in my case:
SMGCityGuideCollectionViewCell *cell;
cell = (SMGCityGuideCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
SMGTouristInfo *touristInfo = [self.listTouristInfo objectAtIndex:indexPath.row];
[cell setTouristInfoCell:touristInfo];
// IMPORTANT HERE *************************
cell.delegate = self;
// IMPORTANT HERE *************************
return cell;
}
...
#pragma mark <SMGPlaceCellDelegate>
-(void)bookmarkClicked:(SMGPlaceCell *)cell {
// notify events here ...
NSIndexPath *index = [self.tableViewPlaces indexPathForCell:cell];
[self.tableViewPlaces reloadRowsAtIndexPaths:#[index] withRowAnimation:UITableViewRowAnimationAutomatic];
}
I hope it helps! :)
To make things clear , when cellForItemAtIndexPath gets called it is the time that your cells get laid in the view , in your custom method you are just creating a cell object but this object does not reference the objects that is laid out in your collection view , so your just changing the color of a newly created object that does not reference anything .
In the Button Click function pass arguments as Sender , by which in the function call you can access the collectionView cell as Sender.SuperView .
You can also find the indexpath from the collectionviewcell now.
Now if you want to update a UI of the Collection view cell , you will have to do it in the cellforItemAtIndexPath function. which can be called by [collectionview reloadData].
So I would suggest to keep some flag variable in the data source to check if you have to update the UI or not.

How can I update a tableview cell from a -(void) method?

I am trying to update a specific dynamic cells font size from a -(void) method or some way without using cellForRowAtIndexPath. Is this possible?
Thanks
If you know the position of the index, as follows:
First create indexPath by row at position:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:0];
Then access to cell:
MyCell *cell = (MyCell *)[self.tableView cellForRowAtIndexPath:indexPath];
Call
[self.view.tableView reloadRowsAtIndexPaths:#[your_index_path] withRowAnimation:UITableViewRowAnimationAutomatic];
inside your method. that will cause row update
You can not update a cell without having a call to: cellForRowAtIndexPath.
When you want to update font size in a cell, then reload the cell. In your cellForRowAtIndexPath method add a conditional statement to check the indexPath of the cell you want a different font size and edit different font size. Do not forget to add else condition so that when the cell reloads it will make the font size normal for other cells.
However if you want somehow. Then you can use global reference variable to the cell. But this will only work if the cell is displaying on screen.
Here is the example code:
#interface TableViewController ()
{
UITableViewCell *aCell;
}
#end
#implementation TableViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *theCell = [tableView dequeueReusableCellWithIdentifier:#"TableViewCellIdentifier" forIndexPath:indexPath];
if(indexPath.row == SPECIFIC_ROW_NUMBER){
aCell = theCell;
theCell.tag = SPECIFIC_ROW_NUMBER;
}else if (theCell.tag == SPECIFIC_ROW_NUMBER){
// If the cell is dequeued
aCell = nil;
theCell.tag = 99999; // some unused row number
}
}
- (void)someMethod {
if(theCell != nil){
aCell.titleLabel.font = NEW_FONT;
}
}
#end

Two different UICollectionViewCells, switch between them in a UISegmentedController

I wonder which's the easiest way to have two different UICollectionViewCells-identifiers in the same UICollectionView? I've got an UISegmentedController that I want to switch between to different styles of UICollectionViewCells.. Where to implement which code. I've got an PatternViewCell for the first Cell but how do i do with the other? Please advice!
You can have two collection view cell prototypes registered for a single cell class for a single collection view with a single data source.
First, in your storyboard, set Cell1 as a reuse identifier for the first cell prototype and Cell2 for the second one. Both of them should have PatternViewCell class.
Then, on changing value of your segmented control you reload your collection view:
- (IBAction)segmentedControlValueChanged:(id)sender {
[self.collectionView reloadData];
}
Bind this action to your segmented control for the Value Changed event.
Then in - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath method you can choose a reuse identifier depending on selected index.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
NSString *identifier = nil;
if (self.segmentedControl.selectedSegmentIndex == 0) {
identifier = #"Cell1";
} else {
identifier = #"Cell2";
}
PatternViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
//configure cell
return cell;
}

segue not working with UITableViewCell alloc, but dequeueReusableCellWithIdentifier

I'm using storyboard with UITableView in UINavigationController.
In this UITableView, used custom tableViewCell having interior properties.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomTableViewCell *cell = nil;
if (SYSTEM_VERSION_LESS_THAN(#"6.0") ) {
//iOS 6.0 below
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
}
else {
//iOS 6.0 above
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath]; //work segue
}
Above code work well with push segue. But not when I used
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"]; //not work segue
I used this alloc method for preserve cell's data from reusing cell.
It's just alloc vs deque.. method difference. What am I missing?
edit) I know that not using the dequeReusableCell method is bad for the performance reason. But, the number of cells would not be many. This is why I don't need the deque method.
"not working" means "do not perform push segue", not crash.
It shows cell same like when dequeReusable method used except the disclosure indicator icon at the right of cell. The indicator icon come from storyboard setting.
And when I touch the cell, the cell highlighted blue but the push segue does not performed.
CustomTableViewCell has 4 properties. That's all different from UITableViewCell. Users set the properties at DetailViewController(push segue lead to this). The cell doesn't have IBOutlet ref. In MasterViewController(having the tableView), cellForRowAtIndexPath method returns CustomTableViewCell above code.
cellForRowAtIndexPath method adds a on/off button on the left of indicator on CustomTableViewCell
And set a tag number for the cell.
The use of dequeueReusableCellWithIdentifier is what enables you to use your prototype cell. If you use initWithStyle instead of dequeueReusableCellWithIdentifier, then you don't and you therefore lose any segues, disclosure indicators, other UI appearance that you've defined for those cell prototypes, too.
If you're determined to go this route, you'll have to go "old school" (i.e. do what we all used to do before cell prototypes) and write your own didSelectRowForIndexPath. But if you already have that segue defined, let's say you called it "SelectRow", then your didSelectRowForIndexPath can perform that:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[self performSegueWithIdentifier:#"SelectRow" sender:cell];
}
If you need to have your disclosure indicator, then your custom cell routine (or the cellForRowAtIndexPath) will have to set that manually. And if you add it with
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
Then you need to manually handle it:
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[self performSegueWithIdentifier:#"SelectAccessory" sender:cell];
}
Bottom line, you can get this to work, but you're just doing a lot of extra work and losing losing the performance and memory benefits of dequeuing cells. I'd heartily encourage you to revisit the decision to not use dequeueCellWithIdentifier.

Resources