Mirror scrolling of UICollection - ios

I have created a UICollection with a custom layout to allow for scrolling both vertical and horizontal. It is a grid of equal sections and items in each section (ie 10 x 10, 20 x 20, etc). I would like to be able to put two headers that remain in view, one along the top and one along the left side. I have not found a way to do this within the UICollection itself. So, I set up UICollection along the left and another along the top. However, as the user scrolls the grid left and right and/or up and down, I want these two collections to mirror those movements.
So, my question is: Is there a way to mirror the horizontal movement of the main UICollection to the top UICollection and then mirror the vertical movement of the main UICollection to the side UICollection?
Thanks!

UICollectionView is a subclass of UIScrollView. It sends its delegate all of the messages defined in the UIScrollViewDelegate protocol.
The message you want to respond to is scrollViewDidScroll:. When your main collection view sends this message, you want to respond to it by getting its contentOffset and applying the offset to your margin collection views as appropriate.
// Implement this in your main collection view's delegate.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self synchronizeCollectionViewContentOffsets];
}
- (void)synchronizeCollectionViewContentOffsets {
CGPoint offset = self.mainCollectionView.contentOffset;
self.leftMarginView.contentOffset = CGPointMake(0, offset.y);
self.topMarginView.contentOffset = CGPointMake(offset.x, 0);
}

So, I figured it out. Using self.mainCollectionView.contentOffset always returned (0,0), so tried to use the object name I actually assigned to it through the Storyboard and the view controller. This worked.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self synchronizeCollectionViewContentOffsets];
}
-(void)synchronizeCollectionViewContentOffsets {
CGPoint offset = myCollectionView.contentOffset;
myLeftMargin.contentOffset = CGPointMake(0, offset.y);
myTopMargin.contentOffset = CGPointMake(offset.x, 0);
}
Where myCollectionView, myLeftMargin and myTopMargin are linked to the UICollectionViews through the Storyboard.

Related

How can I cause UIScrollView to lock 2 out of 3 UIViews when scrolling?

I have an iPad app (XCode 4.6.3, iOS 6.2, ARC and Storyboards) which has the following structure on the bottom half of one of the scenes (all of the grids are UIViews, as is SubViewData).
The purpose is to have something that looks like a spreadsheet; I need to be able to scroll horizontally and the Left Grid will stay locked and if I scroll vertically, the Top Grid will stay locked.
This what it looks like now, without the scrolling (there is more to the right and also down):
UPDATE: This is the code that defines the UIViews:
I have looked in SO and Google and found no examples of this. Can someone please tell me what I need to change to get this code to work properly, or give me some good docs where I can get detailed information on contentOffset? (I have already read the UIScrollView, and it's no help!)
SOLVED - it's now working... this is how I got it to work, with the help of Fogmeister:
created a separate top row and left row UIView to hold the grid hours and staff names
embedded those new UIViews in UIScrollViews (Editor -> Embedd in scroll view)
followed the instructions from Fogmeister with regard to the code to make it happen.
This is the new structure:
And this is the code to make it happen:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint mainOffset = [scrollView contentOffset];
NSLog(#"\n\nmainOffset.x: %f\nmainOffset.y: %f", mainOffset.x, mainOffset.y);
// set the horizontal offset of the main view onto the column headers
[self.topGridSV setContentOffset:CGPointMake(mainOffset.x, 0)];
if(mainOffset.x < 0) {
[self.topGridSV setContentOffset:CGPointMake(0, 0)];
[self.schedScrollView setContentOffset:CGPointMake(0, 0)];
}
// set the vertical offset onto the row headers
[self.leftGridSV setContentOffset:CGPointMake(0, mainOffset.y)];
if(mainOffset.y < 0) {
[self.leftGridSV setContentOffset:CGPointMake(0, 0)];
[self.schedScrollView setContentOffset:CGPointMake(0, 0)];
}
OK, the way I'd do this is to move the "header" row and column into their own scroll view.
So you'll have a scrollview in the middle with the actual cells in.
Then you'll have a scroll view along the top that ONLY CONTAINS the column headers.
Then have a scroll view down the left that ONLY CONTAINS the row headers.
Now, make the "owning" view controller the delegate of the "main" scrollView with the cells in.
So you'll have...
UIScrollView *cellScrollView;
UIScrollView *columnHeaderScrollView; // along the top
UIScrollView *rowHeaderScrollView; // down the left
You will have to set the content accordingly. Obviously, you don't want the headers in the cellScrollView. etc...
Now, in the delegate method...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// scrollView here should always be == self.cellScrollView
// as this is where the delegate method is triggered from.
CGPoint *mainOffset = [scrollView contentOffset];
// set the horizontal offset of the main view onto the column headers
[self.columnHeaderScrollView setContentOffset:CGPointMake(mainOffset.x, 0)];
// set the vertical offset onto the row headers
[self.rowHeaderScrollView setContentOffset:CGPointMake(0, mainOffset.y)];
}
Something like this should easily get the effect you're after.
With one scroll view
You will have three sub views of the scroll view and references to these...
cellView
leftView
topView
These are all subclasses of UIView.
Initially you will have a layout of something like...
leftView frame == [0, 50, 80, some long height]
topView frame == [80, 0, some long width, 50]
cellView frame == [80, 50, some long width, some long height]
i.e. the cell view will be indented by the height of the top view and the width of the left view. (I hope this makes sense).
So in your scrollViewDidScroll...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// we will transform the position of the top view and left view using the offset.
// I chose a transform as it always acts from the original position.
// changing the view's frame will make it difficult to set it back again.
CGPoint offset = [scrollView contentOffset];
// move the left view to the left and right depending on the offset
leftView.transform = CGAffineTransformMakeTranslation(offset.x, 0);
// up and down is taken care of by the scroll view correctly.
// move the top view up and down depending on the offset
topView.transform = CGAffineTransformMakeTranslation(0, offset.y);
// left and right is taken care of by the scroll view correctly.
}
I chose to use transform as it make the calculations easier. Instead of trying to calculate the difference in position required each time, you just set the transform amount to the offset and it works.

