Check if a UITableViewCell is selected, and the cell went off screen - ios

I have a custom UITableViewCell that dequeueReusableCells. I have an int called selectedRow which gets the selected rows number in the method of didSelectRowAtIndexPath. I then pass selectedRow to an int called rowNumber which is in the class of my customCell.
In customCell.m, I have the method prepareForReuse. In that I made an NSLog of rowNumber.
What I want to do is: if a row is selected and that row went off screen, then perform some code. I would probably have to use prepareForReuse, but I don't know what to do in it.
I know it's a bit complicated, but if you have any questions, then I'd be happy to answer

Actually, you don't need to call prepareForReuse directly as it would be called automatically:
this method is invoked just before the object is returned from the
UITableView method dequeueReusableCellWithIdentifier:.
and as you don't know what to do in it, note:
For performance reasons, you should only reset attributes of the cell
that are not related to content, for example, alpha, editing, and
selection state
UITableViewCell Class Reference

You can use - (void)tableView:tableView didEndDisplayingCell:cell forRowAtIndexPath:indexPath; in UITableViewDelegate to know which cell is scrolled off screen.
However, this method is iOS6+ only.

You're over complicating things. You don't have to do prepareForReuse the in the custom cell.
Take a look at this.
http://www.icodeblog.com/2009/05/24/custom-uitableviewcell-using-interface-builder/
Its pretty similar for storyboards.

Related

Should I use dequeReusableCellWithIdentifier?

