UIScrollView: paging horizontal, scrolling vertikal?

89

Bagaimana saya bisa memaksa UIScrollViewdi mana paging dan scrolling hanya bergerak secara vertikal atau horizontal pada saat tertentu?

Pemahaman saya adalah bahwa directionalLockEnabledproperti harus mencapai ini, tetapi gesekan diagonal masih menyebabkan tampilan bergulir secara diagonal alih-alih membatasi gerakan ke satu sumbu.

Edit: agar lebih jelas, saya ingin mengizinkan pengguna untuk menggulir secara horizontal ATAU vertikal, tetapi tidak keduanya secara bersamaan.

Cœur
sumber
Apakah Anda ingin membatasi tampilan untuk digulir HANYA Horiz / Vert atau Anda ingin pengguna menggulir Horiz ATAU Vert, tetapi tidak keduanya secara bersamaan?
Will Hartung
Bisakah Anda membantu saya dan mengubah judul sehingga terlihat sedikit lebih menarik dan tidak sepele? Sesuatu seperti “UIScrollView: paging horizontal, scrolling vertikal?”
Andrey Tarantsov
Sayangnya, saya memposting pertanyaan sebagai pengguna yang tidak terdaftar di komputer yang tidak lagi dapat saya akses (sebelum mendaftar dengan nama yang sama ketika saya sampai di rumah), yang berarti saya tidak dapat mengeditnya. Ini juga harus menjelaskan saya menindaklanjuti lebih rendah daripada mengedit pertanyaan asli. Maaf telah melanggar etiket Stack kali ini!
Nick

Jawaban:

117

Anda berada dalam situasi yang sangat sulit, harus saya katakan.

Perhatikan bahwa Anda perlu menggunakan UIScrollView dengan pagingEnabled=YESuntuk beralih antar halaman, tetapi Anda perlu pagingEnabled=NOmenggulir secara vertikal.

Ada 2 kemungkinan strategi. Saya tidak tahu mana yang akan berhasil / lebih mudah diterapkan, jadi coba keduanya.

Pertama: UIScrollViews bertingkat. Terus terang, saya belum pernah melihat orang yang membuat ini berhasil. Namun secara pribadi saya belum berusaha cukup keras, dan latihan saya menunjukkan bahwa ketika Anda berusaha cukup keras, Anda dapat membuat UIScrollView melakukan apapun yang Anda inginkan .

Jadi strateginya adalah membiarkan tampilan gulir luar hanya menangani pengguliran horizontal, dan tampilan gulir dalam hanya menangani pengguliran vertikal. Untuk mencapai itu, Anda harus tahu bagaimana UIScrollView bekerja secara internal. Ini mengganti hitTestmetode dan selalu mengembalikan dirinya sendiri, sehingga semua peristiwa sentuh masuk ke UIScrollView. Kemudian di dalam touchesBegan, touchesMoveddll itu memeriksa apakah tertarik dengan acara tersebut, dan menangani atau meneruskannya ke komponen dalam.

Untuk memutuskan apakah sentuhan akan ditangani atau diteruskan, UIScrollView memulai pengatur waktu saat Anda pertama kali menyentuhnya:

  • Jika Anda belum menggerakkan jari Anda secara signifikan dalam 150 md, kejadian ini akan diteruskan ke tampilan dalam.

  • Jika Anda telah menggerakkan jari Anda secara signifikan dalam 150 md, itu mulai bergulir (dan tidak pernah melewatkan acara ke tampilan dalam).

    Perhatikan bagaimana saat Anda menyentuh tabel (yang merupakan subkelas tampilan gulir) dan segera mulai menggulir, baris yang Anda sentuh tidak pernah disorot.

  • Jika Anda belum menggerakkan jari Anda secara signifikan dalam 150 md dan UIScrollView mulai meneruskan kejadian ke tampilan dalam, tetapi kemudian Anda telah menggerakkan jari cukup jauh untuk memulai pengguliran, UIScrollView memanggil touchesCancelledtampilan dalam dan mulai menggulir.

    Perhatikan bagaimana saat Anda menyentuh tabel, tahan sedikit jari Anda dan kemudian mulai menggulir, baris yang Anda sentuh disorot terlebih dahulu, tetapi tidak lagi disorot setelahnya.

Urutan kejadian ini dapat diubah dengan konfigurasi UIScrollView:

  • Jika delaysContentTouchesTIDAK, maka tidak ada pengatur waktu yang digunakan - peristiwa segera masuk ke kontrol dalam (tetapi kemudian dibatalkan jika Anda menggerakkan jari cukup jauh)
  • Jika cancelsTouchesTIDAK, maka setelah peristiwa dikirim ke kontrol, pengguliran tidak akan pernah terjadi.

