Synchronized horizontal scrolling of collection views in tableview rows - ios

I'm trying to synchronize scrolling in all collection views within a table view (see image link below) :
Example Image :
http://postimg.org/image/dduhr89e5/
The sample that I have been able to find explains how you can sync two separate scroll views by identifying each. However I am unsure how to identify each collection view when they are in a table view. There could be 1 or hundreds that all need synchronized.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if ([scrollView isEqual: theFirstScrollView])
{
theSecondScrollView.contentOffset =
CGPointMake(theFirstScrollView.contentOffset.x, 0);
}
else
{
theFirstScrollView.contentOffset =
CGPointMake(theSecondScrollView.contentOffset.x, 0);
}
}

Assuming each collection view has its own cell and the collection views are similar widths (or at least you have that part figured out), here is the approach I would use:
1) Make your VC the delegate for all of the collection views.
2) When any of them scrolls, go through the TableViews visibleCells and set the content offset for the cells collectionView. Also, store the content offset in the viewController.
3) Whenever a new cell is dequeued, set the content offset to that last stored value in the VC.

Related

Create a UICollectionView that scrolls with another UICollectionView

I have already built out 2 collection views, 1 horizontally scrolling (at the top) & 1 vertically scrolling (at the middle - bottom) that are used to view 2 different sets of content in my Objective-C iOS application similar to this Instagram screenshot:
I am trying to add functionality to make it so that the horizontally scrolling Collection View disappears when the user scrolls up on the vertically scrolling one. What is the best way to accomplish this task? I have looked up tutorials on adding a collection view in another collection view's cell but I cant find anything on just adding a collection view to the 1st cell of another collection view. What would be the best way to accomplish this functionality?
I think you should you use UITableView with UICollectionView. On the screeshot, I think horizontal collectionview is embedded in first cell of the tableview. And when use starts to scroll tableview, first row is gone as you want.
Edit
Create uitableview with 2 prototype cells. Create horizontall collectionview and embed it in first cell of tableview, this is first prototype cell. Then create second prototype cell for images. And when user will start scroll the tableview first cell will gone, as you want.
If you don't want to go with the other suggested method of embedding the horizontal collection view into the top cell of the vertical collection view, you could use the vertical collection view's scrolling callbacks (scrollViewDidScroll, since UICollectionView subclasses UIScrollView). When the vertical collection view scrolls, you can apply a transform to the top collection view to move it off the top of the screen based on the contentOffset of the vertical collection view, and then have it reappear once the contentOffset approaches 0.
Keep in mind that with this approach, the vertical collection view's frame will likely be the height of the screen minus the height of the horizontal collection view. Therefore, you will need a bit of extra logic to expand the vertical collection view's frame to take up the whole screen once the horizontal collection view has disappeared from sight. Otherwise, you will have an awkward blank bar at the top of the screen where the horizontal collection view initially was while you scroll.
You have two scroll view lets call it "cvHorizontal" and "cvVertical".
You can manage scrollViewDidScroll method to hide cvHorizontal when scrolled up and show cvHorizontal when scrolled down.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (lastContentOffset > scrollView.contentOffset.y){
[self showCategory:YES];
lastContentOffset = scrollView.contentOffset.y;
} else if (lastContentOffset < scrollView.contentOffset.y) {
[self showCategory:NO];
lastContentOffset = scrollView.contentOffset.y;
}
}
-(void)showCategory:(BOOL)flag{
if(flag){
[UIView animateWithDuration:0.6 animations:^{
if(cvHorizontal.hidden ){
cvHorizontal.hidden=NO;
cvHorizontalHeight.constant=65.0f;//manage the cvHorizontal height and all the other constraints calculation if any
}
}];
}else {
[UIView animateWithDuration:0.6 animations:^{
cvHorizontal.hidden=YES;
cvHorizontalHeight.constant=0;
}];
}
}

Nested Scroll view vs Table View or something else for this requirement