Adjust a UIScrollView height based on a UITableView

I have a UIScrollView which contains a UIView and a UITableView. My goal is to adjust the height of the UIScrollView to allow me to scroll the contents of the UIScrollView to a specific point.
Here is my view: It has a UIView up top and a UITableView down below.
When I scroll, I want the UIView to stop at a specific point like so:
The tableView would be able to continue scrolling, but the UIView would be locked in place until the user scrolled up and brought the UIView back to its original state.
A prime example of what I am trying to do is the AppStore.app on iOS 6. When you view the details of the app, the filter bar for Details, Reviews and Related moves to the top of the screen and stops. I hope this all made sense.
Thanks
I ended up going with a simpler approach. can't believe I didn't see this before. I created two views, one for the UITableView's tableHeaderView and one for the viewForHeaderInSection. The view I wanted to remain visible at all times is placed in the viewForHeaderInSection method and the other view is placed in the tableHeaderView property. This is a much simpler approach, I think than using a scrollview. The only issue I have run into with this approach is all my UIView animations in these two views no longer animate.
Here is my code.
[self.tableView setTableHeaderView:self.headerView];
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return self.tableViewHeader;
}
add yourself as a UIScrollViewDelegate to the UITableView and implement the - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView so that if your views are in their starter positions they do this:
- your UITableView animates its size to the second state:
[UIView animateWithDuration:.1f animations:^{
CGRect theFrame = myView.frame;
theFrame.size.height += floatOfIncreasedHeight;
myView.frame = theFrame;
}];
- your UIView animates its vertical movement
[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionCurveLinear animations:^(void){
view.center = CGPointMake(view.center.x , view.center.y + floatOfVerticalMovement);
}completion:^(BOOL Finished){
view.center = CGPointMake(view.center.x , view.center.y - floatOfVerticalMovement);]
Finally always in the delegate implement – scrollViewDidScrollToTop: so that you know can animate back to the initial state (using the same techniques reversed).
UPDATE:
since your views are inside a scroll view, there is a simpler way if you are ok with the table view being partly out of bounds in your starter position (i.e. instead of changing size it just scrolls into view):
make the scroll view frame size as big as your final tableview + your initial (entire) view and place it at 0,0 (so its final part will be hidden outside of the screen)
scrollview.frame = CGRectMake(0,0,tableview.frame.size.width,tableview.frame.size.height + view.frame.size.height);
you make the container scrollview contents as big as the entire table view + the entire view + the amount of the view that you want out of the way when scrolling the table view.
scrollview.contentSize = CGSizeMake(scrollview.frame.size.width, tableview.frame.size.height + view.frame.size.height + floatOfViewHeightIWantOutOfTheWay);
you place the view one after the other in the scrollview leaving all the additional empty space after the table view
view.frame = CGRectMake(0,0,view.frame.size.width, view.frame.size.height);
tableview.frame = CGRectMake(0,view.frame.size.height, tableview.frame.size.width, tableview.frame.size.height);
now it should just work because since iOS 3 nested scrolling is supported
You can easily achieve this by setting the content size of the scrollView correctly and keep the height of the UITableView smaller than your viewcontroller's height, so that it fits the bottom part of the top UIView and the UITableView...
Another scenario is to split the top View in 2 parts.
The part that will scroll away and the part that will be visible.
Then set the part that will scroll away as the entire UITableView header and the part that will remain visible as the header view for the first table section.
So then you can achieve this with a single UITableView, without having to use a UIScrollView
What you're looking for is something like what Game Center happens to do with it's header which can actually be modelled with a table header, a custom section header view, and some very clever calculations that never actually involve messing with the frame and bounds of the table.
First, the easy part: faking a sticky view. That "view that's always present when scrolling the table" implemented as a section header. By making the number of sections in the table 1, and implementing -headerViewForSection:, it's possible to seamlessly make the view scroll with the tableview all for free (API-wise that is):
- (UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,50)];
label.text = #"Info that was always present when scrolling the UITableView";
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor colorWithRed:0.243 green:0.250 blue:0.253 alpha:1.000];
label.textColor = UIColor.whiteColor;
return label;
}
Finally, the hard part: KVO. When the table scrolls, we have to keep the header up there sticky with regards to the top of the view's frame, which means that you can KVO contentOffset, and use the resultant change in value to approximate the frame that the view should stick to with a little MIN() magic. Assuming your header is 44 pixels tall, the code below calculates the appropriate frame value:
CGPoint offset = [contentOffsetChange CGPointValue];
[self.tableView layoutSubviews];
self.tableView.tableHeaderView.frame = CGRectMake(0,MIN(0,offset.y),CGRectGetWidth(self.scrollView.frame),44);
If the above is infeasible, SMHeadedList actually has a fairly great, and little known, example of how complicated it can be to implement a "double tableview". That implementation has the added benefit of allowing the "header" tableview to scroll with the "main" tableview.
For future visitors, I've implemented a much simpler version, albeit one that accomplishes the goal with Reactive Cocoa, and a little bit of a different outcome. Even so, I believe it may be relevant.
What if you break the UIView into the top and bottom. The bottom will be the info.
Set UITableView.tableHeaderView = topView in viewDidLoad
and the return bottomView as Section Header in delegate method to make it float:
(UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section
{
return bottomView;
}
Just using the UITableView can solve with your problem. it is not need to use another scroll view.
set your view as the header view of UITableView. Then add your present view to the header view.
complete - (void)scrollViewDidScroll:(UIScrollView *)scrollView; . Tn the function to check the contentoffset of scroll view, and set the present view's frame.

Make UIView scroll with UITableView but pin to top out of view

I currently have a view controller that is comprised of a Navigation bar, followed by a UIView that has two UIButtons added as subViews. There is then a UITableView underneath that begins at the bottom of the container UIView.
At the moment, when the user scrolls the UITableView it goes behind the UIView and UIButtons. What I actually want to happen is for the UIView and UIButtons to move up with the table view but only by the value of their height which in this case is 58 pixels. The flow would be like this...
1) Table scrolls and the UIView moves with it for the first 58 pixels.
2) The user continues to scroll the table but the UIView "pins" itself just out of view under the navigation bar.
3) When the user scrolls the table back down the UIView is then picked up and dragged back into view. I believe the new Facebook app does something similar in the timeline.
I don't want to set the UIView as the TableHeaderView of the table as I also have a pull-to-refresh which then sits above the buttons and looks terrible. I've tried playing around with the contentOffset properties of the underlying scrollview of the table but have hit a brick wall.
Any advice on where to start would be appreciated.
Thanks
EDIT: I am gotten a little further and using this code to move the frame of the UIView.
-(void) scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog (#"Content Offset: %f", self.tableView.contentOffset.y);
NSLog (#"Button Frame: %f", self.btnBackground.frame.origin.y);
if (self.tableView.contentOffset.y > 0)
{
CGRect newFrame = self.btnBackground.frame;
newFrame.origin.x = 0;
newFrame.origin.y = -self.tableView.contentOffset.y;
[self.btnBackground setFrame: newFrame];
}
}
The problem now is that the scrollViewDidScroll delegate method doesn't get fired quickly enough if the table view is scrolled fast. The result is that the UIView doesn't quite make all way back to its original position when scroll quickly.
The scroll content offset is a good idea. Also if you tableview has only one section one approach is to do a custom header view representing the top level widgets. If there is more than one sections create an additional empty section which would return your custom header.
You can refer to this stack overflow post.
Customize UITableview Header Section
Well Asked Question (y)
well , for me i would first : use a main UIScrollView that contains both your topView and the tableView under it and that has the same width as your top UIView and UITableView and set its height to be height(tableView) + height(topView).
Second : since UITableView is a subClass of UISCrollView you can use scrollViewDidScroll delegate to know if the tableview is scrolled up or down.
in this cas you will have Two cases :
1) tableview is scrolled up = > you set the content offset of the main scrollView to be
[scrollView setContentOffset:CGPointMake(0, 58) animated:YES];
2) when the table view is scrolled down you can reset the content offset again
[scrollView setContentOffset:CGPointMake(0, 0) animated:YES];

