在UIScrollView中查找滚动方向?

我有一个只允许水平滚动的UIScrollView,我想知道用户滚动的方向(左,右)。我所做的是子类化__abc0并覆盖__abc2方法:

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];


UITouch *touch = [touches anyObject];
float now = [touch locationInView:self].x;
float before = [touch previousLocationInView:self].x;
NSLog(@"%f %f", before, now);
if (now > before){
right = NO;
NSLog(@"LEFT");
}
else{
right = YES;
NSLog(@"RIGHT");


}


}

但是当我移动时,这个方法有时根本不会被调用。你怎么看?

164389 次浏览

确定方向相当简单,但请记住,在一个手势的过程中,方向可能会改变几次。例如,如果您有一个打开了分页的滚动视图,并且用户滑动以转到下一页,则初始方向可能是向右,但如果您打开了弹跳,则它将暂时不向任何方向移动,然后暂时向左移动。

要确定方向,您需要使用UIScrollView scrollViewDidScroll Delegate.在这个示例中,我创建了一个名为lastContentOffset的变量,我用它来比较当前内容与前一个内容的偏移量。如果它更大,则ScrollView向右滚动。如果小于,则ScrollView将向左滚动:

// somewhere in the private class extension
@property (nonatomic, assign) CGFloat lastContentOffset;


// somewhere in the class implementation
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {


ScrollDirection scrollDirection;


if (self.lastContentOffset > scrollView.contentOffset.x) {
scrollDirection = ScrollDirectionRight;
} else if (self.lastContentOffset < scrollView.contentOffset.x) {
scrollDirection = ScrollDirectionLeft;
}


self.lastContentOffset = scrollView.contentOffset.x;


// do whatever you need to with scrollDirection here.
}

我使用下面的枚举来定义方向。将第一个值设置为ScrollDirectionNone有一个额外的好处,即在初始化变量时将该方向设置为默认值:

typedef NS_ENUM(NSInteger, ScrollDirection) {
ScrollDirectionNone,
ScrollDirectionRight,
ScrollDirectionLeft,
ScrollDirectionUp,
ScrollDirectionDown,
ScrollDirectionCrazy,
};

使用scrollViewDidScroll:是找到电流方向的好方法。

如果您想知道用户完成滚动后之后的方向,请使用以下命令:

@property (nonatomic) CGFloat lastContentOffset;


- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {


self.lastContentOffset = scrollView.contentOffset.x;
}


- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {


if (self.lastContentOffset > scrollView.contentOffset.x) {
// moved right if last content offset is greater then current offset
} else if (self.lastContentOffset < scrollView.contentOffset.x) {
// moved left if last content offset is less that current offset
} else {
// didn't move
}
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {


CGPoint targetPoint = *targetContentOffset;
CGPoint currentPoint = scrollView.contentOffset;


if (targetPoint.y > currentPoint.y) {
NSLog(@"up");
}
else {
NSLog(@"down");
}
}

或者,可以观察关键路径“ ContentOffset ”。当您无法设置/更改滚动视图的委托时,这很有用。

[yourScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

添加观察者后,您现在可以:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
CGFloat newOffset = [[change objectForKey:@"new"] CGPointValue].y;
CGFloat oldOffset = [[change objectForKey:@"old"] CGPointValue].y;
CGFloat diff = newOffset - oldOffset;
if (diff < 0 ) { //scrolling down
// do something
}
}

请记住在需要时移除观察器。例如,您可以在ViewWill出现中添加观察者,并在视图消失中删除它

当分页打开时,您可以使用这些代码。

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
self.lastPage = self.currentPage;
CGFloat pageWidth = _mainScrollView.frame.size.width;
self.currentPage = floor((_mainScrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
if (self.lastPage < self.currentPage) {
//go right
NSLog(@"right");
}else if(self.lastPage > self.currentPage){
//go left
NSLog(@"left");
}else if (self.lastPage == self.currentPage){
//same page
NSLog(@"same page");
}
}

我检查了一些答案,并详细阐述了AnswerBot的答案,将所有内容都放在UIScrollView类别中。“ LastContentOffset ”保存在UIScrollView中,然后只需调用:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
[scrollView setLastContentOffset:scrollView.contentOffset];
}


- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (scrollView.scrollDirectionX == ScrollDirectionRight) {
//Do something with your views etc
}
if (scrollView.scrollDirectionY == ScrollDirectionUp) {
//Do something with your views etc
}
}

源代码位于 https://github.com/tehjord/uiscrollviewscrollingdirection

...我想知道用户向哪个方向(左,右)滚动。

在这种情况下,在IOS 5及更高版本上,使用UIScrollViewDelegate来确定用户平移手势的方向:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x > 0) {
// handle dragging to the right
} else {
// handle dragging to the left
}
}

不需要添加额外的变量来跟踪这一点。只需像这样使用UIScrollViewpanGestureRecognizer属性。不幸的是,只有当速度不是0时,这才有效:

CGFloat yVelocity = [scrollView.panGestureRecognizer velocityInView:scrollView].y;
if (yVelocity < 0) {
NSLog(@"Up");
} else if (yVelocity > 0) {
NSLog(@"Down");
} else {
NSLog(@"Can't determine direction as velocity is 0");
}

