UICollectionView animating reusable cells? - ios

I've got a UICollectionView that is formatted as a single, horizontally scrollable row across the screen. It represents the next-up entries in a queue, so whenever items are added and deleted, it always happens at Index 0 (the far left of the View) and then the whole thing moves forward or back by one space. It's super-simple in the code, just:
[_collectionviewNextUp insertItemsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]]];
and
[_collectionviewNextUp deleteItemsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]]];
Everything is working properly, except the animations for insertItemsAtIndexPaths: and deleteItemsAtIndexPaths:, specifically when the CollectionView items extend beyond the edge of the screen. As long as there are only 6 items in the queue (the number that can be seen at once), it animates perfectly; but 7 items or more and the animation gets weird. Delete an item (from the left) and it also animates one being added to the right; add an item (on the left) and it also animates one being deleted from the right. Even if the queue is 50 items long, it always animates item #6 (the last visible one onscreen) disappearing and reappearing. There are no errors or warnings or anything, and I'm left with the correct number of items in the View after the animation is over, it just animates a cell being added/deleted at the edge of the screen.
My theory is that the reusable cells are being animated. Does that sound like it could be a thing? Scrolling left and right works perfectly, but inserting and deleting seems to only animate in pairs, and they always "fly in" and "fly out" in the same direction, as though one is taking the other's place. It looks (to my inexperienced eye) like the cell on the end is being flown over and added to the front, or the one on the front is being moved over to the end.
I've tried to do research on this but all I can find are animation problems that throw errors and exceptions, which mine doesn't do. Has anyone else experienced this? And can anyone offer a fix, wherein the Index 0 item gets animated and the rest just smoothly slide onto and off of the screen?

I assume you're using flow layout. Flow layout is very buggy animating items across the layoutAttributesForElementsInRect: boundary (i.e. items moving in and out of view). In my experience, you can fix some of these issues by overriding the 'initialLayoutAttributesForAppearingItem' & finalLayoutAttributesForDisappearingItemAtIndexPath: methods and correcting the frames when the super class returns incorrect values. Here is a successful answer to a very similar issue.
I open sourced a simple uniform grid layout VCollectionViewGridLayout that greatly improves animations over what UICollectionViewFlowLayout provides. However, it currently only does vertical scrolling. But if you went in there and transposed all of the horizontal and vertical calculations, I'm sure it would solve your issue.

Related

How to tell the UITableView to bounce into place programatically?

My goal is pretty straightforward:
The user should be able to move cells in the UITableView up and down by using the provided buttons. I want the new selected cell to appear exactly where the old selected cell was with the exception of the scrolling being at the the beginning of end of the tableview.
I tried using
[table selectRowAtIndexPath:newIndexPath animated:false scrollPosition:UITableViewScrollPositionMiddle];
But the problem is that the row is always moved to the same position near the center of the screen which I don't want.
I want to center the
table.contentOffset.y
exactly where I am programmatically placing it. However, when I set this with
[table setContentOffset:CGPointMake(0, newVerticalContentOffset)];
so I did exactly that. Now I have the problem of the scrolling going way outside the the table and even the normal bounce range. The only way to get it to 'bounce' back is if the user scroll touches the list.
I also tried programmatically setting the offset to the 'min' and 'max' of it happened to go out of those bounds. However, I realized that this was complicated because I have to add the height of the navigation bar and the status bar to get the top coordinates and get the content size to determine the bottom. I think it would be easier if there was simply a way to programmatically 'bounce' or 'release' the tableview back into place. I tried calling the table's delegate with flags like
scrollViewDidScroll
This crashes my application. I realized that even if I did perform one of these last two solutions, that the action would still not represent my goal. If I move a cell down from the top, the view scrolls with it. I do not want that to happen because it is very close to the top. In this case, I wouldn't mind it making its way to the middle before scrolling the view.
The best idea I have so far is somehow attempting to center scroll the cell and if it is able to be centered (meaning it is not near the top or bottom), not to actually center it.
I thought about performing a centering then checking if the y coordinate changed by exactly 1 cell and leaving it if it did, otherwise using the old position. However, I imagine that the user may be somewhere near the middle of the list and happens to be exactly 1 cell away from the middle. When he moves the cell, it will move 'unexpectedly' which is not what I want.

