im atempting to do a facebook type load more data and right now it works, but very laggy on the device because its asking the server to get anything that isnt there already, then calling a scroll all the way down function (because somehow when i reload the data it scrolls to the top). If there would be a way to prevent scrolling to the top that would be great. But my main thing is, is there a way to detect when i scrolled down, and LET GO (stopped scrolling) as in i scrolled past what i have, then it went back to its possition and then calls my methods... Currently it keeps getting called when the scroll is greater then the height of the UITableView heres the code
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat height = scrollView.frame.size.height;
CGFloat contentYoffset = scrollView.contentOffset.y;
CGFloat distanceFromBottom = scrollView.contentSize.height - contentYoffset;
if(distanceFromBottom < height)
{
[getMessage removeAllObjects];
[self loadMessages];
[self.tableView reloadData];
[self scrollAllTheWayDown];
}
}
UIScrollViewDelegate method
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
does what you want. it gets called after user stops scrolling, and you can check for scrollOffset at that point to see if you should trigger your refresh code. (you'd use scrollViewDidScroll to update the view to show user update will happen if he lets go)
Related
I have a child scroll view contained within a parent scroll view (not a direct child). What I want is for the parent scroll view to start scrolling in the same direction as soon as the child scroll view reaches the end of its content.
This kind of works out of the box, but not really. Right now I have to lift my finger to make the parent scroll view start scrolling after the child has reached the end.
Any thoughts on this?
EDIT:
An example of what I'm looking for can be seen in Snapchat by swiping right in a table view cell to reveal the chat controller.
Not sure if I correctly understand what does not being a direct child mean. But iOS should do it automatically for you as soon as you reached the end of scroll of child view it should let you scrolling parent scroll.
For this you need to implement for the child:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate: (BOOL)decelerate{
scrollView.bounces = NO;
}
scrollView.bounces = NO does not let scrollView to jump back on end scroll. So it's just stick at the position it stops scrolling.
And then you need to trigger parent scrolling from the scrollViewDidScroll method for the child.
Something similar to:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat scrollViewHeight = scrollView.frame.size.height;
CGFloat scrollContentSizeHeight = scrollView.contentSize.height;
CGFloat scrollOffset = scrollView.contentOffset.y;
CGFloat offset = scrollContentSizeHeight - (scrollOffset + scrollViewHeight);
if (offset <= 0)
[self.parentView setContentOffset:CGPointMake(self.parentView.contentOffset.x, self.parentView.contentOffset.y + diffOffset) animated:YES];
}
Where diffOffset is the distance you want the parentScroll to scroll.
You may prefer more advanced workaround.
For example you can implement
scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
so you know what is the velocity of the scroll, comparing targetContentOffset to scrollContentSizeHeight and scrollViewHeight should let you know that this particular scroll is about to end dragging reaches the end of scroll view content.
And you can calculate diffOffset and animationTime more accurately.
It could be nice to block scrolling for child view when it's not fully visible within parent scroll, so the scrolling ability appears only when you scroll up until certain position.
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}];
}
How would you go about implementing a UICollectionView with this behaviour?
The idea is that once a user navigates past a certain point, they cannot go back and view those cells again.
My attempt at a solution has been to listen for gestures on the collection view and if disable scrolling once a swipe occurs on the element. The obvious problem with this is that the user can simple hold and drag any particular cell.
Any thoughts?
I think this behavior may be confusing for your users.
Maybe you should try to add some elasticity/bouncing so that your users would be less confused.
Anyway, I see two different ways to achieve this without subclassing
1/ Since UICollectionViewDelegate conforms to UIScrollViewDelegate, you can get the starting offset of your scrollview with – scrollViewWillBeginDragging: then in – scrollViewDidScroll: you would compare the new offset's x value. If the new offset.x 's value is smaller than the starting one, set it to 0 and update your scrollview.
#pragma mark - UIScrollViewDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
offset = scrollView.contentOffset;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint newOffset = scrollView.contentOffset;
if (newOffset.x < offset.x) {
// scrolling to the left, reset offset
[scrollView setContentOffset:offset];
}
}
Because there is inertia with scrolling in iOS, scrollViewDidScroll: is called a lot of time, so it may cause performance issues. You may reduce the number of call by targeting your offset with scrollViewWillEndDragging:withVelocity:targetContentOffset: from UIScrollViewDelegate.
2/ Or ou can just use the method scrollViewWillEndDragging:withVelocity:targetContentOffset: which I just spoke about, which sets the offset back to its beginning, with an animation.
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
CGPoint newOffset = scrollView.contentOffset;
if (newOffset.x < offset.x) {
// scrolling to the left, reset offset with animation
targetContentOffset->x = offset.x;
}
}
3/ You spoke of UISwipeGestureRecognizer, did you give a try to UIPanGestureRecognizer? This is what "simple hold and drag" is.
You can implement it the same way you would implement an infinite scroll view, by adding / removing items based on the scroll offset.
Override the viewDidScroll: method (UICollectionViewDelegate)
Check if your offset puts the first object of your list past the offset (ie. can you still see it on the screen?)
If it is, then remove it from the collection view.
This simple implementation might result in choppy animations, you might have to do some optimization once it's working but this should get you started.
Another possible solution would be to "reposition" all your elements constantly to appear where they were before you started scrolling if you are scrolling left.
You can achieve this by keeping track of the highest offsetX you ever encountered, and reposition your cells if the current offsetX is lower than the max. That way you will have the impression that your cells are not moving or that you can't scroll, but you will actually be scrolling.
In my case, I have a paginated collection view, so I have to take care when decelerating in the last item of the collection view as well in automatic decelerations for each page.
To fix the issue, I just disable user interaction while the collection view is "moving automatically".
Here the code:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (!_isDragging) // <-- only do things if the user is dragging!
return;
CGPoint contentOffset = scrollView.contentOffset;
// If we move to the left
if (contentOffset.x < _contentOffset.x)
{
CGSize contentSize = scrollView.contentSize;
// If content offset is moving inside contentSize:
if ((contentOffset.x + scrollView.bounds.size.width) < contentSize.width)
scrollView.contentOffset= _contentOffset;
}
else
{
// Update the current content offset
_contentOffset = contentOffset;
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
_isDragging = YES;
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
_isDragging = NO;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (decelerate)
{
// If willDecelerate, stop user interaction!
_collectionView.userInteractionEnabled = NO;
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
// Once deceleration is finished, enbale user interaction
_collectionView.userInteractionEnabled = YES;
// Set the new content offset
_contentOffset = scrollView.contentOffset;
}
I have a UITableview that is populated by a REST web service. I have bouncing enabled so that I can trigger a call to the server with
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
Using this method the call gets triggered when the user lifts their finger after trying to scroll past the bottom of the tableView. The view then bounces back and the new cells are added. The problem I have is that it is not obvious anything was added. The tableView bounces back to the bottom of the screen and it isn't until the user tries to scroll again that the new cells become visible. Here is the method I use to trigger the call.
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
NSInteger currentOffset = scrollView.contentOffset.y;
NSInteger maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;
if (maximumOffset - currentOffset <= -40)
{
if (scrollView == _feedTableView)
{
//This method has a call to the server and adds the new cells to the table
[self loadFeed:feedRefreshControl];
}
}
}
I tried turning off bouncing after the trigger in an attempt to get the bottom cell to remain where it was when the user lifts their finger but the last cell just shoots back to the bottom in an non-animated way.
Any ideas would be appreciated!
In the past, I've used a fake last cell which held a spinner. As the user scrolls down to the end of the list, they see the spinner cell as the last cell. When more data is added the spinner cell gets replaced with the first cell of the new data. This is an obvious visual clue to the user that more information has been loaded into the list. I also flashed the status bar, again just a visual cue to the user that the data has been updated.
I am working with aUIScrollview, and based on if the user scrolls left or right, I reconfigure the UI. The problem is that I need to verify that the user definitely crossed from one screen to another (something along contentOffset).
I've tried using this method:
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
But this will fire if the user kind of moves the scrollview partially in one direction, but then doesn't complete the gesture.
I've also tried this method:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView;
but mainly the same issue; the scrollview scrolls left and right, but on an iPhone, with a content with of 640 ( 320 * 2). I am trying to figure out if the scroll did cross over or not, from one location to the other.
Any suggestions?
I am not entirely sure what you mean by "crossed from one screen to another" I assume you mean paging? If so, here's what you can do:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender {
int page = sender.contentOffset.x / width;
[self.pageControl setCurrentPage:page]; // in case you need it for updating a UIPageControl
}
I found a way of doing this :
I have a variable to maintain the x location of the contentOffset
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
currX = self.scrollView.contentOffset.x;
}
And then in this event :
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if(self.scrollView.contentOffset.x == currX ) {
return;//This is what I needed, If I haven't panned to another view return
}
//Here I do my thing.