UICollectionView rotation animation - ios

I have two different UIViewControllers, and both of them have UiCollectionView.
Problems appears when I try rotate iPad.
Next I have in portrait position
When I rotate to landscape I reload my collectionView, so in lanscape mode I need have 3 columns
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self.collectionView reloadData];
}
In first viewController all good, and when collectionView begin rotate controller still have 3 columns
But when I try do it in another controller, I get bad situation
It is looks like in one moment two collectionView in my view, and then one of them is disappearing.
I use standart UICollectionViewFlowLayout and have same methods for buils layout

Have you tried invoking the view..
(void)invalidateLayout
You can call this method at any time to update the layout information. This method invalidates the layout of the collection view itself and returns right away. Thus, you can call this method multiple times from the same block of code without triggering multiple layout updates. The actual layout update occurs during the next view layout update cycle.

Have you also tried this?
(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
[self.collectionView reloadData];
}

I think I have solution, try this which understands when animating the rotation;
(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:( NSTimeInterval)duration {
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self.collectionView reloadData];
}

Related

UiTableView header not disappearing

I have a UITableView configured as plain style so I can have the header views stuck on the top of the table until another header pulls it away.
The problem is: If I have a header stuck on the top of the screen, and I programmatically scroll to another part of the table (where that header should not appear at all), that UIView will not be dismissed. I.e. if I scroll again to that part of the table, a ghost of that header will be visible on that part of the table.
I've implemented the method - (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(nonnull UIView *)view forSection:(NSInteger)section to understand what is happening. I found that if I manually scroll until a header is pull away of the screen, this delegate is called. But if I scroll programmatically, the delegate is not called.
By the way, I tried scrolling programmatically using two different methods, and the problem is the same.
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated;
One workaround that I can imagine is implementing - (void)scrollViewDidScroll:(UIScrollView *)scrollView;, filtering all the header views that are outside the visible screen, and removing them from superview. I can probably make it work, but I would like to know if there is any other better solution.
[EDIT] If I call - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; with animated = YES, the bug does not happen. I can go with this solution, but I really would like in some cases to scroll without animation.
Not entirely sure I understand your issue entirely but it seems that your header view(s) (some UIView) is/are not rendered correctly once you programmatically scroll away from this area / section and then return.
I'm not sure how you are filling your header view content but I have several applications running UITableView's with multiple section headers that require updating for scrolling / content offset's with no problem, as long as you "draw" your headers with this delegate:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
// Per section, simply return the appropriate header view
...
NSString *someIdentifier = [NSString stringWithFormat:#"sectionHeaderView:<some #, letter, or tag>", <SOMETHING UNIQUE ADD HERE>];
UITableViewHeaderFooterView *myHeaderView = [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:someIdentifier];
if (!myHeaderView) {
// No header view found with that ID. Make a new one
}
...
return myHeaderViewForSection;
}
This way whether you finger scroll or programmatically set the content offset which ever way you like, your table view will know what to draw, when to draw it, and where to put it.
Using their delegates is a bit of a drag as it's slightly tedious at start, but using the viewForHeaderInSection proved the only way I ever obtained the results I (you) wanted.
Hope this helps - happy coding!
TL;DR
Do NOT explicitly scroll the table view between beginUpdates and endUpdates.
Explanation
I'm using NSFetchedResultsController to populate the table. These are my implementations for some of the methods of NSFetchedResultsControllerDelegate.
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[_conversationTableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[_conversationTableView endUpdates];
}
The problem is that endUpdates was making a chain of calls that ended calling my method [self scrollToBottom] (which was a very ugly code actually). This method, as the name says, calls - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; to scroll the table view to the bottom of the table.
The explicit scrolling of the table during a beginUpdates - endUpdates was the culprit of my whole problem.
Solution
Scrolling the table view only after finishing endUpdates.
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[_conversationTableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[_conversationTableView endUpdates];
[self scrollToBottom];
}
Side Note
This also fixed a problem where the table view was sometimes flickering when scrolling.
Manually set the sectionHeader height to 0 when it should not appear
tableView.sectionHeaderHeight = 0;

Proper way to invalidate collection view layout upon size change

I'm implementing a collection view whose items are sized based on the bounds of the collection view. Therefore when the size changes, due to rotating the device for example, I need to invalidate the layout so that the cells are resized to consider the new collection view bounds. I have done this via the viewWillTransitionToSize API.
This works well until the user presents a modal view controller over the view controller that contains the collection view, rotates the device, then dismisses it. When that occurs the item size hasn't updated to the appropriate size. viewWillTransitionToSize is called and the layout is invalidated as expected, but the collection view's bounds is still the old value. For example when rotating from portrait to landscape the collection view bounds value still has its height greater than the width. I'm not sure why that's the case, but I'm wondering if this is the best way to invalidate upon size change? Is there a better way to do it?
I have also tried subclassing UICollectionViewFlowLayout and overriding shouldInvalidateLayoutForBoundsChange to return YES, but for some reason this doesn't work even rotating without a modal presentation. It doesn't use the proper collection view bounds either.
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(nonnull id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[self.collectionView.collectionViewLayout invalidateLayout];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> __nonnull context) {
[self.collectionView.collectionViewLayout invalidateLayout];
} completion:^(id<UIViewControllerTransitionCoordinatorContext> __nonnull context) {
//finished
}];
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
//collectionView.bounds.size is not always correct here after invalidating layout as explained above
}
I've also tried invaliding it in the completion block but it still doesn't use the proper collection view bounds.
If I invalidate the layout again in viewWillAppear, this does use the proper collection view bounds which resolves the issue with rotating with the modally presented view controller. But this shouldn't be necessary, perhaps there are other situations where this won't be sized properly.
I know what the problem is. When you call invalidateLayout inside animateAlongsideTransition (either in the animation block or the completion block), it doesn't actually recalculate the layout if there is a modal view controller presented over full screen (but it will if it's over current context). But it will invalidate it if you invalidate it outside of the animation block like I was doing. At that time however the collection view hasn't laid out for the new size, which explains why I was seeing the old value for its bounds. The reason for this behavior is invalidateLayout does not immediately cause the layout to be recalculated - it is scheduled to occur during the next layout pass. This layout pass does not occur when there's a modally presented view controller over full screen. To force a layout pass, simply call [self.collectionView layoutIfNeeded]; immediately after [self.collectionView.collectionViewLayout invalidateLayout];, still within the animateAlongsideTransition block. This will cause the layout to be recalculated as expected.