Problems animating UICollectionView bounds with a nonzero contentOffset

Sigh. Second question in two days. I'm starting to not like UICollectionView quite so much.
Ok. I have a UICollectionView set up, working nicely, and a custom layout object managing the... er... layout. I'd post code but there's tons of it and I'm not certain which parts would be relevant. Mostly I just followed this tutorial: http://skeuo.com/uicollectionview-custom-layout-tutorial
Now herein lies the problem. If I scroll my collection view away from contentOffset 0,0, then cause its bounds to animate, the start location of any cells that were already visible and are remaining visible is a long way out! They arrive at the right place, but start about 60 pixels too far left and too far down. Always the same amount each time no matter how far I've scrolled from 0,0. The weird thing is, so long as I don't move away from 0,0 it all animates exactly as I want it to, no problems whatsoever. Somehow the contentOffset appears to be interfering with the current frame of my content cells, and in a completely nonsensical way.
It's really bizarre and I can't work out why it's happening. Anyone got a clue?
[EDIT] a little more information. As an experiment, I changed the layout style of my collection view so that it only changed width rather than height when it resized. Now it appears that the starting Y positions are correct, so it could well be an unusual bug involving autoresizing. For the record, I have checked my various cells and they are all set to top/left, fixed width and height, so that's not the problem.
[EDIT] And more information. It seems that, when the bounds changes, the layout view is asked if it should invalidate. Returning YES is the only way to animate the bounds change. However, what THEN happens is that the collectionView immediately sets its own bounds a second time to send the current offset back to zero/zero. The problem may be that if I do both, it gets confused, but if I only invalidate on the first call it gets the wrong information, whereas if I don't invalidate on the first, I don't get the second at all. Chicken/Egg.

Animating the offset of the scrollView in a UICollectionView/UITableView causes prematurely disappearing cells

