Menemukan arah bergulir di UIScrollView?

183

Saya hanya memiliki UIScrollViewdengan pengguliran horizontal yang diizinkan, dan saya ingin tahu ke arah mana (kiri, kanan) yang digulirkan pengguna. Apa yang saya lakukan adalah untuk subclass UIScrollViewdan menimpa touchesMovedmetode:

- (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");

    }

}

Tetapi metode ini terkadang tidak dipanggil sama sekali ketika saya pindah. Bagaimana menurut anda?

Alex1987
sumber
Lihat tanggapan saya di bawah ini - Anda harus menggunakan delegasi tampilan gulir untuk melakukan ini.
memmons
jawaban terbaik di sini: stackoverflow.com/questions/11262583/…
iksnae

Jawaban:

392

Menentukan arah cukup mudah, tetapi perlu diingat bahwa arah dapat berubah beberapa kali selama gerakan. Misalnya, jika Anda memiliki tampilan gulir dengan paging dihidupkan dan pengguna menggesek untuk pergi ke halaman berikutnya, arah awal bisa ke kanan, tetapi jika Anda telah bouncing dihidupkan, sebentar akan pergi ke arah tidak sama sekali dan maka sebentar akan ke kiri.

Untuk menentukan arah, Anda harus menggunakan UIScrollView scrollViewDidScrolldelegasi. Dalam sampel ini, saya membuat variabel bernama lastContentOffsetyang saya gunakan untuk membandingkan konten saat ini dengan yang sebelumnya. Jika lebih besar, maka scrollView sedang bergulir ke kanan. Jika kurang dari itu maka scrollView sedang bergulir ke kiri:

// 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.    
}

Saya menggunakan enum berikut untuk menentukan arah. Menyetel nilai pertama ke ScrollDirectionNone memiliki manfaat tambahan menjadikan arah itu sebagai default saat menginisialisasi variabel:

typedef NS_ENUM(NSInteger, ScrollDirection) {
    ScrollDirectionNone,
    ScrollDirectionRight,
    ScrollDirectionLeft,
    ScrollDirectionUp,
    ScrollDirectionDown,
    ScrollDirectionCrazy,
};
memmons
sumber
1
Bagaimana saya bisa mendapatkan LastContentOffset
Dev
@JasonZhao Heh - Saya mendapatkan beberapa hasil yang aneh ketika saya menguji kode secara awal karena saya tidak memperhitungkan bahwa bouncing scrollview menyebabkan arah scroll ke..er..bounce. Jadi, saya menambahkan itu ke enum ketika saya menemukan hasil yang tidak terduga.
memmons
1
@ Dev Ada dalam kodelastContentOffset = scrollView.contentOffset.x;
memmons
@ akivag29 Tangkapan yang bagus pada lastContentOffsettipenya. Mengenai variabel properti vs statis: salah satu solusi adalah pilihan yang baik. Saya tidak punya preferensi nyata. Ini poin yang bagus.
memmons
2
@JosueEspinosa Untuk mendapatkan arah gulir yang benar, Anda memasukkannya ke scrollViewWillBeginDragging: metode untuk panggilan setter LastContentOffset terakhir.
strawnut
73

... Saya ingin tahu ke arah mana (kiri, kanan) yang digulirkan pengguna.

Dalam hal itu, di iOS 5 dan di atasnya, gunakan UIScrollViewDelegateuntuk menentukan arah gerakan pan pengguna:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{ 
    if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x > 0) {
        // handle dragging to the right
    } else {
        // handle dragging to the left
    }
}
followben
sumber
1
bisa menjadi solusi terbaik, tetapi dengan start yang sangat lambat, dx akan sama dengan 0.
trickster77777
Tentu saja. Ini harus lebih ditingkatkan karena ini lebih merupakan "asli" atau cara yang tepat terutama karena harus menggunakan nilai yang disimpan properti.
Michi
Bukankah ini memiliki masalah yang sama dengan stackoverflow.com/a/26192103/62 di mana ia tidak akan berfungsi dengan benar setelah momentum bergulir muncul setelah pengguna berhenti menggeser?
Liron Yahdav
59

Menggunakan scrollViewDidScroll:adalah cara yang baik untuk menemukan arah saat ini.