Perhatikan bahwa itu adalah UIScrollView yang menerima semua touchesBegin , touchesMoved, touchesEndeddan touchesCanceledperistiwa dari CocoaTouch (karena yang hitTestmengatakan itu untuk melakukannya). Itu kemudian meneruskan mereka ke pandangan batin jika mau, selama itu mau.

Sekarang setelah Anda mengetahui segalanya tentang UIScrollView, Anda dapat mengubah perilakunya. Saya yakin Anda ingin memberi preferensi pada pengguliran vertikal, sehingga setelah pengguna menyentuh tampilan dan mulai menggerakkan jarinya (bahkan sedikit), tampilan mulai bergulir ke arah vertikal; tetapi ketika pengguna menggerakkan jarinya ke arah horizontal cukup jauh, Anda ingin membatalkan pengguliran vertikal dan memulai pengguliran horizontal.

Anda ingin membuat subkelas UIScrollView luar Anda (katakanlah, Anda menamai kelas Anda RemorsefulScrollView), sehingga alih-alih perilaku default, ia segera meneruskan semua peristiwa ke tampilan dalam, dan hanya jika gerakan horizontal yang signifikan terdeteksi, ia akan menggulir.

Bagaimana cara membuat RemorsefulScrollView berperilaku seperti itu?

  • Sepertinya menonaktifkan pengguliran vertikal dan menyetel delaysContentToucheske NO akan membuat UIScrollView bersarang berfungsi. Sayangnya, tidak; UIScrollView tampaknya melakukan beberapa pemfilteran tambahan untuk gerakan cepat (yang tidak dapat dinonaktifkan), sehingga meskipun UIScrollView hanya dapat di-scroll secara horizontal, UIScrollView akan selalu memakan (dan mengabaikan) gerakan vertikal yang cukup cepat.

    Efeknya begitu parah sehingga pengguliran vertikal di dalam tampilan gulir bersarang tidak dapat digunakan. (Tampaknya Anda memiliki pengaturan ini persis, jadi cobalah: tahan jari selama 150 md, lalu gerakkan ke arah vertikal - UIScrollView bersarang berfungsi seperti yang diharapkan!)

  • Ini berarti Anda tidak dapat menggunakan kode UIScrollView untuk penanganan acara; Anda harus mengganti keempat metode penanganan sentuh di RemorsefulScrollView dan melakukan pemrosesan Anda sendiri terlebih dahulu, hanya meneruskan acara ke super(UIScrollView) jika Anda telah memutuskan untuk menggunakan pengguliran horizontal.

  • Namun Anda harus meneruskan touchesBeganke UIScrollView, karena Anda ingin UIScrollView mengingat koordinat dasar untuk pengguliran horizontal di masa mendatang (jika Anda kemudian memutuskan itu adalah pengguliran horizontal). Anda tidak akan bisa mengirim touchesBeganke UIScrollView nanti, karena Anda tidak bisa menyimpan touchesargumen: ini berisi objek yang akan dimutasi sebelum touchesMovedacara berikutnya , dan Anda tidak bisa mereproduksi keadaan lama.

    Jadi, Anda harus segera meneruskan touchesBeganke UIScrollView, tetapi Anda akan menyembunyikan touchesMovedperistiwa lebih lanjut darinya hingga Anda memutuskan untuk menggulir secara horizontal. Tidak touchesMovedberarti tidak ada scrolling, jadi inisial touchesBeganini tidak akan merugikan. Tapi setel delaysContentToucheske NO, sehingga tidak ada timer kejutan tambahan yang mengganggu.

    (Offtopic - tidak seperti Anda, UIScrollView dapat menyimpan sentuhan dengan benar dan dapat mereproduksi serta meneruskan touchesBeganacara asli nanti. Ini memiliki keuntungan yang tidak adil dalam menggunakan API yang tidak dipublikasikan, sehingga dapat menggandakan objek sentuh sebelum dimutasi.)

  • Mengingat bahwa Anda selalu maju touchesBegan, Anda juga harus maju touchesCancelleddan touchesEnded. Anda harus berubah touchesEndedmenjadi touchesCancelled, bagaimanapun, karena UIScrollView akan menafsirkan touchesBegan, touchesEndedurutan sebagai klik-sentuh, dan akan meneruskannya ke tampilan dalam. Anda sendiri sudah meneruskan acara yang tepat, jadi Anda tidak ingin UIScrollView meneruskan apa pun.

Pada dasarnya inilah pseudocode untuk apa yang perlu Anda lakukan. Untuk kesederhanaan, saya tidak pernah mengizinkan pengguliran horizontal setelah peristiwa multisentuh terjadi.