UICollectionView unresponsive to swipe on the right w-h pixels in landscape mode

I have a UICollectionView that I am populating with several cells and a custom flow layout. This works great in portrait mode. When I rotate to landscape I encounter an issue where I can't swipe up/down on the right portion of the screen. It seems to be the right w-h pixels which are unresponsive to touch. The collection view does draw cells properly and everything else seems normal. Also, if I begin a swipe in the working zone and go diagonally into the unresponsive area, the drag continues to work.
When the device is rotated, I make the following calls:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
_currentIndex = self.assetsCollectionView.contentOffset.y / self.assetsCollectionView.bounds.size.height;
[self.assetsCollectionView.collectionViewLayout invalidateLayout];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
float y = _currentIndex * self.assetsCollectionView.bounds.size.height;
[self.assetsCollectionView setContentOffset:CGPointMake(0, y) animated:NO];
}
These look fine to me, and when I comment them out completely, I get the same behavior.
Other details: The UICollectionView is the only UI component in my View Controller, except for a small detail label which I'm sure is not the issue.
I'm switching between two subclasses of UICollectionViewFlowLayout so that the cells can expand to full screen and back, but I'm experiencing the same problem no matter which layout, or even before I swap them out. One more detail is that the fullscreen layout is pageEnabled while the smaller one is not.
The containing UIViewController is inside a UITabController.
One more note: I've double checked my layout constraints to ensure there isn't funny business there either.
Any ideas?
I had exactly the same problem. After spending an evening trying to figure out what was wrong I managed to solve it by adding these two lines in viewDidLoad method of the CollectionView view controller class. (I do not use autolayout)
self.view.autoresizesSubviews = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
I hope that this solves your issue too.

UICollectionView and UINavigationController not cooperating — cells occluded by nav controller on setting prompt

In my UIViewContoller's subclass, ViewWillAppear asks whether or not there's any data to present, and if there is, changes the UINavigationController's prompt accordingly. This triggers an animation as the prompt pops into view, causing the UINavigationBar to grow in size. When this happens it partially occludes the cells in the top row of the UICollectionView.
I have a vertical Auto Layout constraint of 0, seemingly pinning the UICollectionView to its nearest neighbor, which should be its superview, but the navbar still blocks the top halves of the cells. I've tried everything — telling the CollectionView to layout its subviews, reloading data, etc., but nothing seems to work. Any idea what's going wrong?
- (void)viewWillAppear:(BOOL)animated{
if(self.orderedURLSet.count == 0){
self.navigationItem.prompt = nil;
[self.collectionView setNeedsDisplay];
} else {
self.navigationItem.prompt = #"Tap photos to edit";
}
[self.collectionView reloadData];
[self.collectionView layoutSubviews];
}
Edit: What makes this even stranger is that when I rotate orientation the collectionViewCells aren't occluded, and the full cells remain visible when I rotate back to portrait orientation. Is there some way I can "trick" my app into thinking its layout has changed and it needs to reposition the cells? LayoutSubviews isn't doing the trick.
Edit: After digging a little more into the UIView documentation, it looks like setNeedsLayout and layoutIfNeeded are really the methods I should be using, and not layoutSubviews. I've tried calling both of them, on navigationController:didShowViewController:animated:, viewWillAppear, viewDidAppear, and viewDidLayoutSubviews to no avail.
Have you tried??
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}

UICollectionView displays cells incorrectly after frame change

I noticed that if I change the frame of a UICollectionView (e.g. when the toggling the in-call status bar), the collection view doesn't update its cells properly for its new frame. It's probably easiest to see in a short video:
http://cl.ly/2t2Y2A3A2w1D/CollectionViewTest.mov
The source files for that simple test are here:
http://cl.ly/0S0A360B3I3Q/CollectionViewTest.zip
It doesn't seem to matter whether I use UICollectionViewController or UIViewController. Has anyone seen this? Am I missing something? The only workaround I've found is to call reloadData on the collection view in the view controller's viewWillLayoutSubviews or viewDidLayoutSubviews which works but is far from ideal when the collection view's frame is being affected by a user drag since reloadData is called many times and results in very sluggish UI updates while the user is dragging.
I had a similar problem, where my UICollectionView's frame changed and unless I call reloadData it got stuck in the same spot without moving with the CollectionView.
I've fixed it by adding this to my CollectionViewFlowLayout:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
Worked nicely.
I had a similar problem. I just needed to resize the collection view frame so that a menu could show below it and the user could still scroll up far enough that the menu doesn't cover the cells at the bottom.
Changing the content inset worked for me.
self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, rect.size.height+8, 0);
I couldn't get changing the frame of the collection view to work without reloading the data, which would screw up cells that were already selected by the user.
You can have your UICollectionViewController register for the UIApplicationWillChangeStatusBarFrameNotification, and reloadData in the selector. That way, you'll only be reloading the data when the status bar changes frame.
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(callCameIn:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil];
}
-(void)callCameIn:(NSNotification *) aNote {
[self.collectionView reloadData];
}

Resources