UICollectionView scrollToItemAtIndexPath: and get fresh cell position - ios

I would like to scroll collection view to some offscreen cell without animation and get position of that cell after scrolling. The problem is that collection view scrolls correctly (visually) but cell frame remains offscreen. Here is my code:
NSIndexPath *path = [fetchedResultsController indexPathForObject:objectInCollection];
[collectionView scrollToItemAtIndexPath:path atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO];
[collectionView layoutSubviews]; // or 'layoutIfNeeded', doesn't matter
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:path];
cellFrame = [cell.superview convertRect:cell.frame toView:nil]; // still not refreshed
I can propose that collection view applies scrolling not immediately, so I would like to find approach to apply the scrolling.
--- UPDATE ---
The collection view is in previous view controller (so I scroll it from code and then pop visible view controller).
Cell frame remains offscreen even in viewDidAppear: method of previous view controller. To be exact, [cell.superview convertRect:cell.frame toView:nil]; returns frame without contentOffset of collection view. So CGRectOffset([cell.superview convertRect:cell.frame toView:nil], 0, -collectionView.contentOffset.y) returns correct cell frame on screen.

As i can understand from your problem statement, you want to set the some cell which is out of the view to visible state.
The cells are placed at the same position when the UICollectionView calculates
(void)prepareLayout
(void)layoutAttributes.. methods.
The frames of the collection cells are fixed till the invalidateLayout or reloaddata is called. Just you have to work around with the contentOffset of the collectionview.
Just get the frame of cell and set the contentOffset so the your frame of cell is visible on the screen. Hope it helps.

The cell frame should be correct and I don't think you need to use convertRect at all.
cellFrame = cell.frame;
However, you probably have to do this after the collection view loads. Call [collectionView reloadData] first.
There is also a layoutAttributesForItemAtIndexPath: method that has the attributes for that cell. You can call this on the indexPath and get the attributes that way.

Try changing scroll position to UICollectionViewScrollPositionBottom, this works for me -
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionBottom animated:NO];

Related

UITableViewCell needs reloadData() to resize to correct height

I have the a custom tableview cell with applied constraints but the first time the table is displayed the row height is not resized properly unless new cells are created, is there a way to do this without calling reloadData again?
Yes. This is actually an issue with self-sizing that you need to work around until it is fixed.
The problem is that when a cell is instantiated, its initial width is based on the storyboard width. Since this is different from the tableView width, the initial layout incorrectly determines how many lines the content actually would require.
This is why the content isn't sized properly the first time, but appears correctly once you (reload the data, or) scroll the cell off-screen, then on-screen.
You can work around this by ensuring the cell's width matches the tableView width. Your initial layout will then be correct, eliminating the need to reload the tableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
[cell adjustSizeToMatchWidth:CGRectGetWidth(self.tableView.frame)];
[self configureCell:cell forRowAtIndexPath:indexPath];
return cell;
}
In TableViewCell.m:
- (void)adjustSizeToMatchWidth:(CGFloat)width
{
// Workaround for visible cells not laid out properly since their layout was
// based on a different (initial) width from the tableView.
CGRect rect = self.frame;
rect.size.width = width;
self.frame = rect;
// Workaround for initial cell height less than auto layout required height.
rect = self.contentView.bounds;
rect.size.height = 99999.0;
rect.size.width = 99999.0;
self.contentView.bounds = rect;
}
I'd also recommend checking out smileyborg's excellent answer about self-sizing cells, along with his sample code. It's what tipped me off to the solution, when I bumped into the same issue you are having.
Update:
configureCell:forRowAtIndexPath: is an approach Apple uses in its sample code. When you have more than one tableViewController, it is common to subclass it, and break out the controller-specific cellForRowAtIndexPath: code within each view controller. The superclass handles the common code (such as dequeuing cells) then calls the subclass so it can configure the cell's views (which would vary from controller to controller). If you're not using subclassing, just replace that line with the specific code to set your cell's (custom) properties:
cell.textLabel.text = ...;
cell.detailTextLabel.text = ...;

Insert cell at indexPath, not working correctly