Jika Anda ingin mengetahui arah setelah pengguna selesai menggulir, gunakan yang berikut ini:

@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
    }
}
Justin Tanner
sumber
3
Ini adalah tambahan yang bagus bagi Justin, namun saya ingin menambahkan satu suntingan. Jika scrollview Anda telah mengaktifkan paging, dan Anda melakukan drag yang akhirnya kembali ke posisi semula, kondisi "lain" saat ini akan mempertimbangkan bahwa "pindah ke kiri", meskipun seharusnya "tidak ada perubahan".
Scott Lieberman
1
@ScottLieberman hak Anda, saya telah memperbarui kode yang sesuai.
Justin Tanner
50

Tidak perlu menambahkan variabel tambahan untuk melacak hal ini. Hanya menggunakan UIScrollView's panGestureRecognizerproperti seperti ini. Sayangnya, ini hanya berfungsi jika kecepatannya tidak 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");
}

Anda dapat menggunakan kombinasi komponen x dan y untuk mendeteksi atas, bawah, kiri dan kanan.

rounak
sumber
9
sayangnya ini hanya berfungsi sambil menyeret. kecepatan adalah 0 ketika sentuhan dihapus, bahkan saat masih bergulir.
heiko
+1 bagus. lain bagian Velocity 0 karena, pengguna tidak lagi menyeret, jadi kecepatan gerakan adalah 0
Pavan
Anda dapat menyimpan arah dalam variabel. Ubah hanya ketika kecepatan! = 0. Bagi saya berfungsi
Leszek Zarna
Bekerja dengan scrollViewWillBeginDragging, luar biasa
demensdeum
40

Solusinya

func scrollViewDidScroll(scrollView: UIScrollView) {
     if(scrollView.panGestureRecognizer.translationInView(scrollView.superview).y > 0)
     {
         print("up")
     }
    else
    {
         print("down")
    } 
}
davidrelgr
sumber
1
Mencobanya dengan velocityInViewbukan translationInView. Ini memecahkan masalah yang muncul saat mengubah arah dalam gerakan yang sama.
enreas
2
Solusi ini berfungsi sempurna. Jawaban yang paling diterima adalah tidak berfungsi beberapa kali ketika saya pergi ke layar berikutnya dan kembali melakukan operasi gulir ke atas dan ke bawah.
Anjaneyulu Battula
Yang terbaik, terima kasih!
Booharin
18

Swift 4:

Untuk pengguliran horizontal, Anda cukup melakukan:

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

Untuk perubahan gulir vertikal .xdengan.y

Alessandro Ornano
sumber
14

Di iOS8 Swift saya menggunakan metode ini:

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)

}

Saya memiliki tampilan dinamis yang mengubah lokasi saat pengguna menggulir sehingga tampilan tersebut tampak seperti berada di tempat yang sama di layar. Saya juga melacak ketika pengguna naik atau turun.

Ini juga cara alternatif:

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    if targetContentOffset.memory.y < scrollView.contentOffset.y {
        println("Going up!")
    } else {
        println("Going down!")
    }
}
Esqarrouth
sumber
1
Gunakan x bukan y?
Esqarrouth
Sebenarnya saya ingin mendeteksinya untuk UiCollectionView. Saya memiliki pengguliran horizontal. Jadi saya akan mendeteksi dengan crollViewDidEndDeceleratingbenar?
Umair Afzal
8

Inilah yang berhasil bagi saya (dalam Objective-C):

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

        NSString *direction = ([scrollView.panGestureRecognizer translationInView:scrollView.superview].y >0)?@"up":@"down";
        NSLog(@"%@",direction);
    }
Javier Calatrava Llavería
sumber
7
- (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");
    }
}
Oded Regev
sumber
2
Metode ini tidak dipanggil ketika nilai properti pagingEnabled tampilan gulir adalah YA.
Ako
5

Atau, dimungkinkan untuk mengamati jalur utama "contentOffset". Ini berguna ketika tidak mungkin bagi Anda untuk mengatur / mengubah delegasi dari tampilan gulir.

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

Setelah menambahkan pengamat, Anda sekarang dapat:

- (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
    }
}

Ingat untuk menghapus pengamat saat dibutuhkan. mis. Anda bisa menambahkan pengamat di viewWillAppear dan menghapusnya di viewWillDisappear

