UITableView header view animation - ios

I'd like to replicate the effect found in the Kickstarter app where, when the user scrolls up and the tableview is already at the beginning, the header view increases in size.
To see the effect, just open a random project and scroll up.
Do you know how to achieve such animation?

You can set delegate for you table view and use method scrollViewDidScroll:, UITableViewDelegate is sub-protocol of UIScrollViewDelegate, so you can just use:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentOffset.y < 0) {
//you have to store image you want to scale somewhere (in ivar for example - _image)
//k for scaling
CGFloat k = fabs(scrollView.contentOffset.y)/10;
_image.transform = CGAffineTransformMakeScale(k, k);
}
}

Related

How to animate views according to tableview scrolling directions without content scrolling

i'm trying to implement a specific tableview behaviour (Like on Facebook app).
I want to have a dynamic header that will be magnified every time the user scrolls up and will be shrieked when the user scroll down.
In addition i want the tableview to cause the effect of pushing the header and than scrolling the tableview cells.
I used the method:
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView
in this method i calculated the offset and the direction and called a method that shrink or magnify the header accordingly
so far so good.
The thing is that the animation is being performed with the tableview scrolling.
To avoid it, I created a custom scrollview on to of the top of my tableview, I taged the two scrollviews differently.
In the scrollview i created a weak reference of the tableview and a boolean value that indicated if the scrollview should return the tableview touch.
When the shrinking\magnifying animation was finished i changed the boolean value so it will signal the custom scrollview to return the tableview in my HitTest methods that i implemented inside the scrollview.
But hitTest not called when the user keep scrolling (without leafing the finger), in additions now my buttons inside my tableViewCell aren't reacting.
Here is my HitTest Method:
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
if (_recieveTouchOnTable)
{
return _table;
}
else
return result;
}
Here is my scrollViewDidScroll method:
(onProgress means that the animation is being performed, so keep returning the custom scrollview)
Tag = 2 = the custom scrollview
Tag = 1 = the tableview
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
CGFloat yVelocity = [aScrollView.panGestureRecognizer velocityInView:aScrollView].y;
CGFloat offset = lastCustomScrollViewContentOffset.y-aScrollView.contentOffset.y;
lastCustomScrollViewContentOffset =aScrollView.contentOffset;
if (yVelocity<0)
offset = fabs(offset)*-1;
else if(yVelocity>0)
offset = fabs(offset);
if (offset!=0 && aScrollView.tag == 2)
[self layoutViewAccorrdingToTableviewScorlingVelocity:offset];
if (!onProgress ){
customScrollView.recieveTouchOnTable=YES;
}
}
Am i missing something, or maybe there's a more simple way to do it?

Prevent UITableView scrolling below a certain point

How can I have a UITableView that permits scrolling above a certain index row, but prevents it when it below a certain point? For example, if I have rows 1 through 100, where only 5 appear in the view at a given time, I want to allow a user to scroll among rows 1-50, but prevent any further scrolling down when row 50 is visible.
You can use the property contentInset of UITableView just for that. Just remember to set them with minus values, cause we are shrinking the content size:
CGFloat insetTop = topAllowedRow * c_CELL_HEIGHT * -1;
CGFloat insetBottom = bottomAllowedRow * c_CELL_HEIGHT * -1;
[self.tableView setContentInset:UIEdgeInsetsMake(insetTop, 0, insetBottom, 0)];
The other solution is to implement UIScrollViewDelegate method scrollViewDidScroll: and when scrollView.contentOffset is too huge, set it back to the max value that user can scroll to:
- (void)scrollViewDidScroll:(UIScrollView*)scrollView {
CGPoint scrollViewOffset = scrollView.contentOffset;
if (scrollViewOffset.x > MAX_VALUE) {
scrollViewOffset.x = MAX_VALUE;
}
[scrollView setContentOffset:scrollViewOffset];
}
First solution has both the advantage and disadvantage since then UIScrollView will manage the bouncing (just like in pull to refresh). It's more natural and HIG-compatible, but if you really need user not to see below the certain row, use delegate method.
UITableView is a subclass of UIScrollView. This means you can use all methods from UIScrollViewDelegate. Try adding the scrollView.contentOffset logic in the following delegate method:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;

UIrefresh Control on UIcollectionView at the top and bottom

I want to add paging in my collectionView. I am using Waterfall layout in my collection View. Having two rows only.
Scrolling is horizontal. I want to add paging in such a way that user can go back and forth the pages.
I found one of that over Git by Simon Westerlund. But in this UIrefreshCOntrol is added at the top only. I want to ahev UIrefresh control at the bottom too. So that I can show paging Effect. Just as, its in the case of Facebook app
I think you can use bounce effect of UICollectionView coz its already inherited from UIScrollView and use following delegate method
You just have to add following lines and call your method instead of [self fetchMoreEnteries]
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
float endScrolling = scrollView.contentOffset.y + scrollView.frame.size.height;
if (endScrolling >= scrollView.contentSize.height)
{
NSLog(#"Scroll End Called");
[self fetchMoreEntries];
}
}

UICollectionView Within Last UITableViewCell AND Congruent Scrolling? How?

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.

ScrollviewDidScroll not called at each point

There is a button at the bottom of my view controller. When the user scrolls down the button has to be attached to the scrollview at certain height.
I need to attach a button to the scrollview, immediately when the contentOffset.y reaches a particular value. -(void) scrollviewDidScroll doesn't help me as there might be a jump in contentOffset when the user is scrolling fast. Any leads on this are helpful.
Also, whenever I add a subview to the scrollview, -(void) viewDidLayoutSubviews is called. Which in turn sets the contentOffset to {0,0}. How can I achieve the functionality I need?
I needed to do the same thing with a UITableView and for me using scrollViewDidScroll worked.
I created a view called staticBar and added it as a subview of the tableView, but I had to rearrange the tableview subviews for it to appear in the right place. I don't have my code in front of me, but in -scrollViewDidScroll: it looked something like this:
- (void)scrollViewDidScroll:(UIScrollView*)scrollView
{
CGFloat staticBarAdjustedY = _staticBarY - scrollView.contentOffset.y;
CGFloat scrollViewYFloor = scrollView.frame.size.height - _staticBar.frame.size.height;
// This way maximum Y the view can have is at the base of the scrollView
CGFloat newY = MIN( staticBarAdjustedY, scrollViewYFloor);
_staticBar.frame = (CGRect){ { _staticBar.frame.origin.x, newY}, _staticBar.frame.size}
}
I will check my code later today and add more details here.
Also, you said the scrollviewDidScroll has jumps in contentOffset, but it's worth mentioning that these jumps are the same that the scrollView uses to scroll its own view. So it's not like you are "losing" frames on this delegate method.
Hope it helps.
PS: So, here is the rest of my code.
//I place my custom view as a subview of the tableView below it's last subview
//The last subview is for scroll indicators.
WTButtonsBar *buttonBar = [[WTButtonsBar alloc] init];
[self.tableView insertSubview:buttonBar belowSubview:self.tableView.subviews.lastObject];
In scrollViewDidScroll:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//In my app I needed my view to stick to the top of the screen
//thats why I use MAX here
//self.buttonsBarOriginalY is the view's position in the scrollView when it isn't attached to the top.
CGFloat newY = MAX(scrollView.contentOffset.y, self.buttonsBarOriginalY)
[_buttonsBar setFrame:(CGRect){{0, newY}, _buttonsBar.frame.size}];
}

Resources