您可以使用X和Y分量的组合来检测上、下、左和右。

我想

代码本身就能解释。 CGFloat Difference1和Difference2在同一个类私有接口中声明。 如果ContentSize保持不变,则很好。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{


CGFloat contentOffSet = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;


difference1 = contentHeight - contentOffSet;


if (difference1 > difference2) {
NSLog(@"Up");
}else{
NSLog(@"Down");
}


difference2 = contentHeight - contentOffSet;


}

好的,对我来说,这个实现非常好:

@property (nonatomic, assign) CGPoint lastContentOffset;




- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
_lastContentOffset.x = scrollView.contentOffset.x;
_lastContentOffset.y = scrollView.contentOffset.y;


}




- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {


if (_lastContentOffset.x < (int)scrollView.contentOffset.x) {
// moved right
NSLog(@"right");
}
else if (_lastContentOffset.x > (int)scrollView.contentOffset.x) {
// moved left
NSLog(@"left");


}else if (_lastContentOffset.y<(int)scrollView.contentOffset.y){
NSLog(@"up");


}else if (_lastContentOffset.y>(int)scrollView.contentOffset.y){
NSLog(@"down");
[self.txtText resignFirstResponder];


}
}

因此,这将在拖动结束后触发TextView以取消

在iOS8 Swift中,我使用了这种方法:

override func scrollViewDidScroll(scrollView: UIScrollView){


var frame: CGRect = self.photoButton.frame
var currentLocation = scrollView.contentOffset.y


if frame.origin.y > currentLocation{
println("Going up!")
}else if frame.origin.y < currentLocation{
println("Going down!")
}


frame.origin.y = scrollView.contentOffset.y + scrollHeight
photoButton.frame = frame
view.bringSubviewToFront(photoButton)


}

我有一个动态视图,它会随着用户的滚动而改变位置,因此视图看起来就像是停留在屏幕上的同一位置。我也在跟踪用户何时上升或下降。

这里也有另一种方法:

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if targetContentOffset.memory.y < scrollView.contentOffset.y {
println("Going up!")
} else {
println("Going down!")
}
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
NSLog(@"px %f py %f",velocity.x,velocity.y);}

使用ScrollView的这个委托方法。

如果速度的y坐标为+ve,则滚动视图向下滚动,如果为-ve,则滚动视图向上滚动。 类似地,可以使用X坐标来检测左右滚动。

短路&;很简单,只需检查速度值,如果它大于零,则向左滚动,否则向右滚动:

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {


var targetOffset = Float(targetContentOffset.memory.x)
println("TargetOffset: \(targetOffset)")
println(velocity)


if velocity.x < 0 {
scrollDirection = -1 //scrolling left
} else {
scrollDirection = 1 //scrolling right
}
}

如果使用UIScrollView和UIPageControl,此方法还将更改PageControl的页面视图。

  func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {


let targetOffset = targetContentOffset.memory.x
let widthPerPage = scrollView.contentSize.width / CGFloat(pageControl.numberOfPages)


let currentPage = targetOffset / widthPerPage
pageControl.currentPage = Int(currentPage)
}

感谢@Esq的SWIFT代码。

解决方案

func scrollViewDidScroll(scrollView: UIScrollView) {
if(scrollView.panGestureRecognizer.translationInView(scrollView.superview).y > 0)
{
print("up")
}
else
{
print("down")
}
}

SWIFT 2.2简单解决方案跟踪单方向和多方向,没有任何损失。

  // Keep last location with parameter
var lastLocation:CGPoint = CGPointZero


// We are using only this function so, we can
// track each scroll without lose anyone
override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
let currentLocation = scrollView.contentOffset


// Add each direction string
var directionList:[String] = []


if lastLocation.x < currentLocation.x {
//print("right")
directionList.append("Right")
} else if lastLocation.x > currentLocation.x {
//print("left")
directionList.append("Left")
}


// there is no "else if" to track both vertical
// and horizontal direction
if lastLocation.y < currentLocation.y {
//print("up")
directionList.append("Up")
} else if lastLocation.y > currentLocation.y {
//print("down")
directionList.append("Down")
}


// scrolled to single direction
if directionList.count == 1 {
print("scrolled to \(directionList[0]) direction.")
} else if directionList.count > 0  { // scrolled to multiple direction
print("scrolled to \(directionList[0])-\(directionList[1]) direction.")
}


// Update last location after check current otherwise,
// values will be same
lastLocation = scrollView.contentOffset
}

下面是我对@FollowBen Answer中的行为的解决方案,但没有慢启动的损失(当dy为0时)

@property (assign, nonatomic) BOOL isFinding;
@property (assign, nonatomic) CGFloat previousOffset;


- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
self.isFinding = YES;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (self.isFinding) {
if (self.previousOffset == 0) {
self.previousOffset = self.tableView.contentOffset.y;


} else {
CGFloat diff = self.tableView.contentOffset.y - self.previousOffset;
if (diff != 0) {
self.previousOffset = 0;
self.isFinding = NO;


if (diff > 0) {
// moved up
} else {
// moved down
}
}
}
}
}

