Поиск направления прокрутки в UIScrollView?
У меня есть UIScrollView
с разрешенной только горизонтальной прокруткой, и я хотел бы знать, в каком направлении (слева, справа) прокручивается пользователь. Я сделал, чтобы подклассифицировать UIScrollView
и переопределить метод touchesMoved
:
- (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");
}
}
Но этот метод иногда не вызывается вообще, когда я двигаюсь. Как вы думаете?
Ответы
Ответ 1
Определение направления довольно прямолинейно, но имейте в виду, что направление может меняться несколько раз в ходе жеста. Например, если у вас есть просмотр прокрутки с включенным пейджингом, и пользователь перебирается, чтобы перейти на следующую страницу, начальное направление может быть в порядке, но если у вас есть отскок, он вкратце будет совсем не направлен и затем ненадолго идти влево.
Чтобы определить направление, вам нужно использовать делегат UIScrollView scrollViewDidScroll
. В этом примере я создал переменную с именем 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,
};
Ответ 2
... Я хотел бы знать, в каком направлении (слева, справа) прокручивается пользователь.
В этом случае на 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
}
}
Ответ 3
Использование 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
} else if (self.lastContentOffset > scrollView.contentOffset.x) {
// moved left
} else {
// didn't move
}
}
Ответ 4
Не нужно добавлять дополнительную переменную, чтобы отслеживать это. Просто используйте свойство UIScrollView
panGestureRecognizer
, подобное этому. К сожалению, это работает, только если скорость не равна 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 для обнаружения вверх, вниз, влево и вправо.
Ответ 5
Решение
func scrollViewDidScroll(scrollView: UIScrollView) {
if(scrollView.panGestureRecognizer.translationInView(scrollView.superview).y > 0)
{
print("up")
}
else
{
print("down")
}
}
Ответ 6
Свифт 4:
Для горизонтальной прокрутки вы можете просто сделать:
if scrollView.panGestureRecognizer.translation(in: scrollView.superview).x > 0 {
print("left")
} else {
print("right")
}
Для вертикальной прокрутки измените .x
на .y
Ответ 7
В 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!")
}
}
Ответ 8
Вот что у меня получилось (в Objective-C):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSString *direction = ([scrollView.panGestureRecognizer translationInView:scrollView.superview].y >0)[email protected]"up":@"down";
NSLog(@"%@",direction);
}
Ответ 9
- (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");
}
}
Ответ 10
В качестве альтернативы можно наблюдать ключевой путь "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
}
}
Не забывайте удалять наблюдателя, когда это необходимо. например вы можете добавить наблюдателя в viewWillAppear и удалить его в viewWillDisappear
Ответ 11
В быстрой:
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translation(in: scrollView).y < 0 {
print("down")
} else {
print("up")
}
}
Вы можете сделать это также в scrollViewDidScroll.
Ответ 12
Вот мое решение для поведения, как в ответе @followben, но без потерь при медленном запуске (когда 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
}
}
}
}
}
Ответ 13
Я проверил часть ответа и разработал ответ 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
Ответ 14
Я предпочитаю выполнять фильтрацию, основываясь на ответе @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
}
}
Ответ 15
Когда пейджинг включен, вы можете использовать этот код.
- (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");
}
}
Ответ 16
Коды объясняют, я думаю.
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;
}
Ответ 17
Хорошо, поэтому для меня эта реализация работает очень хорошо:
@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 уволит после завершения перетаскивания.
Ответ 18
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
NSLog(@"px %f py %f",velocity.x,velocity.y);}
Используйте этот метод делегата scrollview.
Если координата y скорости имеет вид + ve прокрутки вниз, и если это -ve scrollview прокручивается вверх.
Аналогично левая и правая прокрутка могут быть обнаружены с помощью координаты x.
Ответ 19
Короткое и простое было бы, просто проверьте значение скорости, если оно больше нуля, а затем его прокрутка влево вправо:
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
}
}
Ответ 20
Если вы работаете с 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.
Ответ 21
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
}
Ответ 22
Во всех верхних ответах есть два основных способа решения проблемы - использование panGestureRecognizer
или contentOffset
. Оба метода имеют свои плюсы и минусы.
Способ 1: panGestureRecognizer
Когда вы используете panGestureRecognizer
, как то, что предлагает @followben, если вы не хотите программно прокручивать вид прокрутки, он работает правильно.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x > 0) {
// handle dragging to the right
} else {
// handle dragging to the left
}
}
Cons
Но если вы переместите представление прокрутки со следующим кодом, верхний код не сможет его распознать:
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
}
Cons
Если вы хотите изменить contentOffset программно, во время прокрутки (например, когда вы хотите создать бесконечную прокрутку), этот метод создает проблему, потому что вы можете изменить contentOffset
во время смены мест просмотра контента, и в это время верхний код будет прокручиваться при прокрутке. вправо или влево.