// RemorsefulScrollView.h

@interface RemorsefulScrollView : UIScrollView {
  CGPoint _originalPoint;
  BOOL _isHorizontalScroll, _isMultitouch;
  UIView *_currentChild;
}
@end

// RemorsefulScrollView.m

// the numbers from an example in Apple docs, may need to tune them
#define kThresholdX 12.0f
#define kThresholdY 4.0f

@implementation RemorsefulScrollView

- (id)initWithFrame:(CGRect)frame {
  if (self = [super initWithFrame:frame]) {
    self.delaysContentTouches = NO;
  }
  return self;
}

- (id)initWithCoder:(NSCoder *)coder {
  if (self = [super initWithCoder:coder]) {
    self.delaysContentTouches = NO;
  }
  return self;
}

- (UIView *)honestHitTest:(CGPoint)point withEvent:(UIEvent *)event {
  UIView *result = nil;
  for (UIView *child in self.subviews)
    if ([child pointInside:point withEvent:event])
      if ((result = [child hitTest:point withEvent:event]) != nil)
        break;
  return result;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event]; // always forward touchesBegan -- there's no way to forward it later
    if (_isHorizontalScroll)
      return; // UIScrollView is in charge now
    if ([touches count] == [[event touchesForView:self] count]) { // initial touch
      _originalPoint = [[touches anyObject] locationInView:self];
    _currentChild = [self honestHitTest:_originalPoint withEvent:event];
    _isMultitouch = NO;
    }
  _isMultitouch |= ([[event touchesForView:self] count] > 1);
  [_currentChild touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  if (!_isHorizontalScroll && !_isMultitouch) {
    CGPoint point = [[touches anyObject] locationInView:self];
    if (fabsf(_originalPoint.x - point.x) > kThresholdX && fabsf(_originalPoint.y - point.y) < kThresholdY) {
      _isHorizontalScroll = YES;
      [_currentChild touchesCancelled:[event touchesForView:self] withEvent:event]
    }
  }
  if (_isHorizontalScroll)
    [super touchesMoved:touches withEvent:event]; // UIScrollView only kicks in on horizontal scroll
  else
    [_currentChild touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  if (_isHorizontalScroll)
    [super touchesEnded:touches withEvent:event];
    else {
    [super touchesCancelled:touches withEvent:event];
    [_currentChild touchesEnded:touches withEvent:event];
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
  [super touchesCancelled:touches withEvent:event];
  if (!_isHorizontalScroll)
    [_currentChild touchesCancelled:touches withEvent:event];
}

@end

Saya belum mencoba menjalankan atau bahkan mengkompilasi ini (dan mengetik seluruh kelas dalam editor teks biasa), tetapi Anda dapat memulai dengan yang di atas dan semoga berhasil.

Satu-satunya tangkapan tersembunyi yang saya lihat adalah jika Anda menambahkan tampilan anak non-UIScrollView ke RemorsefulScrollView, peristiwa sentuh yang Anda teruskan ke anak mungkin kembali kepada Anda melalui rantai penjawab, jika anak tersebut tidak selalu menangani sentuhan seperti yang dilakukan UIScrollView. Penerapan RemorsefulScrollView anti peluru akan melindungi dari touchesXxxmasuk kembali.

Strategi kedua: Jika karena alasan tertentu, UIScrollView yang disarangkan tidak berhasil atau terbukti terlalu sulit untuk dilakukan dengan benar, Anda dapat mencoba untuk menyesuaikan hanya dengan satu UIScrollView, dengan mengalihkan pagingEnabledpropertinya dengan cepat dari scrollViewDidScrollmetode delegasi Anda .

Untuk mencegah pengguliran diagonal, Anda harus mencoba mengingat contentOffset terlebih dahulu scrollViewWillBeginDragging, dan memeriksa serta menyetel ulang contentOffset di dalam scrollViewDidScrolljika Anda mendeteksi gerakan diagonal. Strategi lain untuk dicoba adalah mengatur ulang contentSize untuk hanya mengaktifkan pengguliran dalam satu arah, setelah Anda memutuskan ke arah mana jari pengguna akan pergi. (UIScrollView tampaknya cukup pemaaf jika mengutak-atik contentSize dan contentOffset dari metode delegasinya.)

Jika itu tidak berhasil atau menghasilkan visual yang ceroboh, Anda harus menimpa touchesBegan, touchesMoveddll. Dan tidak meneruskan peristiwa gerakan diagonal ke UIScrollView. (Pengalaman pengguna akan menjadi kurang optimal dalam kasus ini, karena Anda harus mengabaikan gerakan diagonal alih-alih memaksanya ke satu arah. Jika Anda benar-benar ingin berpetualang, Anda dapat menulis mirip UITouch Anda sendiri, seperti RevengeTouch. Tujuan -C adalah C lama biasa, dan tidak ada yang lebih mirip bebek di dunia selain C; selama tidak ada yang memeriksa kelas sebenarnya dari objek, yang saya yakin tidak ada yang melakukannya, Anda dapat membuat kelas apa pun terlihat seperti kelas lain. kemungkinan untuk mensintesis sentuhan yang Anda inginkan, dengan koordinat apa pun yang Anda inginkan.)

Strategi cadangan: ada TTScrollView, implementasi ulang UIScrollView di pustaka Three20 . Sayangnya itu terasa sangat tidak wajar dan non-iphonish bagi pengguna. Tetapi jika setiap upaya menggunakan UIScrollView gagal, Anda dapat kembali ke tampilan gulir berkode kustom. Saya merekomendasikan untuk tidak melakukannya jika memungkinkan; menggunakan UIScrollView memastikan Anda mendapatkan tampilan dan nuansa asli, tidak peduli bagaimana perkembangannya di versi OS iPhone mendatang.

Oke, esai kecil ini sedikit terlalu panjang. Saya baru saja menyukai game UIScrollView setelah mengerjakan ScrollingMadness beberapa hari yang lalu.

NB Jika Anda mendapatkan salah satu dari ini berfungsi dan ingin berbagi, silakan kirimi saya kode yang relevan di [email protected], saya akan dengan senang hati memasukkannya ke dalam tas trik ScrollingMadness saya .

PPS Menambahkan esai kecil ini ke ScrollingMadness README.

Andrey Tarantsov
sumber
7
Perhatikan bahwa perilaku scrollview bersarang ini berfungsi di luar kotak sekarang di SDK, tidak perlu bisnis lucu
andygeers
1
Memberi +1 pada komentar andygeers, lalu menyadari bahwa itu tidak akurat; Anda masih dapat "merusak" UIScrollView biasa dengan menyeret secara diagonal dari titik awal dan kemudian Anda telah "menarik penggulung hilang" dan itu akan mengabaikan kunci arah sampai Anda melepaskan dan akhirnya entah di mana.
Kalle
Terima kasih atas penjelasan di balik layar yang luar biasa! Saya menggunakan solusi Mattias Wadman di bawah ini yang terlihat sedikit kurang invasif.
Ortwin Gentz
Akan sangat bagus untuk melihat jawaban ini diperbarui sekarang karena UIScrollView memperlihatkan pengenal gerakan pannya. (Tapi mungkin itu di luar cakupan aslinya.)
jtbandes
Apakah ada kemungkinan posting ini diperbarui? Posting yang bagus dan terima kasih atas masukan Anda.
Pavan
56

Ini bekerja untuk saya setiap saat ...

scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * NumberOfPages, 1);
Tonetel
sumber
20
Bagi orang lain yang tersandung pada pertanyaan ini: Saya pikir jawaban ini telah diposting sebelum pengeditan yang mengklarifikasi pertanyaan tersebut. Ini akan memungkinkan Anda untuk hanya menggulir secara horizontal, ini tidak mengatasi masalah kemampuan untuk menggulir secara vertikal atau horizontal tetapi tidak secara diagonal.
andygeers
Bekerja seperti pesona bagi saya. Saya memiliki scrollView horizontal yang sangat panjang dengan paging, dan UIWebView di setiap halaman, yang menangani pengguliran vertikal yang saya butuhkan.
Tom Redman
Luar biasa! Tapi adakah yang bisa menjelaskan cara kerjanya (menyetel tinggi kontenSize ke 1)!
Praveen
Ini benar-benar berhasil, tetapi mengapa dan bagaimana cara kerjanya? Adakah yang bisa memahaminya?
Marko Francekovic
27

Untuk orang malas seperti saya:

Setel scrollview.directionalLockEnabledkeYES

- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

- (void) scrollViewDidScroll: (UIScrollView *) scrollView
{
    if (scrollView.contentOffset.x != self.oldContentOffset.x)
    {
        scrollView.pagingEnabled = YES;
        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x,
                                               self.oldContentOffset.y);
    }
    else
    {
        scrollView.pagingEnabled = NO;
    }
}