I know this question is dumb. But suddenly got stuck up with this question.
When I use dequeReusableCellWithIdentifier, the cells are reused. To be more specific, first 'n' set of cells is reused - along with their references
As the same references are reused, I can't actually store local variables to the cells. I actually need to assign them everytime in cellForRowAtIndexPath
Assume I'm using a custom complex UITableviewcell. (I know we should reduce complexity. But still...)
Some views are to be added to the cell.
Best example i could think of is an image slider.
So the number of images in the image slider differs based on the datasource. So i need to add the views in cellForRowAtIndexPath. Can't avoid it.
Since the same reference of cells are reused, I need to add these views everytime cellForRowAtIndexPath is called. I think that is a bit of heavy load.
I thought of using drawRect method, but it does not work with UITableViewAutomaticDimension
If I don't use dequeReusableCell, I will have individual referenced cells, but it will cost the memory and performance.
So what is the best solution?
Can I use dequeReusableCell and still need not rewrite its content?
Edit 1:
By mentioning dequeReusableCell, I did mention dequeueReusableCellWithIdentifier - forIndexPath. I'm sorry for the confusion.
Edit 2:
I feel, I'm not so clear in my question.
Lets consider I have a array in my viewcontroller.
I'm configuring my cell in cellForRowAtIndexPath with dequeueReusableCellWithIdentifier - forIndexPath
So what happens is, everytime i scroll, when the invisible rows become visible, cellForRowAtIndexPath is called.
Lets say I have a image slider with n imageviews. For each cell, this 'n' differs. So I'm forced to draw the cell's view based on its dataSource.
So everytime the tableView calls cellForRowAtIndexPath, the cell is configured again and again.
I want to avoid this to improve performance.
what I do in this case is the following:
I always use dequeReusableCell:, for reasons you already said
I write a cleanCell method in the custom UITableViewCell class in order to clean everything has been already set on the cell (just to make it ready for reuse)
then in the cellForRowAtIndexPath: I configure my cell as desired
That's what I would do: I would move the logic of adding those heavy views inside the cell's class itself. So that in cellForRowAtIndexPath I would just set an object that has to be displayed inside the cell, and cell would create all the necessary views on its own (it's a view layer, after all). So inside cellForRowAtIndexPath it would look something like this:
cell = [tableView dequeueReusableCellWithIdentifier...forIndexPath...];
cell.modelObject = myModelObject;
and that's it.
Then inside the cell class I would implement optimizations for switching between different model objects. Like, you could defer releasing the heavy views, and reuse them, when the cell is reused.
To be honest, I don't get your example with the slider, but let's suppose you have a star rating with 5 stars, which can be visible or not. There are various way to do it, but let's assume you're just adding / removing a UIImageView for each star. The way you do it now, you would have an empty cell and create / add views in cellForRow. What I'm suggesting is to have 5 UIImageViews as part of your cell, and inside the cell set their visibility based on the modelObject.rating. Something like that. I hope, it helps to illustrate my point :)
UPDATE: when your cell can have an arbitary number of images inside of it, I would probably create them inside the cell's class. So, for instance, for the first model object we need 3 image views. So we create them. But then we don't release them in prepareForReuse, we wait for the next model object to come. And if it has, say, 1 image, we release 2 image views (or we don't, so that we didn't have to recreate them later, it depends on what is more critical: performance, or memory usage), and if it needs 5, we create two more. And if it needs 3, we're all set already.
In your case you should use dequeueReusableCellWithIdentifier(_ identifier: String, forIndexPath indexPath: NSIndexPath) method. This method automatically decides whether the cell needs to be created or dequeued.
I need to add these views everytime cellForRowAtIndexPath is called. I think that is a bit of heavy load.
You don't need to add those views every time cellForRowAtIndexPath gets called. Those views should be already added as a cell is instantiated. In cellForRowAtIndexPath you only assign images and other values to those views.
In case if you use storyboards and prototype cells your code should look like this:
var items = [UIImage]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ImageCell", forIndexPath: indexPath)
let item = items[indexPath.row]
let recipient = item.recipients.first
// I have given my UIImageView tag = 101 to easily access it from the view controller.
// This allows me to avoid defining a custom UITableViewCell subclass for simple cells.
if let imageView = cell.viewWithTag(101) as? UIImageView {
imageView.image = item
}
return cell
}
1)I think you need to use "lazy load" of cells.
http://www.theappguruz.com/blog/ios-lazy-loading-images (for example)
2) You can create a property in your viewController, and create an NSMutableDictionary property, which will store the some data by the some key,
if your model have things like "ID" of the cells or you can use indexPath.row for it, then you can load it only one time, and next time when -cellForRow will be called, you can retrieve data from your dictionary by the
[self.dataDictionary objectForKey:#"cellID"];
And with this you, solve your repeating load.
In -cellForRow you can set the check,like.
if ([self.dataDictionary objectForKey:#"cellID"]) {
cell.cellImage = [self.dataDictionary objectForKey:#"cellID"];
} else {
UIImage *img = [uiimage load_image];
cell.cellImage = img;
[self.dataDictionary setObject:img forKey:#"cellID"];
}
And so on. Its like example, the dataType's you can choose by yourself, it's example.
Thanks for everyone helping me out. I think the better answer was from #Eiko.
When you use reusable cells, it must be completely reusable. If you are going to change its views based on the data, you either have to use a new identifier or avoid using reusable cells.
But avoiding reusable cells, will take a load. So you should either use different identifiers for similar cells or as #FreeNickName told - you can use an intermediate configuration and alter it on your need.

In UITableView, what's the delegate for "visibleCells"?

When cells come in and out of device's screen, I want my viewController to know exactly what came and what went out. Is there a way to do this?
There isn't a delegate method just for "visible cells". There isn't anything called when a cell leaves the screen. There really isn't anything when a cell becomes visible.
There is the cellForRowAtIndexPath data source method. This is called when a cell is needed.
There is the willDisplayRowAtIndexPath delegate method. This is called when a cell will be displayed.
There is the didEndDisplayingCell delegate method. This is called when a cell is removed from the table view.
There is the indexPathsForVisibleRows method on UITableView. This lets you know what rows are currently in view.
There is the prepareForReuse method on UITableViewCell. This lets a cell reset itself to be reused for another row.
Better describe what you are trying to accomplish in order to get a more specific answer.

UICollectionView selection does not last

I have a UICollectionView I use like a tool selection container. You have many tools (the cells), when you select one, the former selected is deselected, and so on... You can only have one tool selected at a time.
The problem is that I can't manage to have the cells selected. When I tap the cell, the collectionView:didSelectItemAtIndexPath: is called. I then reload the selected cell to change it's appearance (I change the alpha of the image) by using the [collectionView reloadItemsAtIndexPaths:#[indexPath]].
Well that reloads the cell ok, except one damn thing: in the collectionView:cellForItemAtIndexPath: the cell never get the selected property set to YES! So I can never change the alphabecause I never know when I must draw a selected cell or not.
Worse: [collectionView indexPathsForSelectedItems] is always empty!!!
I don't even mention the collectionView:didDeselectItemAtIndexPath: that is never called...
Well, if someone can help me understand what is going on, thanks in advance...
When you call reloadItemsAtIndexPaths: the collection view discards those cells and creates new ones, therefor discarding the selected state of the cells.
I'd suggest a couple of different options:
1.) In collectionView:didSelectItemAtIndexPath: call cellForItemAtIndexPath: to get a reference to the selected cell and update it's appearance there.
2.) (My Favorite) Use a custom subclass of UICollectionViewCell if you're not already, and override the method setSelected:. There you'll be notified when the cell is selected and you can update the appearance from within the subclass.
With regards to selecting multiple cell at once, you should try the allowsMultipleSelection property [myCollectionView setAllowsMultipleSelection:YES] The docs for that are here: https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionView_class/Reference/Reference.html#//apple_ref/occ/instp/UICollectionView/allowsMultipleSelection
Otherwise, #daltonclaybrook's answer is sufficient.

iOS callback when uitableviewcell gets destroyed

As a user scrolls up and down in an uitableview cells get destroyed and created.
Is there a way to detect when a cell is going to be or has been destroyed?
Assuming that by "getting destroyed" you actually are referring to a cell getting reused, simply implement prepareForReuse within your UITableViewCell derived class.
prepareForReuse
Prepares a reusable cell for reuse by the table view's delegate.
- (void)prepareForReuse
Discussion
If a UITableViewCell object is reusable—that is, it has a reuse
identifier—this method is invoked just before the object is returned
from the UITableView method dequeueReusableCellWithIdentifier:. For
performance reasons, you should only reset attributes of the cell that
are not related to content, for example, alpha, editing, and selection
state. The table view's delegate in tableView:cellForRowAtIndexPath:
should always reset all content when reusing a cell. If the cell
object does not have an associated reuse identifier, this method is
not called. If you override this method, you must be sure to invoke
the superclass implementation.
Availability
Available in iOS 2.0 and later.
See Also
– initWithFrame:reuseIdentifier:
#property reuseIdentifier
Declared In
UITableViewCell.h
Without going into the implications of suitability or performance, another option might be to periodically check what cells remain visible, using the visibleCells method of the UITableView class:
- (NSArray *)visibleCells
As per the documentation:
Returns an array containing UITableViewCell objects, each representing a visible cell in the receiving table view.
You can subclass UITableViewCell and override it's dealloc method.
Any good reason to do it assuming you are reusing the cells to save the resources ?
What you're attempting to intercept is part of the internal implementation of UITableView and how it manages its cells. While there are ways in which you can attempt to intercept such behavior, I would suggest that you avoid using them, as there is no guarantee that future implementations of UITableView will maintain this behavior.
It would be better in cases such as this to consider a different approach: be it design and implement your own table class, or change your code logic.
As stated above, cells aren't destroyed when the leave the screen. However there are some things you can do, to track related actions, depending on what you are trying to do.
First there is a delegate message:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
This is called before a cell enters the screen. Another possibility is the already stated prepareForReuse method of a cell.
Another approach would be: Try and override willMoveToSuperview: or any other of the related methods. I am not sure if this is fired after the cell becomes invisible, but it might work.
Best regards,
Michael

How to notice if a UITableViewCell has left the visible area?

I'm stuck with the problem that I want to know in my UITableView if a specific UITableViewCell, let's say the first, is still visible or already off the visible area.
I would also be ok to know if the cell of interest is now beeing reused at an other indexPath of the table.
One of my - later and frustrated approaches - was to have a thread that knows the first cell object and frequently pings it to check if a value I did set in the cell changed. Obviously a not so good solution.
Andy ideas how to do this right?
Remember that UITableView is UIScrollView subclass and its delegate also confirms to UIScrollViewDelegate protocol as well.
So in your table delegate you can implement scrollViewDidScroll: method and check contentOffset - if it's more then first cell height then first cell is not visible. You can also get the array off all currently visible cells using -visibleCells method, but I think knowing contentOffset should be enough.

Resources