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];
}
Related
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.
I'm racking my head over this one, I know this has to be possible but after going over every bit of documentation I still can't come up with something that works well.
Basically, I have a UICollectionView in the LAST cell of a UITableView. What I want to happen is, only when the UITableView is totally scrolled to the bottom, can the UICollectionView in it's last cell start scrolling. And, if the TableView offset reaches the bottom of the CollectionView's tableviewcell during a drag / pan, any additional dragging of the current table view drag / pan should effect the collection view instead of the table view.
Also, when the collection view is scrolling, if a user starts scrolling on the collection view, if the collection view reaches the top of it's scroll (Content Y offset of 0 or less), and additional scrolling of the current pan/drag gesture in affect should cause the containing table view to scroll up.
The reason I want to achieve this effect, is because the table view cell above the last cell containing the collection view, contains a UISegmentControl that toggles the contents of the UICollectionView, and I want the user to be able to toggle this segment at any time while scrolling in the CollectionView. Meaning the collection view has to scroll but the parent table view needs to not scroll..
I've tried playing with the gesture recognizers, and doing something with
– gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
This gets me halfway there, I can use a flag to return yes/no to this method if the collection view is to scroll into a negative offset (Past it's top offset) or if the table view has reached the end of it's total scroll.. I get sort of close to what I want to achieve, but if I scroll up slowly on the collection view, it doesn't fire the simultaneous recognizer, same happens if I scroll down on the table view too slowly.
Another issue is, I do NOT want the collection view to bounce. However setting bounces to NO totally prevents the simultaneous recognizer to fire at all. I even tried setting content offset to CGPointZero on the collection view in it's viewDidScroll if it's y offset were to dip below zero. This also doesn't work and prevents the simultaneous method from firing...
Does anyone have any idea what to do? Or something to point me in the right direction?
UPDATE -
Still trying at this, I've made little progress towards the behavior I'm trying to achieve. I've messed with toggling userInteractionEnabled in the viewDidScroll method, as have I tried in willBeginDragging. The same with scrollEnabled property.. No luck :( I get a behavior similar to what I want with this, however the parent view will not scroll up until the user lets off the screen and attempts to scroll again..
UPDATE -
Is there anyway to transition the panGestureRecognizer currently handling scroll events DURING scrolling? If I could transition the scroll handler from the child to the parent while still scrolling this would solve my issue. I've looked through apple's gesture related and uiscrollview related documentation and can't find anything close to doing that.
UPDATE -
Just got done trying something like this..
- (CGPoint)maxParentContentOffset
{
return CGPointMake(0, self.parentScrollView.contentSize.height - self.frame.size.height - 44);
}
- (void)parentScrollViewDidScroll:(UIScrollView *)parentScrollView
{
if (self.contentOffset.y > 0) {
self.parentScrollView.contentOffset = [self maxParentContentOffset];
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.y < 0) {
scrollView.contentOffset = CGPointZero;
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
CGPoint translation = [scrollView.panGestureRecognizer velocityInView:scrollView.superview];
if (translation.y < 0) {
[UIView animateWithDuration:0.5f animations:^(void) {
self.parentScrollView.contentOffset = [self maxParentContentOffset];
}];
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
However there is a problem... for some reason I keep getting a bad access error on the following method
- (void)parentScrollViewDidScroll:(UIScrollView *)parentScrollView
{
if (self.contentOffset.y > 0) {
self.parentScrollView.contentOffset = [self maxParentContentOffset];
}
}
Specifically setting the content offset. Which is really strange, because with breakpoints I'm seeing parentScrollView and self as being set. I'm wondering if it's not a bad access but it's getting trapped in an infinite loop for some reason? Any ideas?
Even though you may manage to make it work now, embedding a collection view inside a table view (both UIScrollView subclasses) is not a good idea and it will bug as soon as apple modifies their implementation.
Try to migrate to a single UICollectionView layout. After all there's nothing you can't achieve with a collection view that a table view can.
Separate your "table view" and "collection view" in two (or more) collection view sections, then implement layoutAttributesForItemAtIndexPath: differently according to the indexPath.section.
To make it table view-like you'll want to return frames whose width are the same as the collection view.
If your layout is simpler then you could use a UICollectionViewFlowLayout (maybe your already are) and implement collectionView:layout:sizeForItemAtIndexPath: as described above.
Figured this out after a good 8 hours.. I had a confliction due to infinite setting of the parent scroll view offset, since I had multiple objects that were of the same class that received a call whenever their parent view scrolled, both trying to set the same parent view offset to zero, which caused the other class to see scroll changing, and calling their method to change offset, and the process happening infinitely causing a crash.
This code however, solved everything and functions exactly as I was desiring. Hopefully this helps anyone else in the future looking to get congruent scrolling between a parent and child scroll view.
- (CGPoint)maxParentContentOffset
{
return CGPointMake(0, self.parentScrollView.contentSize.height - self.frame.size.height - 44);
}
- (void)parentScrollViewDidScroll:(UIScrollView *)parentScrollView
{
if (self.contentOffset.y > 0 && self.isDragging) {
self.parentScrollView.contentOffset = [self maxParentContentOffset];
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.y < 0) {
scrollView.contentOffset = CGPointZero;
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
CGPoint translation = [scrollView.panGestureRecognizer velocityInView:scrollView.superview];
if (translation.y < 0) {
[UIView animateWithDuration:0.5f animations:^(void) {
self.parentScrollView.contentOffset = [self maxParentContentOffset];
}];
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
In an application that I've worked, we needed something close to that, we wanted to have a uitableview inside the first cell of another uitableview, which should be scrollable (you can check the app here)
Our approach was quite different, and we thought about doing it your way, but that way was a bit overkill for the time we had, and needed too much custom control instead of using what apple already gives us.
I'll share our method just for you to have another way to solve your problem, since I can't figure out what is wrong in that method call.
On the outer tableview's datasource lifecycle, we were able to know how our inner table view would be, allowing us to feed our inner table view with all the data, which would then gave us the total content size of the inner table view.
With this info, and since we knew (and in your case, you know) the actual cell where the inner table view would be placed, we make that cell's height equal to the height of the inner tableview content size. With this, the inner table view is 'totally visible'(in your case, the collection view) inside the outer table view cell, but since the cell is so big, it looks like we are scrolling the inner table view when we are simply scrolling a really big, special, cell.
And it won't bounce, if you don't want it too.
In our case, this worked, and the implementation was pretty straight forward.
In your case, and since you have a UISegmentedController, you should do a beginUpdates endUpdates and a 'reloadData', upon press in the UISegmenedController, in your outer table view in order to be able to recalculate the collection view's content size and resize the last cell, but that is pretty straight forward.
I know I'm not answering your question, but this method allowed us to achieve something so similar in such a fast and straight forward way that I thought it was worth sharing.
Hope it helps.
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.
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];
}
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;
}