- (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}
icompot.dll
sumber
16

Saya menggunakan metode berikut untuk mengatasi masalah ini, semoga bisa membantu Anda.

Pertama, tentukan variabel berikut di file header pengontrol Anda.

CGPoint startPos;
int     scrollDirection;

startPos akan menyimpan nilai contentOffset saat delegasi Anda menerima pesan scrollViewWillBeginDragging . Jadi dalam metode ini kami melakukan ini;

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    startPos = scrollView.contentOffset;
    scrollDirection=0;
}

kemudian kami menggunakan nilai-nilai ini untuk menentukan arah gulir yang diinginkan pengguna dalam pesan scrollViewDidScroll .

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

   if (scrollDirection==0){//we need to determine direction
       //use the difference between positions to determine the direction.
       if (abs(startPos.x-scrollView.contentOffset.x)<abs(startPos.y-scrollView.contentOffset.y)){          
          NSLog(@"Vertical Scrolling");
          scrollDirection=1;
       } else {
          NSLog(@"Horitonzal Scrolling");
          scrollDirection=2;
       }
    }
//Update scroll position of the scrollview according to detected direction.     
    if (scrollDirection==1) {
       [scrollView setContentOffset:CGPointMake(startPos.x,scrollView.contentOffset.y) animated:NO];
    } else if (scrollDirection==2){
       [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x,startPos.y) animated:NO];
    }
 }

