I have a UICollectionView whose all cells contains a UITableView. My UICollectionView delegate / datasource methods are handled by a view controller (let's say TTCollectionViewController), and tableViews are handled directly by collection view cells (TTCollectionViewCell).
In order to change some label colors, I need to keep track of each selected rows on my tableViews so i did something like that :
//
// TTCollectionViewCell.m
//
[...]
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// keep track of selected drugs
if (![self.selectedDrugIndexPaths containsObject:indexPath])
{
[self.selectedDrugIndexPaths addObject:indexPath];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TTTableViewCell *contentCell = [tableView dequeueReusableCellWithIdentifier:#"test"];
if (contentCell == nil)
contentCell = [[TTTableViewCell alloc] init];
[...]
// test
if ([self.selectedDrugIndexPaths containsObject:indexPath]) {
contentCell.drugLabel.textColor = [UIColor grayColor];
} else {
contentCell.drugLabel.textColor = [UIColor blackColor];
}
}
[...]
But here's the thing : with the dequeue reusable thing, my collectionView is messing up all my tracks (on scrolling, selected rows on table 1 become selected on table 3 for example).
I know it's a bad idea to don't use dequeuereusable (i don't even know if it's possible to not use it), but is there something similar that i can do ?
Do anyone have already implement this kind of case ?
Thx in advance.
EDIT :
Here's my cellForRow... methods of my collectionView as you asked for :
///
/// TTCollectionController.m
///
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
TTCollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
cell.data = [self.data objectAtIndex:indexPath.row];
return cell;
}
Related
I have added tableview on top of UIViewController and I have also implemented the UITableViewDataSource and UITableViewDelegate methods
For some reason, I am not getting the value out of didSelectRowAtIndexPath. I get the expected value of indexPath.row but the selectedCell.textLabel.text returns nil.
I am not able to figure out what may be the problem. I am using the dynamic table.
//Array of multiple objects is filled through the delegated arrays and the arrays are properly filled.
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return _arrayForMultipleObjects.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
array = [[NSMutableArray alloc]init];
// Configure the cell...
_singleSelectionLabel= (UILabel *) [cell viewWithTag:2];
_singleSelectionLabel.text=[self.arrayForMultipleObjects objectAtIndex:indexPath.row];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *valu ;
NSLog(#"%i count",indexPath.row);
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
NSString *cellText = selectedCell.textLabel.text;
NSLog(#"%#",cellText);
//Here cellText is null
if([tableView cellForRowAtIndexPath:indexPath].accessoryType==UITableViewCellAccessoryNone)
{ [tableView cellForRowAtIndexPath:indexPath].accessoryType=UITableViewCellAccessoryCheckmark;
valu = cellText;
}
NSLog(#"%#",valu);
}
If you are using storyboard then the default cell style of table cells is Custom which gives you a blank cell where you can add other types of controls to it.
If you dragged a label onto the cell and changed its text, then you are likely using a Custom style of UITableViewCell.
The property textLabel is only available on cells of types other than Custom, such as Basic, or Detailed.
Please double check if this is the case for you.
Edit: As ready suggested you can fetch the text like this:
self.arrayForMultipleObjects[indexPath.row]
I'm trying to achieve airbnb look for my UITableViewController.
separators are not in the full width of the screen
each cell has a unique size and background color
I've managed to tackle #2 using static table and setting each cell size in IB but have the following problems:
setting a background color in IB didn't take (changed both background color as well as tint)
I wish to "delete" a cell programmatically when it has no content but the only function that returns cell height - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
is generic for all cells and is called prior to populating the cells with data, so at this method I can't decide if the cell should be deleted or not.
I think you should use the delegate method willDisplayCell: forRowAtIndexPath: of the table view. This delegate method is called just before the cell is ready to be displayed on the table view. In this delegate you will have the cell created, if you need some modifications you can do here, which will be reflected just before the cell is displayed.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
[cell setBackgroundColor:[UIColor redColor]];
// and all other cell customizations
}
You could set separatorInset and layoutMargins properties of table view before layout subviews and cell's separatorInset and layoutMargins properties before displaying cell.
For setting custom cell's height you may get needed cell by calling a table view data source method instead of call cellForRowAtIndexPath: method of table view.
Example:
// 1. Make full width separators:
- (void)viewWillLayoutSubviews {
if ([self.tableView respondsToSelector:#selector(setSeparatorInset:)]) {
[self.tableView setSeparatorInset:UIEdgeInsetsZero];
}
if ([self.tableView respondsToSelector:#selector(setLayoutMargins:)]) {
[self.tableView setLayoutMargins:UIEdgeInsetsZero];
}
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if ([cell respondsToSelector:#selector(setSeparatorInset:)]) {
[cell setSeparatorInset:UIEdgeInsetsZero];
}
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
#pragma mark - Table view data source
// 2.Customize cells background color and height
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellIdentifier = [NSString stringWithFormat:#"s%ld-r%ld", indexPath.section, indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
}
if (indexPath.row % 2) {
cell.backgroundColor = [UIColor redColor];
cell.contentView.backgroundColor = [UIColor whiteColor];
cell.textLabel.text = cellIdentifier;
} else {
cell.backgroundColor = [UIColor cyanColor];
cell.contentView.backgroundColor = [UIColor whiteColor];//background color of contentView overlaps cell's background color
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath]; //getting cell at indexPath
if ([self isCellEmpty:cell]) {
return 22;
}
return 44;
}
- (BOOL)isCellEmpty:(UITableViewCell *)cell {
return !cell.textLabel.text.length; //Place your code for checking cell's content
}
I am trying to make some photoPicker with CollectionView.
Have
allowsMultipleSelection = YES
Using following method
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
selectedPictures = [NSMutableArray array];
[selectedPictures addObject:[imagesArray objectAtIndex:indexPath.item]];
NSLog(#"Selected list:\n %#", selectedPictures);
NSLog(#"Objects in Array %i", selectedPictures.count);
}
While I am selecting cells, it's always adding to MutableArray only one object according it's indexPath. What could be an issue?
Why don't u keep the selectedPictures as a member variable
in your code
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
selectedPictures = [NSMutableArray array]; //keep on creation the new array on each selection
[selectedPictures addObject:[imagesArray objectAtIndex:indexPath.item]]; //adding the selected images means single image
NSLog(#"Selected list:\n %#", selectedPictures);
NSLog(#"Objects in Array %i", selectedPictures.count);
}
try this
put his in viewDidLoad
- (void)viewDidLoad
{
selectedPictures = [[NSMutableArray alloc]init]; //initilise hear
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// selectedPictures = [NSMutableArray array]; //keep on creation the new array on each selection
[selectedPictures addObject:[imagesArray objectAtIndex:indexPath.item]]; //adding the selected images means single image to already initialised array
NSLog(#"Selected list:\n %#", selectedPictures);
NSLog(#"Objects in Array %i", selectedPictures.count);
}
Hope this helps u .. :)
it may be caused by not calling super. While the documentation for UICollectionReusableView fails to mention this, the documentation for UITableViewCell, which has the same method, does.
- (void)prepareForReuse
{
[super prepareForReuse]
// Your code here.
}
Old Answer:
This may be a bug with the UICollectionView.
What's happening is cells that were previously selected are being reused and maintain the selected state. The collection view isn't setting selected to "NO".
The solution is to reset the the selected state in prepareForReuse of the cell:
- (void)prepareForReuse
{
self.selected = NO;
}
If the reused cell is selected, the collection view will set selected to "YES" after prepareForReuse is called.
This is something the UICollectionView should be doing on it's own. Thankfully the solution is simple. Unfortunately I spent a ton of time working around this bug by tracking my own select state. I didn't realize why it was happening until I was working on another project with smaller cells.
Also Try this
I'm not seeing why this would take place. I do not believe the issue is the use of row vs item, though you really should use item. I can imagine, though, if your collection view has more than one section, that only looking at row/item but ignoring section would be a problem (i.e. it would select the same item number in every section).
To cut the Gordian knot, I'd suggest saving the NSIndexPath of the selected item, and then using that for the basis of comparison. That also makes it easy to render an optimization in didSelectItemAtIndexPath. Anyway, first define your property:
#property (nonatomic, strong) NSIndexPath *selectedItemIndexPath;
And then implement cellForItemAtIndexPath and didSelectItemAtIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
cell.imageView.image = ...
if (self.selectedItemIndexPath != nil && [indexPath compare:self.selectedItemIndexPath] == NSOrderedSame) {
cell.imageView.layer.borderColor = [[UIColor redColor] CGColor];
cell.imageView.layer.borderWidth = 4.0;
} else {
cell.imageView.layer.borderColor = nil;
cell.imageView.layer.borderWidth = 0.0;
}
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
// always reload the selected cell, so we will add the border to that cell
NSMutableArray *indexPaths = [NSMutableArray arrayWithObject:indexPath];
if (self.selectedItemIndexPath)
{
// if we had a previously selected cell
if ([indexPath compare:self.selectedItemIndexPath] == NSOrderedSame)
{
// if it's the same as the one we just tapped on, then we're unselecting it
self.selectedItemIndexPath = nil;
}
else
{
// if it's different, then add that old one to our list of cells to reload, and
// save the currently selected indexPath
[indexPaths addObject:self.selectedItemIndexPath];
self.selectedItemIndexPath = indexPath;
}
}
else
{
// else, we didn't have previously selected cell, so we only need to save this indexPath for future reference
self.selectedItemIndexPath = indexPath;
}
// and now only reload only the cells that need updating
[collectionView reloadItemsAtIndexPaths:indexPaths];
}
Check also this
Your observation is correct. This behavior is happening due to the reuse of cells. But you dont have to do any thing with the prepareForReuse. Instead do your check in cellForItem and set the properties accordingly. Some thing like..
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cvCell" forIndexPath:indexPath];
if (cell.selected) {
cell.backgroundColor = [UIColor blueColor]; // highlight selection
}
else
{
cell.backgroundColor = [UIColor redColor]; // Default color
}
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *datasetCell =[collectionView cellForItemAtIndexPath:indexPath];
datasetCell.backgroundColor = [UIColor blueColor]; // highlight selection
}
-(void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *datasetCell =[collectionView cellForItemAtIndexPath:indexPath];
datasetCell.backgroundColor = [UIColor redColor]; // Default color
}
I solved my issue;
The problem was very simple, I should have initialise MutableArray not in the Method didSelectItemAtIndexPath, but in the ViewDidLoad. Now it adding pictures one by one
I am trying to create simple messaging app just like the built one.
I want to reproduce the same effect speech bubbles as iMessage.
I found a project from apple called MultipeerGroupChat which has that functionality.
The problem is It has a lot more than what I need making it hard to replicate, because of class dependencies. I dont need multipeer or sending images.I stripped a lot of code out already.
I now have a simple TableView, I added the bubble images and 2 classes:
MessageView.h
Transcript.h
I narrowed down the issue to this table view delegate to display the bubbles:
// The individual cells depend on the type of Transcript at a given row. We have 3 row types (i.e. 3 custom cells) for text string messages, resource transfer progress, and completed image resources
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Get the transcript for this row
Transcript *transcript = [self.transcripts objectAtIndex:indexPath.row];
// Check if it's an image progress, completed image, or text message
UITableViewCell *cell;
if (nil != transcript.imageUrl) {
// It's a completed image
cell = [tableView dequeueReusableCellWithIdentifier:#"Image Cell" forIndexPath:indexPath];
// Get the image view
ImageView *imageView = (ImageView *)[cell viewWithTag:IMAGE_VIEW_TAG];
// Set up the image view for this transcript
imageView.transcript = transcript;
}
else if (nil != transcript.progress) {
// It's a resource transfer in progress
cell = [tableView dequeueReusableCellWithIdentifier:#"Progress Cell" forIndexPath:indexPath];
ProgressView *progressView = (ProgressView *)[cell viewWithTag:PROGRESS_VIEW_TAG];
// Set up the progress view for this transcript
progressView.transcript = transcript;
}
else {
// Get the associated cell type for messages
cell = [tableView dequeueReusableCellWithIdentifier:#"Message Cell" forIndexPath:indexPath];
// Get the message view
MessageView *messageView = (MessageView *)[cell viewWithTag:MESSAGE_VIEW_TAG];
// Set up the message view for this transcript
messageView.transcript = transcript;
}
return cell;
}
As mention before I only need the message so I stripped down to this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
Transcript *transcript = [self.messageArray objectAtIndex :[indexPath row]];
cell = [tableView dequeueReusableCellWithIdentifier:#"Message Cell" forIndexPath:indexPath];
MessageView *messageView = (MessageView *)[cell viewWithTag:MESSAGE_VIEW_TAG];
messageView.transcript = transcript;
//how does the code add the view and return it ?? :-S
return cell;
}
This code does not display anything.
Now I dont understand how this code customize the cell to show speech bubbles.
Please advice.
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath on UITableView will return an instance of UITableViewCell that is ready to be reused or if no reusable cells exist it will create one.
However, according to the documentation, you need to first register a nib or Class with the table view so it knows what cell maps to that reuse identifier.
Check out the methods:
- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier
- (void)registerClass:(Class)aClass forHeaderFooterViewReuseIdentifier:(NSString *)identifier
You are missing:
[cell.contentView addSubview:messageView];
in:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
It should be:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
Transcript *transcript = [self.messageArray objectAtIndex :[indexPath row]];
cell = [tableView dequeueReusableCellWithIdentifier:#"Message Cell" forIndexPath:indexPath];
MessageView *messageView = (MessageView *)[cell viewWithTag:MESSAGE_VIEW_TAG];
messageView.transcript = transcript;
[cell.contentView addSubview:messageView];
return cell;
}
How do you hide a static cell?
I would like to hide and static cell if an image does not exist.
I tried:
imageCell.hidden = YES; //did not work
I have seen answers suggesting to change datasource or use:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 0;// will hide all cell
}
But I couldnt find a way to do this with a specific view cell.
what I want to achieve:
if(image==nil){
//hide imageCell
}
Now here is the catch , the image is downloaded asynchronously, so deleguate methods might be called before the attempted downlaod.
Do the following :
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 2 && !myImageIsLoaded)
return 0; // Will hide just the third row of your table if myImageIsLoaded is false
return 44;
}
And you can use the following to animate all whenever you want (e.g. each time an image as loaded) :
[myTable beginUpdate];
[myTable endUpdate];
If your cells are static so it should work. Otherwise you could encounter some problems.
If you want literally hide the cell that image does not exist, you can try this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
return [cell imageView] ? 44.0f : 0.0f;
}