UIScrollView with fixed contents inside

my question is:
In my app I want realize somethings like this using UIScrollView
A UIScrollView whit all his contents inside. But when I reached the top of the screen (or maybe the bottom of the screen) I want to anchor some contents like in the AppStore (iOS 6).
In this case the "Detail", "Review" and "Related" buttons stop scrolling and remains fixed at the top.
Anyone have any idea how to do?
Give your scroll view a delegate if it doesn't already have one. This would probably be your view controller.
Also give the delegate a reference to the floating view. (In the example, the floating view contains the “Dettagli”, “Recensioni”, and “Correlati” buttons, and the floating view is transparent where the triangular notch is, and the shadow is also part of the floating view.) The floating view must be a subview of the scroll view.
Save the “normal” Y coordinate of the floating view's frame in an instance variable. For example, if the delegate is a view controller, you should probably do it in viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
originalFloatingViewY = floatingView.frame.origin.y;
}
In the delegate's scrollViewDidScroll: method, check whether the scroll view has been scrolled below the top of the “normal” position of the floating view. If so, move the floating view to the top edge of the visible area:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self updateFloatingViewFrame];
}
- (void)updateFloatingViewFrame {
CGPoint contentOffset = scrollView.contentOffset;
// The floating view should be at its original position or at top of
// the visible area, whichever is lower.
CGFloat y = MAX(contentOffset.y, originalFloatingViewY);
CGRect frame = floatingView.frame;
if (y != frame.origin.y) {
frame.origin.y = y;
floatingView.frame = frame;
}
}
You could also do it by subclassing UIScrollView and overriding layoutSubviews. Watch the “Advanced ScrollView Techniques” video from WWDC 2011 for more information on this technique.
You need to handle this in the UIScrollView's delegate method( scrollViewDidScroll:) for scrolling. When the y of scrollview reaches that particular value you need to disable scrolling in this direction alone. You just need to add an if condition there to check and reset the y point while scrolling. For this you might have to keep this particular portion as a separate scrollview outside the below content and move it along with the below scroll view's upward movement.