akhirnya kita harus menghentikan semua operasi pembaruan ketika pengguna akhirnya menyeret;

 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    if (decelerate) {
       scrollDirection=3;
    }
 }
Yildiray Meric
sumber
Hmm ... Sebenarnya, setelah penyelidikan lebih lanjut - ini bekerja dengan cukup baik, tetapi masalahnya adalah kehilangan kendali arah selama fase perlambatan - jadi jika Anda mulai menggerakkan jari Anda secara diagonal maka itu akan tampak hanya bergerak secara horizontal atau vertikal sampai Anda melepaskannya di titik mana itu akan melambat dalam arah diagonal untuk sementara waktu
andygeers
@andygeers, mungkin akan bekerja lebih baik jika - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (decelerate) { scrollDirection=3; } }diubah menjadi - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (!decelerate) { scrollDirection=0; } }dan- (void)scrollViewDidEndDecelerating{ scrollDirection=0; }
cduck
Tidak berhasil untuk saya. Menyetel contentOffset dalam penerbangan tampaknya menghancurkan perilaku paging. Saya mengikuti solusi Mattias Wadman.
Ortwin Gentz
13

Saya mungkin sedang menggali di sini, tetapi saya tersandung ke posting ini hari ini karena saya mencoba untuk memecahkan masalah yang sama. Inilah solusi saya, tampaknya berfungsi dengan baik.

Saya ingin menampilkan layar penuh konten, tetapi mengizinkan pengguna untuk menggulir ke salah satu dari 4 layar lainnya, atas kiri bawah dan kanan. Saya memiliki satu UIScrollView dengan ukuran 320x480 dan contentSize 960x1440. Offset konten dimulai pada 320.480. Di scrollViewDidEndDecelerating :, saya menggambar ulang 5 tampilan (tampilan tengah dan 4 di sekitarnya), dan mengatur ulang konten offset ke 320.480.

Inilah inti dari solusi saya, metode scrollViewDidScroll :.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{   
    if (!scrollingVertically && ! scrollingHorizontally)
    {
        int xDiff = abs(scrollView.contentOffset.x - 320);
        int yDiff = abs(scrollView.contentOffset.y - 480);

        if (xDiff > yDiff)
        {
            scrollingHorizontally = YES;
        }
        else if (xDiff < yDiff)
        {
            scrollingVertically = YES;
        }
    }

    if (scrollingHorizontally)
    {
        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, 480);
    }
    else if (scrollingVertically)
    {
        scrollView.contentOffset = CGPointMake(320, scrollView.contentOffset.y);
    }
}
James J.
sumber
Anda mungkin akan menemukan bahwa kode ini tidak "melambat" dengan baik ketika pengguna melepaskan tampilan gulir - itu akan berhenti mati. Jika Anda tidak menginginkan perlambatan, ini mungkin baik untuk Anda.
andygeers
4

Guys itu sesederhana membuat tinggi subview sama dengan tinggi tampilan gulir dan tinggi ukuran konten. Kemudian pengguliran vertikal dinonaktifkan.

julianlubenov.dll
sumber
2
Bagi orang lain yang tersandung pada pertanyaan ini: Saya pikir jawaban ini telah diposting sebelum pengeditan yang mengklarifikasi pertanyaan tersebut. Ini akan memungkinkan Anda untuk hanya menggulir secara horizontal, itu tidak mengatasi masalah kemampuan untuk menggulir secara vertikal atau horizontal tetapi tidak secara diagonal.
andygeers
3

Berikut adalah implementasi saya dari halaman UIScrollViewyang hanya memungkinkan gulungan ortogonal. Pastikan untuk menyetel pagingEnabledke YES.

PagedOrthoScrollView.h

@interface PagedOrthoScrollView : UIScrollView
@end

