I'd like to know when a UICollectionViewCell is displayed on the actual screen. cellForRowAtIndexPath is insufficient, as the cell isn't actually seen at this point. didEndDisplayingCell is insufficient, as it gets called when the cell is removed from view.
UITableViewDelegate has a willDisplayCell method that I've found useful for similar stuff in the past, but it doesn't appear to exist in UICollectionViewDelegate.
How can I determine when the cell is brought on to the screen?
Make a subclass of UICollectionViewCell. Override its didMoveToWindow method:
- (void)didMoveToWindow {
if (self.window != nil) {
// I am now in the window's view hierarchy and thus “on screen”.
}
}
Technically, the cell could still be not visible, either because it's outside the window's visible bounds, or because it's covered by another view. But normally neither of those will be the case.
Note also that if a cell is reused, it might not get removed from and re-added to the view hierarchy. The collection view can just change the cell's frame. (I know UITableView does this with table view cells as of iOS 6.0.) In that case, you won't receive a didMoveToWindow message when the cell gets re-used for another item.
If you explain why you want to know when a cell is displayed, we might be able to give you a better answer.
-[UICollectionView visibleCells] returns a NSArray of all cells visible on screnn
Related
I have used this tutorial to put a collectionView inside a UITableView. https://ashfurrow.com/blog/putting-a-uicollectionview-in-a-uitableviewcell-in-swift/
The UITableViewController is the dataSource & delegate for both the UITableView & the different collection views in each cell.
The problem is that I want to dynamically hide the CollectionView and to change its height to 0 whenever the collectionview is empty.
To do so, I have this code in CellForRowAtIndexPath
if (patients.paraclinicImage.count == 0){
[cell.collectionView setHidden:true];
cell.collectionViewHeight.constant = 0;
} else {
[cell.collectionView setHidden:false];
cell.collectionViewHeight.constant = 80;
}
By having this code, the collectionView shows up correctly initially. However, when scrolling fast, I will sometime have the cell load with the correct cell height, however the collection view will be empty. Refreshing the cell fixes this issue. Removing the above line of codes also fixes the issue.
Here are two images showing how the cell looks when first rendered, and after multiple scrollings (and re-renderings of the cell).
Before scrolling issue looks like below image
But After scrolling issue seems to like below one
I would appreciate any ideas you guys might have.
Debug view hierarchy showing an empty collectionView with an appropriate cell height
It is because your cell height is not updated on fast scrolling. UITableView caches the row height of indexPath just to calculate the scroll area. You need to set constraints properly and adjust the height of cell in heightForRowAtIndexPath: method.
Apply the logic inside heightForRowAtIndexPath: and your problem will be solved.
Happy Coding!!
I had the same issue. Sometimes, my collection view inside a tableviewcell wouldn't show the collectionview cells. I solved it by calling collectionView reload on main thread. I don't exactly know the reason, as I have many background calls on each cell and was reloading the tableview on main thread itself. Still, had to reload collectionview on main thread.
DispatchQueue.main.async {
cell.colViewProperties.reloadData()
}
I have UITableView which cells contain one UITextField in it. My UITableViewController is a delegate of all this text fields. Now, when UITableViewController gets deallocated, I want to set delegate of all that text fields to nil. The text field has a tag, so I can get it by it's tag once I have a cell.
The question is how to get all created cells? Asking UITableView for visibleCells returns only visible cells, but it can happen, that there is a row which is not visible, bit it still has my UIViewController as a delegate. So I really need to get all created cells somehow. cellForRowAtIndexPath does the same, so it wouldn't work for me either. The only way I see here is to store all text fields in array, but may be there is a better way?
Here is some code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:#"reuseId"];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"reuseId"];
UITextField *nameTextField = [[UITextField alloc] initWithFrame:nameTextFieldRect];
nameTextField.tag = TEXT_FIELD_TAG;
nameTextField.delegate = self;
[cell.contentView addSubview:nameTextField];
}
return cell;
}
-(void)dealloc
{
// todo: get all text fields and set theirs delegate to nil
}
Well, most answers suggest that I don't need to set delegate to nil, but as I'm paranoid, I suspect that the following scenario is possible:
User taps 'Back' button, so dealloc of my view controller is called. In dealloc my view controller releases it's table view, but the tableView still exists at this point, as well as all the text fields. And if somehow one of text fields would call it's delegate method at this point, the app would crash, because the delegate is not a valid object anymore.
If someone can explain why this scenario is not possible, than it would convince me that I don't need to set delegate to nil.
You do not need to do that. All of the cells will be deallocated as well, so they won't have a reference to the delegate.
By default the delegate is a weak reference, so it will not retain your object.
I am not expecting to have this answer marked as accepted, but this won't fit into a comment window.
So, rather than us trying to convince you, you could start one of the most important tool for an iOS developer, which is the profiler. And see for yourself by playing with the interface that you should get no more than the number of cells necessary to fill the screen are kept allocated, and then when you tap back, all are getting deallocated.
If they are not, they probably have a strong reference to something else, but this can easily detected with the profiler.
I also like to add that when working with cells the act of scrolling UITable, tap back enter again into table, scroll tap back (repeated at least 10 times) it's a mandatory practice to detect memory leak.
Also, I don't know the purpose of assigning a tag to the cell, I maybe wrong but with this:
nameTextField.tag = TEXT_FIELD_TAG;
consider that you have more than one cell with the same tag, therefore you cannot simply recall the desired one. I remember that the rule is the first placed on screen 'win' the TAG (or kind of).
[UPDATE]
Just a guess, I have never proved this, but to stay on the question, if your problem is to have a cell first for having the UITextView, have you tried to loop the main view and just ignore the cell:
UITextView textView = [mainView viewWithTag:TEXT_FIELD_TAG];
while(textView!=nil){ // or whatever loop or criteria you like
// deallo, nil, etc...
textView = [mainView viewWithTag: TEXT_FIELD_TAG];
}
Create a new delegate/protocol for that cell, and implement that delegate in the view controller, like PersonTableViewCellDelegate. Apart from that, your cell implements the UITextField delegate and in that code, call [self.delegate onKeyPressed] or whatever. I recommend you pass also a reference of the cell, so in the view controller you can use indexPathFromCell (or something like that) to know the position of the cell. If you are interested tell me about it and will copy some code.
I have two table views set up side by side, and I need them to scroll at exactly the same time. So, when you scroll one, the other one will scroll at the same time.
I did some searching and I couldn't find any information, but I assume it must be possible somehow.
My table views are both connected to the same class and I differentiate between them like this:
if tableView == tableView1 {
//
} else if tableView = tableView2 {
//
}
You can get set the scrollView delegate to self on both of your tableView's scrollViews. And in -scrollViewDidScroll, take the contentOffset and set the other scrollView's contentOffset to the same value.
Like Schemetrical said you should use scrollViewDidScroll.
see the first answer of this:
Scrolling two UITableViews together
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
{
UITableView *slaveTable = nil;
if (self.table1 == scrollView) {
slaveTable = self.table2;
} else if (self.table2 == scrollView) {
slaveTable = self.table1;
}
[slaveTable setContentOffset:scrollView.contentOffset];
}
If you want them to scroll in prefect lock-step then this isn't a trivial problem. UITableView is a subclass of UIScrollview, so you could probably create a custom subclass of UITableView that overrode various UIScrollView methods and when something caused the table view to scroll, it would do the same thing to the other table view.
Edit: #Schemetrical's suggestion of using the scroll view delegate is cleaner than creating subclasses. You might have to monitor quite a few of the scroll view delegate methods and use them to match the behavior in the other scroll view.
EDIT #2:
Apparently I'm wrong and scrollViewDidScroll is called for every change in the scroll view, so it's simpler than I thought to keep them synced. I'm going to leave my answer for context even though I was wrong.
Following https://stackoverflow.com/a/7313410/1971013 I need to get the height of each of my cells in - (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath.
But when I do:
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
MyTableViewCell* cell = (MyTableViewCell*)[self.tableView cellForRowAtIndexPath:indexPath];
return cell.height;
}
this (of course) results in unwanted recursion blowing up my app.
Any ideas of how I should do this?
The best answer is, sadly, I don't know. That's up to you here. The heightForRowAtIndexPath from UITableViewDelegate is what the UITableView uses to ask you what the height should be. In this function you are determining that.
Perhaps you can save it in your own memory in cellForRowAtIndexPath after you dequeueReusableCellWithIdentifier.
But if your linked questions are any indication, you're trying to expand a selected cell, correct? You can determine if it's the selected cell via
if ([indexPath compare:[tableView indexPathForSelectedRow]] == NSOrderedSame)
return 88; // some expanded value
else
return 44; // the default value
Gutblender is right: in the usual case, you don't ask a cell its height, you tell it.
It's important to embrace the fact that cells are recycled. From a practical perspective, you can't examine cells that are off screen because they don't exist.
Cautions aside, here are the options:
Create a method that defines the height of your table view cells, and use that method to calculate the value you return from heightForRowAtIndexPath. You can then call that method from elsewhere to determine what the height of the cell will be at a given index path, whether or not it exists. This is the standard approach. It's also, 99.9% of the time, the correct one.
Iterate through the table view's subviews. A bad idea because you don't control the NSTableView class, and the view hierarchy can change. And, again: cells are recycled.
Create custom table view cell class, use something like the MVVM design pattern and let your model objects vend the table view cells. You won't have to worry about the view hierarchy, but again: cells are recycled – you will end up managing stale or incorrect state.
One method I've used on static table view's that don't have any reuse and the cells are pre-generated, is create a subclassed tableView cell with a height property, so in heightForRowAtIndexPath you can get the cell for that indexPath and return the cell's height you hard-coded in that you wanted for the cell. That way, any time you want to change the height you can change it directly on the cell itself and when you reloadData it should update the cell height.
I am looking at the TableViewSuite example code from Apple. In Suite 5 - they have a UITableViewCell which has another UIView within it. That view is responsible for drawing the view. My question is about how it handles highlighting. In the UIView they have the following:
- (void)setHighlighted:(BOOL)lit {
// If highlighted state changes, need to redisplay.
if (highlighted != lit) {
highlighted = lit;
[self setNeedsDisplay];
}
}
My question is - how does this get called? I searched for highlight and there aren't many matches in the project. Does a UITableViewCell call setHighlighted on all of its subviews when it has setHighlighted called on itself? I'm assuming this is what is happening but can't find any documentation that states this.
I'm pretty sure the table view cell recurses into its subviews. I recall having a subview in a custom table cell that would highlight if the cell was highlighted.