How should I implement a custom grid on an iPad?

I'm designing an iPad app that will have a custom grid in it. The grid will display simple geometric shapes, in different colors, that contain a single character. These will update frequently based on user actions.
The grid will be larger than the screen size, in both directions, so will need to scroll/pan.
I also need the top row and first column to be "frozen" - so the top row remains at the top, but the content within it scrolls horizontally with the rest of the grid, and the first column remains at the left, but the content scrolls veritical with the rest of the grid.
The contents of the first column could be wide, so I'll need to allow the user to resize it.
I'm struggling with the best way to design this. I'm thinking that it might be easiest just to have it as a single custom view when I manage all the drawing and interaction manually. But I can't help feeling that I'm missing some easier way of doing it. Maybe there's even a suitable third party component that would be a better starting point.
What would be the best way of designing this component of the app?
I'd have 3 UIScrollViews that you can set the frames for apropriately so that they make the first column and first row the size you wish - the main grid contents can be the bottom right UIScrollView.
The controller class can be the UIScrollViewDelegate for all of the views. Set the contentsize of the 1st column UIScrolView to be the same width as the width of its frame, but height the right size for the content, the 1st row UIScrollView should have the same height as its frame, but the right width for the content, and the bottom left UIScrollView will have it's content size set as the size of teh grid contents.
The - (void) scrollViewDidScroll:(UIScrollView*)scrollView method is called when scrolling any one of them so check which one has scrolled and scroll teh other two as appropriate.
i.e.
//Declared and positioned somewhere earlier, like in the .h
UIScrollView* firstCol;
UIScrollView* firstRow;
UIScrollView* mainGrid;
- (void) scrollViewDidScroll:(UIScrollView*)scrollView {
if (scrollView == firstCol) {
CGPoint offset = mainGrid.contentOffset;
offset.y = firstCol.contentOffset.y
[mainGrid setContentOffset:offset];
}
else if (scrollView == firstRow) {
CGPoint offset = mainGrid.contentOffset;
offset.x = firstRow.contentOffset.x
[mainGrid setContentOffset:offset];
}
else if (scrollView == mainGrid) {
CGPoint offset = firstRow.contentOffset;
offset.x = mainGrid.contentOffset.x;
[firstRow setContentOffset:offset];
offset = firstCol.contentOffset;
offset.y = mainGrid.contentOffset.y;
[firstCol setContentOffset:offset];
}
}
UICollectionView does a pretty good job for it. But if you need a data grid, then look at this one. It is in alfa stage, but it will be quite a powerful in month or two, I would say. Any suggestions are welcome and it's free, of course.

Resources