PagedOrthoScrollView.m

#import "PagedOrthoScrollView.h"

typedef enum {
  PagedOrthoScrollViewLockDirectionNone,
  PagedOrthoScrollViewLockDirectionVertical,
  PagedOrthoScrollViewLockDirectionHorizontal
} PagedOrthoScrollViewLockDirection; 

@interface PagedOrthoScrollView ()
@property(nonatomic, assign) PagedOrthoScrollViewLockDirection dirLock;
@property(nonatomic, assign) CGFloat valueLock;
@end

@implementation PagedOrthoScrollView
@synthesize dirLock;
@synthesize valueLock;

- (id)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self == nil) {
    return self;
  }

  self.dirLock = PagedOrthoScrollViewLockDirectionNone;
  self.valueLock = 0;

  return self;
}

- (void)setBounds:(CGRect)bounds {
  int mx, my;

  if (self.dirLock == PagedOrthoScrollViewLockDirectionNone) {
    // is on even page coordinates, set dir lock and lock value to closest page 
    mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds));
    if (mx != 0) {
      self.dirLock = PagedOrthoScrollViewLockDirectionHorizontal;
      self.valueLock = (round(CGRectGetMinY(bounds) / CGRectGetHeight(self.bounds)) *
                        CGRectGetHeight(self.bounds));
    } else {
      self.dirLock = PagedOrthoScrollViewLockDirectionVertical;
      self.valueLock = (round(CGRectGetMinX(bounds) / CGRectGetWidth(self.bounds)) *
                        CGRectGetWidth(self.bounds));
    }

    // show only possible scroll indicator
    self.showsVerticalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionVertical;
    self.showsHorizontalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionHorizontal;
  }

  if (self.dirLock == PagedOrthoScrollViewLockDirectionHorizontal) {
    bounds.origin.y = self.valueLock;
  } else {
    bounds.origin.x = self.valueLock;
  }

  mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds));
  my = abs((int)CGRectGetMinY(bounds) % (int)CGRectGetHeight(self.bounds));

  if (mx == 0 && my == 0) {
    // is on even page coordinates, reset lock
    self.dirLock = PagedOrthoScrollViewLockDirectionNone;
  }

  [super setBounds:bounds];
}

@end
Mattias Wadman
sumber
1
Berfungsi bagus, juga di iOS 7. Terima kasih!
Ortwin Gentz
3

Saya juga harus menyelesaikan masalah ini, dan meskipun jawaban Andrey Tarantsov jelas memiliki banyak informasi yang berguna untuk memahami cara UIScrollViewskerja, saya merasa solusinya agak terlalu rumit. Solusi saya, yang tidak melibatkan subclass atau penerusan sentuh apa pun, adalah sebagai berikut:

  1. Buat dua tampilan gulir bersarang, satu untuk setiap arah.
  2. Buat dua boneka UIPanGestureRecognizers, satu lagi untuk setiap arah.
  3. Buat ketergantungan kegagalan antara properti UIScrollViews'panGestureRecognizer dan dummy yang UIPanGestureRecognizersesuai dengan arah tegak lurus ke arah pengguliran yang diinginkan UIScrollView Anda dengan menggunakan UIGestureRecognizer's -requireGestureRecognizerToFail:selector.
  4. Dalam -gestureRecognizerShouldBegin:callback untuk dummy UIPanGestureRecognizers, hitung arah awal pan menggunakan -translationInView: selector dari isyarat (Anda UIPanGestureRecognizerstidak akan memanggil callback delegasi ini sampai sentuhan Anda telah diterjemahkan cukup untuk mendaftar sebagai pan). Izinkan atau larang pengenal gerakan Anda untuk memulai tergantung pada arah yang dihitung, yang pada gilirannya akan mengontrol apakah pengenal gerakan pan UIScrollView tegak lurus akan diizinkan untuk memulai atau tidak.
  5. Dalam -gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:, jangan biarkan boneka Anda UIPanGestureRecognizersberjalan bersamaan dengan pengguliran (kecuali Anda memiliki beberapa perilaku tambahan yang ingin Anda tambahkan).
Kurt Horimoto
sumber
2

Apa yang saya lakukan adalah membuat paging UIScrollView dengan bingkai seukuran layar tampilan dan mengatur ukuran konten ke lebar yang dibutuhkan untuk semua konten saya dan tinggi 1 (terima kasih Tonetel). Kemudian saya membuat menu halaman UIScrollView dengan bingkai yang diatur ke setiap halaman, ukuran tampilan layar, dan mengatur ukuran konten masing-masing dengan lebar layar dan tinggi yang diperlukan untuk masing-masing. Kemudian saya hanya menambahkan setiap halaman menu ke tampilan paging. Pastikan paging diaktifkan pada tampilan gulir paging tetapi dinonaktifkan pada tampilan menu (harus dinonaktifkan secara default) dan Anda sebaiknya melakukannya.

