Animating all UICollectionViewCells in a UICollectionView - ios

I was wondering what a good way to animate all the cells in a UICollectionView is. I'm trying to simulate editing in a UICollectionView. So what I want to do is shrink all the bounds of the UICollectionViewCells. So what I have is this:
- (IBAction)startEditingMode:(id)sender {
[_items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:0];
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
[UIView animateWithDuration:0.25 animations:^{
cell.layer.transform = CATransform3DMakeScale(0.9, 0.9, 1);
}];
}];
}
It works, but I wasn't sure if there was a property on UICollectionView, or a better more standard way to do something like this. Thanks.

I would create a UICollectionViewLayout subclass. Add a BOOL property called editing. When editing changes, call invalidateLayout. Then in the attribute returned by the -layoutAttributesForItemAtIndexPath: method you can specify a transform.
The problem with your approach is that it affects only the visible cells. The UICollectionViewLayout subclass is nice because it will apply the transform to all of the cells even as new ones get added. And it moves all of the collection view layout handling out of the view controller.
Cell attributes can include frame, size, center, transform(3D), alpha, and your own custom attributes.
You'll change the value of editing in a -performBatchUpdates: block, as suggested by wL_.
- (IBAction)startEditingMode:(id)sender {
[self.collectionView performBatchUpdates:^{
((MyCollectionViewLayout *)self.collectionView.collectionViewLayout).editing = YES;
}
completion:NULL];
}
And in the UICollectionViewLayout subclass:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
if (self.editing) {
attributes.transform = CGAffineTransformMakeScale(0.9, 0.9);
}
else {
attributes.transform = CGAffineTransformIdentity;
}
return attributes;
}
Also note you (probably) don't need a 3D transform here. An affine transform is sufficient.

Have you tried using UICollectionView performBatchUpdates:?
Something like:
[collectionView performBatchUpdates:^{
[_items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:0];
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
cell.layer.transform = CATransform3DMakeScale(0.9, 0.9, 1);
}];
} completion:^{}];

Related

UICollectionview custom flow layout delete cell