I have a custom UITableViewCell with objects in it (such as UILabel, UITextView, UITextField etc.). When a button gets selected, a cell gets added to the tableView.
When I run it on the simulator, and the cell gets added, all the visible cell's and subviews height get really compact. (I do have auto constraint applied.)
....
[[self myTableView] insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationTop];
If I do the following, the cells get back to normal:
NSArray* visibleCellIndex = self.myTableView.indexPathsForVisibleItems;
[self.myTableView reloadRowsAtIndexPaths:visibleCellIndex withRowAnimation:UITableViewRowAnimationFade];
[self.myTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:savedScrollPosition inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
The problem with reloading the visible cells, is: First, that's a workaround, not getting to the source of the problem. Second, it's not a fully functioning workaround, because the whole tableView scrolls all the way up for a second, then scroll back to position.
The reason why it was shrinking, is because, you have to implement the method of heightForRowAtIndexPath.
The only problem now, is that the tableView jumps up, then scrolls to position. Don't know why.
Does your target run only on iOS 8 and later? If yes, you can set self.tableView.rowHeight = UITableViewAutomaticDimension to enable Autolayout for your cells. Then, you also don't need to implement delegate tableView:heightForRowAtIndexPath:.
If you're already doing this, your problem probably lies in your custom cell. Maybe its constraints are not well defined? How do you initialize the cell's constraints?
Another idea is to trigger the layout pass manually in tableView:cellForRowAtIndexPath:. After the cell has been initialized and its text label values have been set, call:
[cell setNeedsLayout];
[cell layoutIfNeeded];

I need help animating UICollectionView cell size when collection view size changes

I have a UICollectionView that takes up the entire screen, and inside it are cells that are the same exact size as the collection view. When the user hits a button, the collection view animates to take up only half the screen (and another view is shown beside it). This is fine, but the cells themselves are still the size of the original collection view.
I can call invalidateLayout on the collection view's layout, and this will cause collectionView:layout:sizeForItemAtIndexPath: to be called and the cells are all recalcualted, and the sizes are changes, but these do not animate, they simply snap to the new size.
Is there a way to animate these cell size changes at the same time, or in the same animation block that animates the size change to the parent collection view?
[_collectionView performBatchUpdates:^{
for (int i =0; i<count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
UICollectionViewCell *cell = [_collectionView cellForItemAtIndexPath:indexPath];
cell.layer.transform = CATransform3DMakeScale(0.9, 0.9, 1);
}
} completion:nil];

How to track movement of a cell in UICollectionView after screen rotation

I have an UICollectionView. When I touch one of the cells I present a popover view from its location with the arrow pointing to the cell with some extra information about the cell.
When I rotate the device, the UICollectionView automatically repositions its cells.
What I'd like to do, is to reposition my popover view automatically so it points to the same cell as before (which is now at a different location in the UICollectionView)
What I'm having trouble is tracking (finding out) what is my cell's new location, so I can manually represent the popover view from the cell's new location.
I tried attaching and storing an "idString" for the cell for comparison, but this for some reason returns the cell's old frame from before the screen rotation
NSArray* visibleCells = [UIAppDelegate.ocollectionView visibleCells];
for (UICollectionViewCell *cell in visibleCells) {
if ([cell.idString isEqualToString:self.idString] ) {
NSLog (#"we have a match!!! %#", cell);
CGRect rectInCollectionView = cell.frame;
rect = [UIAppDelegate.collectionView convertRect:rectInCollectionView toView:[UIAppDelegate.collectionView superview]];
}
}
any ideas appreciated. thank you.
Where is this code being placed? I guess it should be placed on a delegate method like this kind:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
So the UICollectionView has redrawn its new cell's position.

Animate Height Change for UITableViewCell Without Scrolling

I know how to animate the height change of a UITableViewCell using the method seen here: Can you animate a height change on a UITableViewCell when selected?
However, using that method, the UITableView will scroll at the same time, which I don't want it to do.
I have a UITableView with very few cells; it takes up less than the screen height. The bottom cell has a UITextField and, when it starts editing, I manually set the content offset of the UITableView so that the cell with the UITextField is scrolled to the top. Then, based on the input in the UITextField, I may want to increase the size of the UITableViewCell to show extra options, more or less.
The problem is that, when animating this change, it will reposition the UITableView so that my UITextField is no longer at the top.
This is what I'm doing, more or less:
self.customAmountCellSize = height;
[self.tableView beginUpdates];
[self.tableView endUpdates];
I have also tried
self.customAmountCellSize = height;
CGPoint originalOffset = self.tableView.contentOffset;
[self.tableView beginUpdates];
[self.tableView endUpdates];
[self.tableView setContentOffset:originalOffset animated:NO];
I want the row height animation, I do not want the UITableView to scroll as a result.
Any ideas?
It appears the problem you're encountering is that your table view is scrolled past the bottom so when you update its content it will attempt to fix that.
There are two approaches you could take to prevent scrolling:
Set the table view's content inset to be the height of the initial white space:
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, verticalGap, 0);
Add an empty footer view with the same height as the vertical gap:
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, verticalGap)];
In both cases, you will need to calculate the vertical space you are trying to achieve. You then need to restore the contentInset or tableFooterView to its original state when you are done.
I think the table view is scrolling because your text field is becoming the first responder, and not because of the cell height change. Try keeping the cell height the same and just adjusting the offset to be sure.
If I am correct, than here's the solution: UITableView automatically tries to scroll when your keyboard appears. To fix this, set the content offset to your desired offset in a dispatch to the main queue, which will fire at the beginning of the next runloop. Put the following code inside your response to a UIKeyboardWillShowNotification, or in a UITextFieldDelegate shouldBeginEditing method:
// Get the current offset prior to the keyboard animation
CGPoint currentOffset = self.tableView.contentOffset;
UIEdgeInsets currentInsets = self.tableView.contentInset;
__weak SomeTableViewControllerClass *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animationWithDuration:0 animations:{
// set the content offset back to what it was
weakSelf.tableView.contentOffset = currentOffset;
weakSelf.tableView.contentInset = currentInsets;
} completion:nil];
});
Similar fixes are sometimes necessary for the contentInset.bottom of a table view, depending on the frame of your UITableView and other factors.

Resources