Tampilan gulir paging sekarang menggulir secara horizontal, tetapi tidak vertikal karena ketinggian konten. Setiap halaman di-scroll secara vertikal tetapi tidak horizontal karena batasan ukuran kontennya juga.

Inilah kodenya jika penjelasan itu meninggalkan sesuatu yang diinginkan:

UIScrollView *newPagingScrollView =  [[UIScrollView alloc] initWithFrame:self.view.bounds]; 
[newPagingScrollView setPagingEnabled:YES];
[newPagingScrollView setShowsVerticalScrollIndicator:NO];
[newPagingScrollView setShowsHorizontalScrollIndicator:NO];
[newPagingScrollView setDelegate:self];
[newPagingScrollView setContentSize:CGSizeMake(self.view.bounds.size.width * NumberOfDetailPages, 1)];
[self.view addSubview:newPagingScrollView];

float pageX = 0;

for (int i = 0; i < NumberOfDetailPages; i++)
{               
    CGRect pageFrame = (CGRect) 
    {
        .origin = CGPointMake(pageX, pagingScrollView.bounds.origin.y), 
        .size = pagingScrollView.bounds.size
    };
    UIScrollView *newPage = [self createNewPageFromIndex:i ToPageFrame:pageFrame]; // newPage.contentSize custom set in here        
    [pagingScrollView addSubview:newPage];

    pageX += pageFrame.size.width;  
}           
Jon Conner
sumber
2

Terima kasih Tonetel. Saya sedikit mengubah pendekatan Anda, tetapi itulah yang saya butuhkan untuk mencegah pengguliran horizontal.

self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 1);
Jeremy
sumber
2

Ini adalah solusi icompot yang ditingkatkan, yang cukup tidak stabil bagi saya. Yang ini berfungsi dengan baik dan mudah diterapkan:

- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

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

    float XOffset = fabsf(self.oldContentOffset.x - scrollView.contentOffset.x );
    float YOffset = fabsf(self.oldContentOffset.y - scrollView.contentOffset.y );

        if (scrollView.contentOffset.x != self.oldContentOffset.x && (XOffset >= YOffset) )
        {

            scrollView.pagingEnabled = YES;
            scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x,
                                                   self.oldContentOffset.y);

        }
        else
        {
            scrollView.pagingEnabled = NO;
               scrollView.contentOffset = CGPointMake( self.oldContentOffset.x,
                                                   scrollView.contentOffset.y);
        }

}


- (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}
Pengembang Seluler
sumber
2

Untuk referensi di masa mendatang, saya menggunakan jawaban Andrey Tanasov dan menemukan solusi berikut yang berfungsi dengan baik.

Pertama, tentukan variabel untuk menyimpan contentOffset dan setel ke koordinat 0,0

var positionScroll = CGPointMake(0, 0)

Sekarang implementasikan yang berikut di delegasi scrollView:

override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
    // Note that we are getting a reference to self.scrollView and not the scrollView referenced passed by the method
    // For some reason, not doing this fails to get the logic to work
    self.positionScroll = (self.scrollView?.contentOffset)!
}

override func scrollViewDidScroll(scrollView: UIScrollView) {

    // Check that contentOffset is not nil
    // We do this because the scrollViewDidScroll method is called on viewDidLoad
    if self.scrollView?.contentOffset != nil {

        // The user wants to scroll on the X axis
        if self.scrollView?.contentOffset.x > self.positionScroll.x || self.scrollView?.contentOffset.x < self.positionScroll.x {

            // Reset the Y position of the scrollView to what it was BEFORE scrolling started
            self.scrollView?.contentOffset = CGPointMake((self.scrollView?.contentOffset.x)!, self.positionScroll.y);
            self.scrollView?.pagingEnabled = true
        }

        // The user wants to scroll on the Y axis
        else {
            // Reset the X position of the scrollView to what it was BEFORE scrolling started
            self.scrollView?.contentOffset = CGPointMake(self.positionScroll.x, (self.scrollView?.contentOffset.y)!);
            self.collectionView?.pagingEnabled = false
        }

    }

}

Semoga ini membantu.

Benjamin
sumber
1

Dari dokumen:

"nilai defaultnya adalah NO, yang berarti bahwa pengguliran diizinkan dalam arah horizontal dan vertikal. Jika nilainya YA dan pengguna mulai menyeret ke satu arah umum (horizontal atau vertikal), tampilan gulir menonaktifkan pengguliran ke arah lain. "