We have a UICollectionView with a custom layout very similar to UITableView (it scrolls vertically). The UICollectionView displays only 3 cells simultaneously, with one of them being the currently active cell:
[ 1 ]
[*2*]
[ 3 ]
(The active cell here is #2.) The cells are roughly 280 points high, so only the active cell is fully visible on the screen. The user doesn't directly scroll the view to navigate, instead, she swipes the active cell horizontally to advance to the next cell. We then do some fancy animations and scroll the UICollectionView so the next cell is in the "active" position, thus making it the active one, moving the old one away and bringing up the next cell in the queue:
[ 2 ]
[*3*]
[ 4 ]
The problem here is setting the UICollectionView's offset. We currently set it in a UIView animation block (self.collectionView.contentOffset = targetOffset;) along with three other animating properties, which mostly works great, but causes the first cell (the previously active one, in the latter case, #2) to vanish as soon as the animation starts running, even before the delay interval completes. This is definitely not ideal.
I've thought of some solutions, but can't figure out the best one:
Absurdly enlarge the UICollectionView's frame to fit five cells instead of three, thus forcing it to keep the cells in memory even if they are offscreen. I've tried this and it works, but it sounds like an awfully dirty hack.
Take a snapshot of the rendered content of the vanishing cell, put it in a UIImageView, add the UIImageView as a subview of the scrollView just before the cell goes away in the exact same position of the old cell, removing it once the animation ends. Sounds less sucky than the previous option (memory-wise, at least), but still kinda hacky. I also don't know the best way to accomplish this, please point me in the right direction.
Switch to UIScrollView's setContentOffset:animated:. We actually used to have this, and it fixed the disappearing cell issue, but running this in parallel with the other UIView animations apparently competes for the attention of the main thread, thus creating a terribly choppy animation on single-core devices (iPhone 3GS/4). It also doesn't allow us to change the duration or easing of the animation, so it feels out of sync with the rest. Still an option if we can find a way to make it work in harmony with the UIView block animations.
Switch to UICollectionView's scrollToItemAtIndexPath:atScrollPosition:animated:. Haven't tried this, but it has a big downside: it only takes 3 possible constants (that apply to this case, at least) for the target scroll position: UICollectionViewScrollPositionTop, UICollectionViewScrollPositionCenteredVertically and UICollectionViewScrollPositionBottom. The active cell could vary its height, but it always has to be 35 points from the top of the window, and these options don't provide enough control to accomplish the design. It could also potentially be just as problematic as 3.1. Still an option because there might be a way to go around the scroll position thing that I don't know of, and it might not have the same issue with the main thread, which seems unlikely.
Any help will be greatly appreciated. Please ask if you need clarification. Thanks a lot!
I went with #2. Here's the snippet that does the rendering and storing:
UIGraphicsBeginImageContext(theCell.bounds.size);
[theCell.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *renderedCellImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The rest of the method was pretty much as I described above, with one caveat: this has to be done before the image is actually used and on a background thread, because rendering can take up to 1 full second on an iPhone 4. Hope this helps somebody.
I got the cells to stay visible by reloading them immediately after setting the content offset in the animation block.
UIView.animateWithDuration(0.3) {
self.collectionView.contentOffset = newOffset
self.collectionView.reloadItemsAtIndexPaths(willDisappearIndexPaths)
}
LongShot: Try to set the UIView animation block options to UIViewAnimationOptionBeginFromCurrentState.
[UIView animateWithDuration:1
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{ }
completion:^(BOOL completed){ } ];

Fast custom animation using CADisplayLink causes stroboscopic effect. How to avoid it?

Scenario:
Horizontally scrolling UIScrollView with reused page views (so that there are only few page viewcontrollers which are being reused similar way like UITableView cells). So that they are able to be updated with new content and reused I need to know exact position of UIScrollView's content view (offset). This works great.
Now I need to implement custom scrolling animation - I mean to programatically move the content view, so that user touches some buttons, and the scroll view scrolls to desired position with this custom animation. The movement can be very quick and very far. I can not use Core Animation for this, as I could not track position during animation (CA reports you only start and end of the movement). So I decided to use CADisplayLink, and calculate each UIScrollView content for each position. This works great too.
The only problem is, that sometimes I see stroboscopic effect - say I am moving the content to the right, and it looks like it is moving to left. If I look at builtin animation within UISCrollView using setContentOffset:animated:, the animation is smooth and nice. Does anyone know, how to get rid of this stroboscopic effect?
Most likely your issue is that timestamp is a double and you're assigning it to a float
Floats have 7 digits while Doubles have 15-16 digits.
So in other words you're experiencing data loss. By using double you should see a silky smooth animation.

UITableView negative top content inset causes glitch when reordering rows

I have a UITableView with a negative top content inset (-20). I use the negative inset for a shadow effect: The table header is a shadow gradient. The negative inset causes the shadow to only appear when the user is dragging the table down (similar to the Alarms table in the Clock app).
This works fine. Until I start reordering the rows (by standard UITableView reordering mechanism).
The instant I grab the first row, the whole table jumps down, so that the bottom edge of the last row is aligned to the bottom of the screen. This is a really weird glitch. When I release the row (at the same or some other position), the table stays in this strange, misaligned state. When I do some scrolling, the table returns to its original position.
This also happens for the other rows if I drag them to the top row. Reordering below the top row works fine. When I change the content inset to zero (or a positive number), the glitch does not occur.
It really seems like a bug in iOS (5.0) - unless I am missing something? Any idea how to work around this?
I'm also experiencing this exact issue in iOS 6.
I couldn't find a fix, but there's a workaround which might work if your table only has a single section. Basically, instead of setting the content inset to a negative value, you can set a negative section header height. This seems to have the same effect as the negative inset -- nudging the entire contents of the scroll area up -- without the glitches during reordering.
So instead of:
[tableView setContentInset:UIEdgeInsetsMake(-16, 0, 0, 0)];
Try:
[tableView setSectionHeaderHeight:-16.0];
This of course won't play well if you have content in section headers (or multiple sections in your table), but for single-section tables it seems to be a passable workaround.
One quick update since I don't have enough rep to comment yet:
The original bug this is trying to work-around appears to be fixed on iOS 7. I wound up wrapping my fix in a version-checking conditional for iOS <7 devices.

Resources