I have a requirement in which I need to have the following functionality -
1)I have a custom segmented control. In order to implement paging to the segmented control I have used horizontal scroll view. Each page has its own vertical scroll view.
Requirement
1)The image should hide as user scrolls up in the respective pages and should show down when user scrolls down in respective pages but keeping the custom segment always at the top of the screen when image is hidden irrespective of the individual page selection-
What I have tried so far -
1st Method
I tried putting the image as header of a table view.
Created a single section with one cell & gave the section header as the custom segment. And in the cell I placed the horizontal scroll view with the cell's height adjusted to cover all portion left out of the superview but it didn't work out as when I scroll the vertical scrolling of individual pages it was not in sync with the table view.
2nd Method
I tried setting the segment initially with a fixed distance from the top & I increased & decreased the constraint inside scrollViewDidScroll(). But it too didn't work as when the user scrolled rapidly ,the changing of constraint value didn't follow correctly.
So is there any other way to achieve the same ?
Please suggest as I can't make out what to do?
You add a tableView and your UIImage on top of it inside a scrollView. The tableView must have the same height & width than your scrollView. Then you disable the pan gesture of the scrollView :
self.scrollView.panGesture.active = false
Then you have to implement a custom scroll in scrollViewDidScroll' of yourtableView`'s delegate:
func scrollViewDidScroll(scrollView: UIScrollView) {
if self.scrollView.contentOffset.y <= 100 {
self.scrollView.contentOffset.y += scrollView.contentOffset.y
self.tableView.contentOffset.y = 0
} else {
// let the tableView scroll normally
}
}
Or, you can have a try with https://github.com/bryankeller/BLKFlexibleHeightBar ;)
It's a great component that can handle many type on animation in the header based on the position of a scrollView.

iOS 7 - Layout advice