Saya pikir bagian penting adalah "jika ... pengguna mulai menyeret ke satu arah umum". Jadi jika mereka mulai menyeret secara diagonal, ini tidak berlaku. Bukan berarti dokumen ini selalu dapat diandalkan dalam hal interpretasi - tetapi tampaknya sesuai dengan apa yang Anda lihat.

Ini sebenarnya tampak masuk akal. Saya harus bertanya - mengapa Anda ingin membatasi hanya pada horizontal atau vertikal saja? Mungkin UIScrollView bukan alat untuk Anda?

philsquared
sumber
1

Tampilan saya terdiri dari 10 tampilan horizontal, dan beberapa dari tampilan tersebut terdiri dari beberapa tampilan vertikal, jadi Anda akan mendapatkan tampilan seperti ini:


1.0   2.0   3.1    4.1
      2.1   3.1
      2.2
      2.3

Dengan paging diaktifkan pada sumbu horizontal dan vertikal

Tampilan juga memiliki atribut berikut:

[self setDelaysContentTouches:NO];
[self setMultipleTouchEnabled:NO];
[self setDirectionalLockEnabled:YES];

Sekarang untuk mencegah pengguliran diagonal lakukan ini:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
    int pageWidth = 768;
    int pageHeight = 1024;
    int subPage =round(self.contentOffset.y / pageHeight);
    if ((int)self.contentOffset.x % pageWidth != 0 && (int)self.contentOffset.y % pageHeight != 0) {
        [self setContentOffset:CGPointMake(self.contentOffset.x, subPage * pageHeight];
    } 
}

Bekerja seperti pesona bagi saya!

pengguna422756
sumber
1
tidak mengerti bagaimana ini bisa berhasil untuk Anda ... bisakah Anda mengunggah proyek sampel?
hfossli
1

Perlu mengatur ulang _isHorizontalScrollke NO di touchesEndeddan touchesCancelled.

Praveen Matanam
sumber
0

Jika Anda belum mencoba melakukan ini dalam versi 3.0 beta, saya sarankan Anda mencobanya. Saat ini saya menggunakan serangkaian tampilan tabel di dalam tampilan gulir, dan berfungsi seperti yang diharapkan. (Yah, bagaimanapun juga penggulirannya. Temukan pertanyaan ini ketika mencari perbaikan untuk masalah yang sama sekali berbeda ...)

Tim Keating
sumber
0

Bagi mereka yang mencari Swift di solusi kedua @AndreyTarantsov, seperti ini dalam kode:

    var positionScroll:CGFloat = 0

    func scrollViewWillBeginDragging(scrollView: UIScrollView) {
        positionScroll = self.theScroll.contentOffset.x
    }

    func scrollViewDidScroll(scrollView: UIScrollView) {
        if self.theScroll.contentOffset.x > self.positionScroll || self.theScroll.contentOffset.x < self.positionScroll{
             self.theScroll.pagingEnabled = true
        }else{
            self.theScroll.pagingEnabled = false
        }
    }

apa yang kita lakukan di sini adalah untuk menjaga posisi saat ini sebelum scrollview diseret dan kemudian memeriksa apakah x meningkat atau menurun dibandingkan dengan posisi x saat ini. Jika ya, setel pagingEnabled ke true, jika tidak (y telah dinaikkan / diturunkan) setel ke pagingEnabled menjadi false

Semoga bermanfaat bagi pendatang baru!

jonprasetyo
sumber
0

Saya berhasil menyelesaikan ini dengan menerapkan scrollViewDidBeginDragging (_ :) dan melihat kecepatan pada UIPanGestureRecognizer yang mendasarinya.

NB: Dengan solusi ini, setiap pan yang akan diagonal akan diabaikan oleh scrollView.

override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    super.scrollViewWillBeginDragging(scrollView)
    let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView)
    if velocity.x != 0 && velocity.y != 0 {
        scrollView.panGestureRecognizer.isEnabled = false
        scrollView.panGestureRecognizer.isEnabled = true
    }
}
Morten Saabye Kristensen
sumber
-3

Jika Anda menggunakan pembuat antarmuka untuk mendesain antarmuka Anda - ini dapat dilakukan dengan mudah.

Dalam pembuat antarmuka cukup klik UIScrollView dan pilih opsi (di inspektur) yang membatasi hanya untuk pengguliran satu arah.

zpesk
sumber
1
Tidak ada pilihan di IB untuk melakukan ini. Dua opsi yang mungkin Anda maksud berkaitan dengan tampilan bilah gulir, bukan pengguliran sebenarnya. Selanjutnya, Kunci Arah hanya mengunci arah setelah pengguliran dimulai.
Sophtware