这是它为我工作的(在Objective-C中):

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView{


NSString *direction = ([scrollView.panGestureRecognizer translationInView:scrollView.superview].y >0)?@"up":@"down";
NSLog(@"%@",direction);
}

在SWIFT中:

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translation(in: scrollView).y < 0 {
print("down")
} else {
print("up")
}
}

您也可以在ScrollViewDidScroll中执行此操作。

斯威夫特4:

对于水平滚动,您可以简单地执行:

if scrollView.panGestureRecognizer.translation(in: scrollView.superview).x > 0 {
print("left")
} else {
print("right")
}

对于垂直滚动,用.y改变.x

根据@Memmons的回答,我更喜欢做一些过滤。

在Objective-C中:

// in the private class extension
@property (nonatomic, assign) CGFloat lastContentOffset;


// in the class implementation
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {


if (fabs(self.lastContentOffset - scrollView.contentOffset.x) > 20 ) {
self.lastContentOffset = scrollView.contentOffset.x;
}


if (self.lastContentOffset > scrollView.contentOffset.x) {
//  Scroll Direction Left
//  do what you need to with scrollDirection here.
} else {
//  omitted
//  if (self.lastContentOffset < scrollView.contentOffset.x)


//  do what you need to with scrollDirection here.
//  Scroll Direction Right
}
}

当在- (void)scrollViewDidScroll:(UIScrollView *)scrollView下测试时:

NSLog(@"lastContentOffset: --- %f,   scrollView.contentOffset.x : --- %f", self.lastContentOffset, scrollView.contentOffset.x);

img

self.lastContentOffset变化非常快,数值差距接近0.5F.

这是没有必要的。

偶尔,当在准确的条件下操作时,你的方向可能会迷失。(有时会跳过实现语句)

例如:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{


CGFloat viewWidth = scrollView.frame.size.width;


self.lastContentOffset = scrollView.contentOffset.x;
// Bad example , needs value filtering


NSInteger page = scrollView.contentOffset.x / viewWidth;


if (page == self.images.count + 1 && self.lastContentOffset < scrollView.contentOffset.x ){
//  Scroll Direction Right
//  do what you need to with scrollDirection here.
}
....

在SWIFT 4中:

var lastContentOffset: CGFloat = 0


func scrollViewDidScroll(_ scrollView: UIScrollView) {


if (abs(lastContentOffset - scrollView.contentOffset.x) > 20 ) {
lastContentOffset = scrollView.contentOffset.x;
}


if (lastContentOffset > scrollView.contentOffset.x) {
//  Scroll Direction Left
//  do what you need to with scrollDirection here.
} else {
//  omitted
//  if (self.lastContentOffset < scrollView.contentOffset.x)


//  do what you need to with scrollDirection here.
//  Scroll Direction Right
}
}

在所有上面的答案中,解决问题的两种主要方法是使用panGestureRecognizercontentOffset。这两种方法各有利弊。

方法1:PangestureRecognizer

当您像@FollowBen建议的那样使用panGestureRecognizer时,如果您不想以编程方式滚动滚动视图,它可以正常工作。

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x > 0) {
// handle dragging to the right
} else {
// handle dragging to the left
}
}


缺点

但是如果你用下面的代码移动滚动视图,上面的代码不能识别它:

setContentOffset(CGPoint(x: 100, y: 0), animation: false)

方法2:ContentOffset

var lastContentOffset: CGPoint = CGPoint.zero


func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (self.lastContentOffset.x > scrollView.contentOffset.x) {
// scroll to right
} else if self.lastContentOffset.x < scrollView.contentOffset.x {
// scroll to left
}
self.lastContentOffset = self.scrollView.contentOffset
}

缺点

如果您想在滚动期间以编程方式更改ContentOffset(例如,当您想创建无限滚动时),此方法会产生问题,因为您可能会在更改内容视图位置期间更改contentOffset,此时,上层代码会跳到您向右或向左滚动的位置。

//Vertical detection
    var lastVelocityYSign = 0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let currentVelocityY =  scrollView.panGestureRecognizer.velocity(in: scrollView.superview).y
let currentVelocityYSign = Int(currentVelocityY).signum()
if currentVelocityYSign != lastVelocityYSign &&
currentVelocityYSign != 0 {
lastVelocityYSign = currentVelocityYSign
}
if lastVelocityYSign < 0 {
print("SCROLLING DOWN")
} else if lastVelocityYSign > 0 {
print("SCOLLING UP")
}
}

来自MOS6Y的回答 https://medium.com/@mos6ycanswift/swift-IOS-determine-scroll-direction-d48a2327a004

斯威夫特5

更干净的解决方案,enum用于垂直滚动。

enum ScrollDirection {
case up, down
}


var scrollDirection: ScrollDirection? {
let yTranslation = scrollView.panGestureRecognizer.translation(in: scrollView.superview).y


if yTranslation > 0 {
return .up
} else if yTranslation < 0 {
return .down
} else {
return nil
}
}

用法

switch scrollDirection {
case .up:   print("up")
case .down: print("down")
default:    print("no scroll")
}