I have the following layout in my view controller. I want to be able to scroll vertically with the header scrolling off the view and the UISegmentedControl sticking to the top of the view, beyond that the remaining scroll should be handled by the Collection View.
However I'm a bit confused as to what is the best approach to implemented this layout.
I tried a few implementations with mixed results:
UIScrollView with UICollectionView as subviews: UIScrollView as the parent view with the header, segmented control and collection views as child controls. The problem with this approach is that the nested scrolling does not seem to work correctly. To be able to scroll the UIScrollView the tap needs to be outside the CollectionView area otherwise only the CollectionView scrolls and the header and segmented control don't move.
Header and Segmented Control in Header cell: I tried another approach by using a single CollectionView. I added the header and Segmented Control as subviews of a single Header cell of the collection view. When the segmented control value was changed, I switch the data source property of the CollectionView to achieve the 3 views required for the collection view. Visually everything works perfectly. The only problem here is the race condition when switching quickly between first,second and third tabs. I load the data from a web service, if the web service takes time and is still loading the data and I quickly switch the tabs then I run into bugs where the data returned is for a different collection view than what is currently selected, a lot of out of order sync issues.
Update constant value for Autolayout Constraint: Another approach I tried is to change the constant value of the auto layout constraint applied to "Header" view. Then I added a gesture to the view controller's view to track the scroll, as the user scrolls vertically I adjust the constant of the auto layout constraint so that the "header" cell pops out of view. Again this doesn't seem to work that smoothly, but I suppose I can tweak it, but it seems sort of a hack.
Is there a better way to implement this layout?
#2 seems like a good solution — the scrolling gestures will be most consistent with what users expect, since it's all a single scroll view. (I agree that #3 sounds like a hack.) You can make the header "sticky" with some custom layout attributes.
The only problem here is the race condition when switching quickly between first, second and third tabs.
This is a common problem with asynchronous loading when views are being switched out (especially when you are loading data into individual cells, which are being reused as you scroll). It is important that upon receiving the data you always check whether the receiver is still expecting it; i.e., you should check the segmented control value before changing the backing data source. You could also:
Use separate data source objects for the different segments, having each one manage its own data fetching so they can't get mixed up.
Cancel the outstanding requests, if you can, when quickly switching tabs, to avoid unnecessary network requests.
Cache data to avoid re-fetching every time you switch tabs.
I think you want the same functionality that pinterest profile page have. To implement such functionality at easy way, you need to do following things.
Step 1 : Add UIView as tableHeaderView those who showing off while scrolling up.
self.tableHeaderView = yourView
Step 2 : Add UISegmentControl in section header view.
- (UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section{
return your_segmentcontrolView;
}
Step 3 : Add UICollectionView into first row of first section.
By implementing following way, you can got your desire functionality.
Hope this help you.
An alternative approach you could consider:
Use a UITableView to contain your UI
Create a UITableView, and set your header as the UITableView's headerView.
Use a sectionHeader to contain the segmentedControl.
Place your collectionView inside of a single UITableViewCell. Or alternatively, you may be able to use the UITableView's footerView to contain the gridView.
By using the sectionHeader, this should allow the header to scroll out of view, but then the sectionHeader will stick below the navigationBar or top of the contentView until another section comes into view (and in your case you will only have one section.)
Add Header View, Body View (Holding Segment View & Collection View) into scroll view.
Initially set userInteractionEnabled property to "NO" for collection view.
Track the insect of scroll view always.
If the y-coordinate of the scrolled insect is more than the height of header view, then set userInteractionEnabled property to "YES" so that thereafter collection view can be scrolled.
If user scroll outside the scroll view and try to bring the header view down, i.e Scroll view y-coordinate insect is less than the height of header view, then immediately change the user iteration mode of collection view and allow user to scroll the scroll view till the top.
Rather than implementing this by hand, you could use a library/cocoapod to set this up for you. This one looks like a pretty good fit: https://github.com/iosengineer/BMFloatingHeaderCollectionViewLayout
Plus, the code is open-source, so you can always modify as needed.
All I can say is that you need to subclass UIView and make it a delegate of UIGestureRecognizerDelegate and UICollectionViewDelegate, then in your UIView subclass, do the following, I can't give out anymore information on this because the code, although owned by myself, is proprietary to the point of probably enraging quite a few organizations that I've used this for, so here's the secret sauce:
CGPoint contentOffset = [scrollView contentOffset];
CGFloat newHeight = [_headerView maxHeight] - contentOffset.y;
CGRect frame = [_headerView frame];
if (newHeight > [_headerView maxHeight]) {
frame.origin.y = contentOffset.y;
frame.size.height = [_headerView maxHeight];
[_headerView setFrame:frame];
} else if (newHeight < [_headerView minHeight]) {
frame.origin.y = contentOffset.y;
frame.size.height = [_headerView minHeight];
[_headerView setFrame:frame];
} else {
frame.origin.y = contentOffset.y;
frame.size.height = newHeight;
[_headerView setFrame:frame];
}
if ([_delegate respondsToSelector:#selector(scrollViewDidScroll:)]) {
return [_delegate scrollViewDidScroll:scrollView];
}
You must subclass another UIView that is defined as the header for this custom UiCollectionView. Then, you must declare the UIView custom header view inside the custom subview of the UIView/UICollectionView delegate, and then set the header of that custom subview inside the UICollctionViewdelegate. You should then pull in this compounded subclass of UIView/UIcollectionView into your UIViewController. Oh yes, and in your layoutSubViews, make sure you do the height calculations that are passed through a double layered subclass. So, you will have the following files:
UIVew this is the delegate of UICollectionView and what I mentioned before
UIView this is a UISCrollViewDelegate and this is the header view
UIViewController that pulls in the subclassed UIView in number 1
UIView subclass of number 1 that pulls in number 2 and sets it as its header
In the number 4 part, make sure you do something like this:
- (CGFloat)maxHeight
{
if (SCREEN_WIDTH == 414)
{
return 260;
}else if (SCREEN_WIDTH == 375)
{
return 325;
}else
{
return 290;
}
}
- (CGFloat)minHeight
{
if (SCREEN_WIDTH == 414)
{
return 90;
}else if (SCREEN_WIDTH == 375)
{
return 325;
}else
{
return 290;
}
}
This will then pass through to the UIView subclass that is a compounded subclass as I already explained. The idea is to capture the maxHeight of you header in the subclass of this header UIView (number 2 above), and then pass this into the main UIView subclass that intercepts these values in the scrollViewDidScroll.
Last tidbit of information, make sure you set up your layoutSubviews in all methods to intercept scroll events. For example in number 1 above, the layoutsubviews method is this:
- (void)layoutSubviews
{
CGRect frame = [_headerView frame];
frame.size.width = [self frame].size.width;
[_headerView setFrame:frame];
[super layoutSubviews];
}
This is all I can give you, I wish I could post more, but this should give you an idea of how it's done in production environments for the big time apps you see out in the wild.
One more thing to note. When you start going down the road of intense implementations like this, don't be surprised to learn that, for example, a single view controller in an app that works with methods like I've explained will have anywhere from 30-40 custom subclasses that are either subclasses in their own right or compounded subclasses or subclasses of my own subclasses or my own subclasses. I'm telling you this so you get an idea of how much code is required to get this right, not to scare you, but to let you know that it might take a while to get right, and to not kick yourself in the butt if it takes awhile to make work. Good luck!!

Scroll second UITableView in line with second UITableView

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.

Determining position of tableHeaderView

I am making a custom header view on my tableview. The custom view has a gradient on it.
I only want that gradient to show if it is the only header visible.
So if a user is scrolling and happens to see two sections of the tableview, the second section on the tableview should not have a gradient.
What is the best approach to do this?
Here are some thoughts:
Perhaps in your table's delegate, you can cache the header views, and every time one is requested, check it's peers to see if they are on screen (determined by UIView's .window property being non-nil).
- (UIView *)tableView:tableView viewForHeaderInSection:section {
if([_headerViews objectAtIndex:section-1].window || [_headerViews objectAtIndex:section+1].window) {
// there are peers on screen
} else {
// this is the only one onscreen
}
}
This is just sample code, and does not ensure that the views are properly initialized, etc. just an idea.

Resources