xu huanze
sumber
5

Dengan cepat:

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

Anda dapat melakukannya juga di scrollViewDidScroll.

Javier Calatrava Llavería
sumber
4

Berikut ini adalah solusi saya untuk perilaku seperti pada jawaban @followben, tetapi tanpa kehilangan dengan awal yang lambat (ketika dy adalah 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
                }
            }
        }
    }
}
Andrey Zhukov
sumber
1

Saya memeriksa beberapa jawaban dan menguraikan jawaban AnswerBot dengan membungkus semuanya dalam setetes dalam kategori UIScrollView. "LastContentOffset" disimpan di dalam uiscrollview dan hanya masalah panggilan:

- (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
  }
}

Kode sumber di https://github.com/tehjord/UIScrollViewScrollingDirection

Bjergsen
sumber
1

Saya lebih suka melakukan penyaringan, berdasarkan jawaban @memmons

Dalam 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
    } 
}

Saat diuji dalam - (void)scrollViewDidScroll:(UIScrollView *)scrollView:

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

img

self.lastContentOffset perubahan sangat cepat, kesenjangan nilai hampir 0,5 f.

Itu tidak perlu.

Dan terkadang, ketika ditangani dalam kondisi akurat, orientasi Anda mungkin hilang. (kadang-kadang pernyataan implementasi dilewati)

seperti :

- (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.
    }
   ....

Dalam 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
    }
}
Mutiara Hitam
sumber
0

Saat paging dihidupkan, Anda bisa menggunakan kode ini.

- (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");
    }
}
Perry
sumber
0

Saya kira kode menjelaskan sendiri. CGFloat difference1 dan difference2 dideklarasikan dalam antarmuka pribadi kelas yang sama. Bagus jika contentSize tetap sama.

- (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;

       }
pengguna2511630
sumber
0

Ok jadi bagi saya implementasi ini bekerja sangat baik:

@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];

    }
}

Jadi ini akan memecat textView untuk mengabaikan setelah drag berakhir

pengguna2021505
sumber
0
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
NSLog(@"px %f py %f",velocity.x,velocity.y);}

Gunakan metode delegasi ini pada scrollview.

Jika Anda mengkoordinasikan kecepatan adalah + ve scroll view scroll ke bawah dan jika itu -ve scrollview scroll ke atas. Demikian pula gulir kiri dan kanan dapat dideteksi menggunakan koordinat x.

Nilesh Tupe
sumber
0

Pendek & Mudah adalah, cukup periksa nilai kecepatan, jika lebih besar dari nol maka gulir ke kiri kanan:

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
    }
}
iDilip
sumber
0

Jika Anda bekerja dengan UIScrollView dan UIPageControl, metode ini juga akan mengubah tampilan halaman 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)
}

Terima kasih kode Swift @Esq.

charles.cc.hsu
sumber
0

Swift 2.2 Solusi sederhana yang melacak arah tunggal dan banyak arah tanpa kehilangan.

  // 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
  }
fatihyildizhan
sumber
0

Dalam semua jawaban atas, dua cara utama untuk menyelesaikan masalah adalah menggunakan panGestureRecognizeratau contentOffset. Kedua metode memiliki kekurangan dan kelebihan.

Metode 1: panGestureRecognizer

Saat Anda menggunakan panGestureRecognizerseperti yang disarankan @followben, jika Anda tidak ingin secara terprogram menggulir tampilan gulir Anda, itu berfungsi dengan baik.

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

Cons

Tetapi jika Anda memindahkan tampilan gulir dengan kode berikut, kode atas tidak dapat mengenalinya:

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

Metode 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

Jika Anda ingin mengubah contentOffset secara terprogram, selama gulir (seperti ketika Anda ingin membuat gulir yang tak terbatas), metode ini membuat masalah, karena Anda dapat berubah contentOffsetselama mengubah tempat tampilan konten dan saat ini, lompatan kode atas yang Anda gulir ke kanan atau kiri.

Esmaeil
sumber
0
//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")
                }
            }

Jawaban dari Mos6y https://medium.com/@Mos6yCanSwift/swift-ios-determine-scroll-direction-d48a2327a004

slimas
sumber