Scrolling UITableView methods - ios

I need to get event then UITableView scrolling up and down. How can I do it?
this not working
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView

try this method.
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView
{
CGPoint offset = aScrollView.contentOffset;
if (offset.y > 0)
{
NSLog(#"scorlling up");
}
else
{
NSLog(#"scorlling down");
}
}

Related

iOS Scrolling one UIScrollView based off contentOffset of another goes out of sync

EDIT: I have attached a simple demo project dropbox link at bottom
I have a UI with a UICollectionView at top and a scrollview at the bottom. I want the scrolling in collectionview to scroll the scrollview too in sync. I have disabled user-interaction in scrollview so only the collectionview can effect the scrolling in it.
Each collectionview item is 150px for this testing purpose.
The UIViews in the scrollview are screen width in size. So for every scroll of a collectionview item, I need to scroll the scrollview by screen width. To achieve this, I am calculating the distance the collectionview offset has changed by and then dividing it by the cell width (150) and multiplying it by scrollview's width.
I am trying to achieve the following UI:
Start:
Scroll collectionview to cell 1:
Scroll collectionview to cell 2:
This all works fine the first few times but as I scroll the collectionview back and forth a few times to longer distances (let's say cell 10 -> 0 -> 10 -> 0 and so on), the scrollview goes out of sync by "tiny" distances. To illustrate this, notice how there is the "yellow" color from the second UIView on the right edge of the scrollview:
I can see this issue by NSLogging the contentOffset of the scrollview too (notice how it starts getting out of sync by 0.5 after few times):
2018-11-25 19:24:28.273278-0500 ScrollViewMatchTest[19412:1203912] Finished: 0
2018-11-25 19:24:31.606521-0500 ScrollViewMatchTest[19412:1203912] Finished: 0.5
2018-11-25 19:24:55.173709-0500 ScrollViewMatchTest[19412:1203912] Finished: 1.5
2018-11-25 19:25:03.007528-0500 ScrollViewMatchTest[19412:1203912] Finished: 1.5
2018-11-25 19:25:07.841096-0500 ScrollViewMatchTest[19412:1203912] Finished: 2.5
2018-11-25 19:26:57.634429-0500
I am not really sure what's causing this problem and I have tried quite a few ways to fix it but in vain. I can sort of figure out a workaround (to reset the offset and bring it back in sync when scrolling finishes) but I would like to know why exactly this out of sync issue is caused.
Workaround solution by resetting the contentOffset of scrollView to closes multiple of screen width:
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (scrollView==self.myCollectionView) {
NSLog(#"Finished: %g",self.myScrollView.contentOffset.x);
NSLog(#"Closest: %g",RoundTo(self.myScrollView.contentOffset.x, self.myScrollView.frame.size.width));
[self.myScrollView setContentOffset:CGPointMake(RoundTo(self.myScrollView.contentOffset.x, self.myScrollView.frame.size.width), self.myScrollView.contentOffset.y) animated:YES];
}
}
float RoundTo(float number, float to)
{
if (number >= 0) {
return to * floorf(number / to + 0.5f);
}
else {
return to * ceilf(number / to - 0.5f);
}
}
END OF WORKAROUND SOLUTION
I have attached a simple demo project to illustrate this issue as well (run the app and scroll back and forth aggresively on the top scrollview a few times): https://www.dropbox.com/s/e2bzgo6abq5wmgw/ScrollViewMatchTest.zip?dl=0
Here's the code:
#import "ViewController.h"
#define countOfItems 50
#interface ViewController (){
CGFloat previousOffset_Header;
CGFloat previousOffset_Scrollview;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.myCollectionView registerNib:[UINib nibWithNibName:#"MyCell" bundle:nil] forCellWithReuseIdentifier:#"cell"];
[self.myCollectionView reloadData];
for (NSInteger i=0; i<countOfItems; i++) {
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(i*self.myScrollView.frame.size.width, 0, self.myScrollView.frame.size.width, self.myScrollView.frame.size.height)];
myView.backgroundColor=i%2==0?[UIColor blueColor]:[UIColor yellowColor];
[self.myScrollView addSubview:myView];
}
self.myScrollView.contentSize=CGSizeMake(countOfItems*self.myScrollView.frame.size.width, self.myScrollView.frame.size.height);
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return countOfItems;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
MyCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
cell.backgroundColor = indexPath.item%2==0?[UIColor blueColor]:[UIColor yellowColor];
cell.myLabel.text=[NSString stringWithFormat:#"%ld",indexPath.item];
return cell;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
if (scrollView==self.myCollectionView) {
previousOffset_Header = self.myCollectionView.contentOffset.x;
previousOffset_Scrollview = self.myScrollView.contentOffset.x;
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (scrollView==self.myCollectionView) {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self performSelector:#selector(scrollViewDidEndScrollingAnimation:) withObject:scrollView afterDelay:0.1 inModes:#[NSRunLoopCommonModes]];
CGFloat offsetToMoveBy = (self.myCollectionView.contentOffset.x-previousOffset_Header)*(self.myScrollView.frame.size.width/150);
previousOffset_Scrollview = previousOffset_Scrollview +offsetToMoveBy;
[self.myScrollView setContentOffset:CGPointMake(previousOffset_Scrollview, self.myScrollView.contentOffset.y) animated:NO];
previousOffset_Header = self.myCollectionView.contentOffset.x;
}
}
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
NSLog(#"Finished: %g",self.myScrollView.contentOffset.x);
}
#end
Be aware in viewDidLoad method myScrollView frame is not really setted to the real device.
So in init of the view, the views's width and height might be wrong in your code.
There are a couple of things you should be doing differently, the way you calculate the offset and not using the UIScrollViewDelegate callbacks scrollViewDidEndDragging:willDecelerate: and scrollViewDidEndDecelerating:. I've rewritten the relevant code:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (scrollView==self.myCollectionView) {
[self calculateScrollviewOffset:self.myCollectionView];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (scrollView == self.myCollectionView && !decelerate) {
[self calculateEndPosition: self.myCollectionView];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView == self.myCollectionView) {
[self calculateEndPosition: self.myCollectionView];
}
}
- (void) calculateScrollviewOffset:(UICollectionView *) collectionView {
CGFloat cellWidth = 150.0;
CGFloat percentageMoved = collectionView.contentOffset.x / cellWidth;
[self setScrollViewOffset:percentageMoved animated:false];
}
- (void) calculateEndPosition:(UICollectionView*) collectionView {
CGFloat cellWidth = 150.0;
// NOTE: Add 0.5 to play around with the end animation positioning
CGFloat percentageMoved = floor(collectionView.contentOffset.x / cellWidth);
CGFloat collectionViewFixedOffset = (percentageMoved * cellWidth);
[collectionView setContentOffset:CGPointMake(collectionViewFixedOffset, collectionView.contentOffset.y) animated:true];
[self setScrollViewOffset:percentageMoved animated:true];
}
- (void) setScrollViewOffset:(CGFloat) percentageMoved animated:(BOOL) animated {
CGFloat newOffsetX = percentageMoved * self.myScrollView.frame.size.width;
[self.myScrollView setContentOffset:CGPointMake(newOffsetX, self.myScrollView.contentOffset.y) animated: animated];
}

UIScrollView scrollViewWillEndDragging: targetContentOffset: not working

Why is this not working? I have a breakpoint set at targetContentOffset->y = -50.0f; and it is being hit, not sure why it's not having any effect.
Yes paging = NO.
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
if (scrollView.contentOffset.y < -50.0f) {
targetContentOffset->y = -50.0f;
}
else {
*targetContentOffset = CGPointZero;
// Need to call this subsequently to remove flickering.
dispatch_async(dispatch_get_main_queue(), ^{
[scrollView setContentOffset:CGPointZero animated:YES];
});
}
}
This seems to be your answer.
scrollViewWillEndDragging:withVelocity:targetContentOffset: not working on the edges of a UISCrollView
Theres a UIScrollView bug with setting the targetContentOffset of scrollViewWillEndDragging:withVelocity:targetContentOffset: while the scroll view is at it's default content offset.

The Pull-to-Add Gesture as in Clear

Is anyone able to do this properly?
Here's a tutorial that covers the gesture http://www.raywenderlich.com/22174/how-to-make-a-gesture-driven-to-do-list-app-part-33. And here's the project files: https://github.com/ColinEberhardt/iOS-ClearStyle
It is far from as smooth as the original Clear app:
#pragma mark - UIScrollViewDelegate methods
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
// this behaviour starts when a user pulls down while at the top of the table
_pullDownInProgress = scrollView.contentOffset.y <= 0.0f;
if (_pullDownInProgress)
{
// add our placeholder
[_tableView insertSubview:_placeholderCell atIndex:0];
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (_pullDownInProgress && _tableView.scrollView.contentOffset.y <= 0.0f)
{
// maintain the location of the placeholder
_placeholderCell.frame = CGRectMake(0, - _tableView.scrollView.contentOffset.y - SHC_ROW_HEIGHT,
_tableView.frame.size.width, SHC_ROW_HEIGHT);
_placeholderCell.label.text = -_tableView.scrollView.contentOffset.y > SHC_ROW_HEIGHT ?
#"Release to Add Item" : #"Pull to Add Item";
_placeholderCell.alpha = MIN(1.0f, - _tableView.scrollView.contentOffset.y / SHC_ROW_HEIGHT);
}
else
{
_pullDownInProgress = false;
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
// check whether the user pulled down far enough
if (_pullDownInProgress && - _tableView.scrollView.contentOffset.y > SHC_ROW_HEIGHT)
{
[_tableView.datasource itemAdded];
}
_pullDownInProgress = false;
[_placeholderCell removeFromSuperview];
}
#end

UICollectionView scrolling issue - views out of order

I have a UICollectionView with a custom cell calld MyCell.
I'v been struggling with the scrolling methods, My scroll is horizontal here is my code:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
if (velocity.x < 0)
{
baseOffset = MAX(0, baseOffset - offsetStep);
baseOffset-=320;
NSLog(#"%f", baseOffset);
}
else if (velocity.x > 0)
{
baseOffset = MIN(scrollView.contentSize.width - scrollView.bounds.size.width, baseOffset + offsetStep);
baseOffset+=320;
NSLog(#"%f", baseOffset);
}
#if 0
dispatch_async(dispatch_get_main_queue(), ^{
[scrollView setContentOffset:CGPointMake(baseOffset, 0) animated:YES];
});
#else
targetContentOffset->x = baseOffset;
#endif
}
The problems are:
1. the cell is not automatically centered when scrolling
2. If i reach the end of the cells and scroll again, when i scroll back it
scrolls back to the last cell and if i scroll back again it scroll to the right cell.
3. same as 2 but when I'm in the first cell and scroll back.
Any help here gurus?
thanks.

UIScrollView Custom Paging

My question has to do with a custom form of paging which I am trying to do with a scroller, and this is easier to visualise if you first consider the type of scroll view implemented in a slot machine.
So say my UIScrollView has a width of 100 pixels. Assume it contains 3 inner views, each with a width of 30 pixels, such that they are separated by a width of 3 pixels. The type of paging which I would like to achieve, is such that each page is one of my views (30 pixels), and not the whole width of the scroll view.
I know that usually, if the view takes up the whole width of the scroll view, and paging is enabled then everything works. However, in my custom paging, I also want surrounding views in the scroll view to be visible as well.
How would I do this?
I just did this for another project. What you need to do is to place the UIScrollView into a custom implementation of UIView. I created a class for this called ExtendedHitAreaViewController. The ExtendedHitAreaView overrides the hitTest function to return its first child object, which will be your scroll view.
Your scroll view should be the page size you want, i.e., 30px with clipsToBounds = NO.
The extended hit area view should be the full size of the area you want to be visible, with clipsToBounds = YES.
Add the scroll view as a subview to the extended hit area view, then add the extended hit area view to your viewcontroller's view.
#implementation ExtendedHitAreaViewContainer
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if ([self pointInside:point withEvent:event]) {
if ([[self subviews] count] > 0) {
//force return of first child, if exists
return [[self subviews] objectAtIndex:0];
} else {
return self;
}
}
return nil;
}
#end
Since iOS 5 there is this delegate method: - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset.
So you can do something like this:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint
*)targetContentOffset {
if (scrollView == self.scrollView) {
CGFloat x = targetContentOffset->x;
x = roundf(x / 30.0f) * 30.0f;
targetContentOffset->x = x;
}
}
For higher velocities you might want to adjust your targetContentOffset a bit different if you want a more snappy feeling.
I had the same problem and this worked great for me, tested on iOS 8.
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset
{
NSInteger index = lrint(targetContentOffset->x/_pageWidth);
NSInteger currentPage = lrint(scrollView.contentOffset.x/_pageWidth);
if(index == currentPage) {
if(velocity.x > 0)
index++;
else if(velocity.x < 0)
index--;
}
targetContentOffset->x = index * _pageWidth;
}
I had to check the velocity and always go to next/previous page if velocity was not zero, otherwise it would give non-animated jumps when doing very short and fast swipes.
Update: This seems to work even better:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset
{
CGFloat index = targetContentOffset->x/channelScrollWidth;
if(velocity.x > 0)
index = ceil(index);
else if(velocity.x < 0)
index = floor(index);
else
index = round(index);
targetContentOffset->x = index * channelScrollWidth;
}
Those are for a horizontal scrollview, use y instead of x for a vertical one.
I've been struggling to overcome this issue, and I found an almost perfect solution which is to ideal with and paging width you want.
I'd set scrollView.isPaging to false (meanwhile, it's false by default) from UIScrollView and set its delegate to UIScrollViewDelegate.
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// Stop scrollView sliding:
targetContentOffset.pointee = scrollView.contentOffset
if scrollView == scrollView {
let maxIndex = slides.count - 1
let targetX: CGFloat = scrollView.contentOffset.x + velocity.x * 60.0
var targetIndex = Int(round(Double(targetX / (pageWidth + spacingWidth))))
var additionalWidth: CGFloat = 0
var isOverScrolled = false
if targetIndex <= 0 {
targetIndex = 0
} else {
// in case you want to make page to center of View
// by substract width with this additionalWidth
additionalWidth = 20
}
if targetIndex > maxIndex {
targetIndex = maxIndex
isOverScrolled = true
}
let velocityX = velocity.x
var newOffset = CGPoint(x: (CGFloat(targetIndex) * (self.pageWidth + self.spacingWidth)) - additionalWidth, y: 0)
if velocityX == 0 {
// when velocityX is 0, the jumping animation will occured
// if we don't set targetContentOffset.pointee to new offset
if !isOverScrolled && targetIndex == maxIndex {
newOffset.x = scrollView.contentSize.width - scrollView.frame.width
}
targetContentOffset.pointee = newOffset
}
// Damping equal 1 => no oscillations => decay animation:
UIView.animate(
withDuration: 0.3, delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: velocityX,
options: .allowUserInteraction,
animations: {
scrollView.contentOffset = newOffset
scrollView.layoutIfNeeded()
}, completion: nil)
}
}
slides contains all page views that you have inserted to UIScrollView.
I have many different views inside scroll view with buttons and gesture recognisers.
#picciano's answer didn't work (scroll worked good but buttons and recognisers didn't get touches) for me so I found this solution:
class ExtendedHitAreaView : UIScrollView {
// Your insets
var hitAreaEdgeInset = UIEdgeInsets(top: 0, left: -20, bottom: 0, right: -20)
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
let hitBounds = UIEdgeInsetsInsetRect(bounds, hitAreaEdgeInset)
return CGRectContainsPoint(hitBounds, point)
}
}
After a couple of days of researching and troubleshooting i came up with something that works for me!
First you need to subclass the view that the scrollview is in and override this method with the following:
-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event {
UIView* child = nil;
if ((child = [super hitTest:point withEvent:event]) == self)
return (UIView *)_calloutCell;
return child;
}
Then all the magic happens in the scrollview delegate methods
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
//_lastOffset is declared in the header file
//#property (nonatomic) CGPoint lastOffset;
_lastOffset = scrollView.contentOffset;
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
CGPoint currentOffset = scrollView.contentOffset;
CGPoint newOffset = CGPointZero;
if (_lastOffset.x < currentOffset.x) {
// right to left
newOffset.x = _lastOffset.x + 298;
}
else {
// left to right
newOffset.x = _lastOffset.x - 298;
}
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelay:0.0f];
targetContentOffset->x = newOffset.x;
[UIView commitAnimations];
}
You also need to set the scrollview's deceleration rate. I did it in ViewDidLoad
[self.scrollView setDecelerationRate:UIScrollViewDecelerationRateFast];
alcides' solution works perfectly. i just enable / disable the scrolling of the scrollview, whenever i enter scrollviewDidEndDragging and scrollViewWillEndDragging. if the user scrolls several times before the paging animation is finished, the cells are slightly out of alignment.
so i have:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
scrollView.scrollEnabled = NO
CGPoint currentOffset = scrollView.contentOffset;
CGPoint newOffset = CGPointZero;
if (_lastOffset.x < currentOffset.x) {
// right to left
newOffset.x = _lastOffset.x + 298;
}
else {
// left to right
newOffset.x = _lastOffset.x - 298;
}
[UIView animateWithDuration:0.4 animations:^{
targetContentOffset.x = newOffset.x
}];
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView {
scrollView.scrollEnabled = YES
}

Resources