I have a horizontal UICollectionView that implements UICollectionViewLayout and uses targetContentOffsetForProposedContentOffset for a custom fling/paging functionality. Each cell is nearly the width of the screen with the next cell showing slightly to the right. The functionality works great, but I want to be able to delete the cell at index 0 when the current cell is index 1. I'm currently able to calculate and do this just fine, but upon deleting the index 0 cell it slides to the next cell (old index 2, new 1 (after deleting 0)) because of the current content offset. I'm not sure how I can delete index 0 while maintaining the current layout.
Right now in my delegate I'm doing:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
float cellWidth = self.collectionView.bounds.size.width - 20;
float currentPage = self.collectionView.contentOffset.x / cellWidth;
if (currentPage == 1) {
[self remove:0];
}
}
-(void)remove:(int)i {
[self.collectionView performBatchUpdates:^{
[self.data removeObjectAtIndex:0];
NSIndexPath *indexPath =[NSIndexPath indexPathForRow:0 inSection:0];
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
} completion:^(BOOL finished) {
}];
}
So the calculation and deletion works fine, but upon deleting [0], the collection view is scrolled to the next cell..and I'm not sure how to stop it.
I've tried self.collectionview.contentOffset = CGMakePoint(0,0) after the deleting, but the transition is still noticeably buggy. Any ideas on how to approach this problem?
so after a ton of trial and error I was able to resolve the issue. It might be a little hackish but I just wrapped the batch with a uiview animation that has no duration. Basically it overrides the deletion animation
[UIView animateWithDuration:0 animations:^{
[self.collectionView performBatchUpdates:^{
[scrollView setContentOffset:CGPointMake(0, 0) animated:NO];
[self.data removeObjectAtIndex:0];
NSIndexPath *indexPath =[NSIndexPath indexPathForRow:0 inSection:0];
[self.collectionView deleteItemsAtIndexPaths:#[indexPath]];
[self.collectionView.collectionViewLayout invalidateLayout];
} completion:nil];
}];

Add cell at bottom and scroll UITableView

I want to add cells at the bottom of a UITableView, and scroll to make it fully visible (just like in Whatsapp when you send or receive a message).
I've tried doing this:
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:[self.tableView numberOfRowsInSection:0]-1 inSection:0] atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
But this makes a graphical glitch and the table flashes and make a bad scrolling instead of a smooth one.
Any help with this?
here what I have done is, inserted a row and then changed the content inset of the tableView
-(IBOutlet)sendButton:(id)sender
{
[array addObject:textView.text];
[self addAnotherRow];
}
- (void)addAnotherRow {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:(array.count - 1) inSection:0];
[self.messagesTableView beginUpdates];
[self.messagesTableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.messagesTableView endUpdates];
[self updateContentInsetForTableView:self.messagesTableView animated:YES];
// TODO: if the scroll offset was at the bottom it can be scrolled down (allow user to scroll up and not override them)
[self.messagesTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
- (void)updateContentInsetForTableView:(UITableView *)tableView animated:(BOOL)animated {
NSUInteger lastRow = [self tableView:tableView numberOfRowsInSection:0];
NSLog(#"last row: %lu", (unsigned long)lastRow);
NSLog(#"items count: %lu", (unsigned long)array.count);
NSUInteger lastIndex = lastRow > 0 ? lastRow - 1 : 0;
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:lastIndex inSection:0];
CGRect lastCellFrame = [self.messagesTableView rectForRowAtIndexPath:lastIndexPath];
// top inset = table view height - top position of last cell - last cell height
CGFloat topInset = MAX(CGRectGetHeight(self.messagesTableView.frame) - lastCellFrame.origin.y - CGRectGetHeight(lastCellFrame), 0);
// What about this way? (Did not work when tested)
// CGFloat topInset = MAX(CGRectGetHeight(self.tableView.frame) - self.tableView.contentSize.height, 0);
NSLog(#"top inset: %f", topInset);
UIEdgeInsets contentInset = tableView.contentInset;
contentInset.top = topInset;
NSLog(#"inset: %f, %f : %f, %f", contentInset.top, contentInset.bottom, contentInset.left, contentInset.right);
NSLog(#"table height: %f", CGRectGetHeight(self.messagesTableView.frame));
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState;
[UIView animateWithDuration:animated ? 0.25 : 0.0 delay:0.0 options:options animations:^{
tableView.contentInset = contentInset;
} completion:^(BOOL finished) {
}];
}
Note: to view existing messages from array, you have to include this
also.
- (void)viewDidLayoutSubviews {
[self updateContentInsetForTableView:self.messagesTableView animated:NO];
}
example function to add an item:
- (IBAction)addItem:(UIBarButtonItem *)sender {
[self.items addObject:[NSString stringWithFormat:#"Item %lu", self.items.count + 1]];
NSIndexPath *indexPathToInsert = [NSIndexPath indexPathForRow:self.items.count - 1 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPathToInsert] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView scrollToRowAtIndexPath:indexPathToInsert atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
for me the animation looks perfectly fine this way!
Well, what I did is use [self.tableView scrollToRowAtIndexPath... but calling it from a 0.1s NSTimer.
Make insertions animated and after endUpdates scroll bottom not animating. Worked for me
Swift
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths(newIndexPaths, withRowAnimation: UITableViewRowAnimation.Top)
self.tableView.endUpdates()
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: self.tableDataCount-1, inSection: 0), atScrollPosition: .Bottom, animated: false)

How can I programatically select a tableview cell?

when I create a custom UITableviewCell I set cell.selected = YES in the UITableView delegate method tableview:cellForRowAtIndexPath, but it doesn't work.
However, when I call [tableView selectRowAtindexPath:] after [tableview reloadData], there is no problem. I am confused
Here is some code source:
-(void)setModel:(GAInterestSetModel *)model
{
_model = model;
if (model.iconURL && model.iconURL.length > 1) {
[iconView setImageWithURL:[NSURL URLWithString:model.iconURL]];
}
if (model.title && model.title.length > 1) {
titleLabel.text = model.title;
}
if (model.memberCounts) {
membersLabel.text = [NSString stringWithFormat:#"成员:%d",model.memberCounts];
}
self.selected = model.defaultChoosed;
}
instead method:
[modelsArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
tempModel = (GAInterestSetModel *)obj;
if (tempModel.defaultChoosed) {
[rootTableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:idx inSection:0] animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}];
The selection is not a property of the cell. Calling .selected = YES does absolutely nothing because the table doesn't know about the selection.
You have to use [table selectRowAtIndexPath:...] otherwise it won't work.
Another possibility is to ignore the default selection and implement your own selection mechanism.

Springboard-like animation for deleting UICollectionViewCells

I am trying to figure out a way to animate the deletion of a UICollectionViewCell similarly to springboard in iOS 7. The deleted cell should shrink into nothingness and the remaining cells should slide over to fill its place (the sliding is the important part). I have tried:
• Setting the frame of each cell to the frame of the previous cell, like so:
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
CGRect rect = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:i - 1 inSection:0]].frame;
[UIView animateWithDuration:0.2 animations:^{
cell.frame = rect;
}];
But this did not work, as the cells did not move at all.
• Both of the following:
[self.collectionView performBatchUpdates:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
} completion:nil];
and
[UIView animateWithDuration:0.2 animations:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
}];
But these make the cells crossfade to their required spots, not slide.
I also found this project, which uses the following:
for (int i = index+1; i<[items count]; i++) {
SEMenuItem *item = [items objectAtIndex:i];
[UIView animateWithDuration:0.2 animations:^{
if (i < index + remainingNumberOfItemsInPage) {
int intVal = item.frame.origin.x;
if (intVal %3== 0)
[item setFrame:CGRectMake(item.frame.origin.x+2*item.frame.size.width, item.frame.origin.y-item.frame.size.height-5, item.frame.size.width, item.frame.size.height)];
else
[item setFrame:CGRectMake(item.frame.origin.x-item.frame.size.width, item.frame.origin.y, item.frame.size.width, item.frame.size.height)];
}
[item updateTag:item.tag-1];
}];
}
However, it is not in a UICollectionView, so it does not help me.
Note: I am doing this on an iPad, just in case it matters to you.
Any ideas?
Subclass UICollectionViewFlowLayout. Implement:
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attr = [self layoutAttributesForItemAtIndexPath:indexPath]; // might need to replace self with super
attr.transform = CGAffineTransformMakeScale(0, 0);
return attr;
}
In addition to duci9y's answer, (using attr.transform = CGAffineTransformMakeScale(0,0);), the following makes the tiles slide. I can't believe I didn't think of this before.
[self.collectionView performBatchUpdates:^{
[self.collectionView deleteItemsAtIndexPaths:#[indexPath]];
[dataSource removeObjectAtIndex:indexPath.row];
} completion:nil];

UICOllectionview, scrolling to first cell that contains in its title an NSString

I have a uicollectionview hosting a number of cells, each cell has a title on it.
the same collectionview can have multiple cells that have the same title.
I need to grammatically scroll the collectionview to the first occurrence of a cell which has title = user selection.
How can I achieve that?
Loop through you data source and find the first object and call then call collection views scrolltoIndexPath method.
__block NSIndexPath* indexPath = nil;
[yourDataSource enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isEqualToString:title]) {
indexPath = [NSIndexPath indexPathForRow:idx inSection:0];
*stop = YES;
}
}];
if (indexPath) {
//set the scroll position accordingly
[collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:YES];
}

Resources