UIScrollView: yatay olarak sayfalama, dikey kaydırma?


89

UIScrollViewSayfalama ve kaydırmanın açık olduğu bir anda belirli bir anda yalnızca dikey veya yatay olarak hareket etmeye nasıl zorlayabilirim ?

Anladığım kadarıyla, directionalLockEnabledözelliğin bunu başarması gerekir, ancak çapraz bir kaydırma hareketi, hareketi tek bir eksene sınırlamak yerine yine de görünümün çapraz olarak kaydırılmasına neden olur.

Düzenleme: Daha açık olmak gerekirse, kullanıcının yatay VEYA dikey olarak kaydırmasına izin vermek istiyorum, ancak her ikisini aynı anda değil.


Bir görünümü YALNIZCA Yatay / Dikey kaydırılacak şekilde sınırlamak mı yoksa kullanıcının Yatay VEYA Dikey kaydırmasını mı istiyorsunuz, ancak her ikisini aynı anda değil mi?
Will Hartung

Bana bir iyilik yapıp başlığı biraz daha ilginç ve önemsiz görünecek şekilde değiştirebilir misin? "UIScrollView: yatay olarak sayfalama, dikey olarak kaydırma" gibi bir şey
Andrey Tarantsov

Ne yazık ki soruyu artık erişemediğim bir bilgisayarda kayıtlı olmayan bir kullanıcı olarak gönderdim (eve geldiğimde aynı adla kaydolmadan önce), bu da onu düzenleyemeyeceğim anlamına geliyor. Bu aynı zamanda orijinal soruyu düzenlemek yerine aşağıdan aşağıya bakmamı da açıklamalı. Bu sefer Yığın görgü kurallarını bozduğum için özür dilerim!
Nick

Yanıtlar:


117

Çok zor bir durumdasın, söylemeliyim.

pagingEnabled=YESSayfalar arasında geçiş yapmak için bir UIScrollView kullanmanız gerektiğini , ancak pagingEnabled=NOdikey olarak kaydırmanız gerektiğini unutmayın .

2 olası strateji vardır. Hangisinin işe yarayacağını / uygulanması daha kolay olduğunu bilmiyorum, bu yüzden ikisini de deneyin.

İlk: yuvalanmış UIScrollViews. Açıkçası, bu işe sahip olan birini henüz görmedim. Ancak kişisel olarak yeterince denemedim ve benim pratiğim, yeterince denediğinizde UIScrollView'ın istediğiniz her şeyi yapmasını sağlayabileceğinizi gösteriyor .

Dolayısıyla strateji, dış kaydırma görünümünün yalnızca yatay kaydırmayı ve iç kaydırma görünümlerinin yalnızca dikey kaydırmayı işlemesine izin vermektir. Bunu başarmak için, UIScrollView'ün dahili olarak nasıl çalıştığını bilmeniz gerekir. hitTestYöntemi geçersiz kılar ve her zaman kendisini döndürür, böylece tüm dokunma olayları UIScrollView'a gider. Sonra içeride touchesBegan, touchesMovedvb. Olayla ilgilenip ilgilenmediğini kontrol eder ve onu ya idare eder ya da iç bileşenlere aktarır .

Dokunmanın işlenip yönlendirilmeyeceğine karar vermek için, UIScrollView ilk dokunduğunuzda bir zamanlayıcı başlatır:

  • Parmağınızı 150 ms içinde önemli ölçüde hareket ettirmediyseniz, olayı iç görünüme aktarır.

  • Parmağınızı 150 ms içinde önemli ölçüde hareket ettirdiyseniz, kaydırmaya başlar (ve olayı asla iç görünüme geçirmez).

    Bir tabloya (kaydırma görünümünün alt sınıfı olan) dokunduğunuzda ve hemen kaydırmaya başladığınızda, dokunduğunuz satırın asla vurgulanmayacağına dikkat edin.

  • Eğer varsa değil 150ms içinde önemli ölçüde parmağınızı taşındı ve UIScrollView iç görünümüne olaylarının iletilmesi başladı ama sonra Başlamadan kaydırma, UIScrollView aramaları için yeterince parmak taşındı touchesCancelledkaydırma iç görünümü ve başlar üstünde.

    Bir masaya dokunduğunuzda, parmağınızı biraz tuttuğunuzda ve ardından kaydırmaya başladığınızda, dokunduğunuz satırın önce vurgulandığını, ancak daha sonra vurgusunun kaldırıldığını unutmayın.

Bu olay dizisi, UIScrollView konfigürasyonu ile değiştirilebilir:

  • Eğer delaysContentTouchesolaylar hemen iç kontrole gitmek (ama yeterince parmağınızı taşırsanız o zaman iptal edilir) - HAYIR, o zaman hiçbir zamanlayıcı kullanılır
  • Eğer cancelsTouchesHAYIR, sonra olaylar asla olmayacak kaydırma, bir kontrole gönderilir kez.

Not tarafımıza ulaşan UIScrollView olduğunu tüm touchesBegin , touchesMoved, touchesEndedve touchesCanceledCocoaTouch gelen olaylar (onun yüzünden hitTestsöyler bunu yapmak için). Daha sonra isterse, istediği sürece onları iç görünüme yönlendirir.

Artık UIScrollView hakkında her şeyi bildiğinize göre, davranışını değiştirebilirsiniz. Bahse girerim, kullanıcı görünüme dokunduğunda ve parmağını hareket ettirmeye başladığında (biraz da olsa), görünüm dikey yönde kaydırmaya başlar; ancak kullanıcı parmağını yatay yönde yeterince hareket ettirdiğinde, dikey kaydırmayı iptal etmek ve yatay kaydırmaya başlamak istersiniz.

Dış UIScrollView'unuzu alt sınıflara ayırmak istiyorsunuz (örneğin, sınıfınıza RemorsefulScrollView adını verin), böylece varsayılan davranış yerine tüm olayları hemen iç görünüme iletir ve yalnızca önemli yatay hareket algılandığında kaydırılır.

RemorsefulScrollView'ın bu şekilde davranmasını nasıl sağlayabilirim?

  • Görünüşe göre dikey kaydırmayı devre dışı bırakmak ve delaysContentTouchesHAYIR olarak ayarlamak , yuvalanmış UIScrollViews çalışacak. Maalesef öyle değil; UIScrollView, hızlı hareketler (devre dışı bırakılamaz) için bazı ek filtreleme yapıyor gibi görünmektedir, böylece UIScrollView yalnızca yatay olarak kaydırılabilse bile, her zaman yeterince hızlı dikey hareketleri yer (ve yok sayar).

    Etki o kadar şiddetlidir ki, iç içe geçmiş bir kaydırma görünümü içinde dikey kaydırma kullanılamaz. (Tam olarak bu kuruluma sahip olduğunuz görülüyor, bu yüzden deneyin: parmağınızı 150ms boyunca tutun ve ardından dikey yönde hareket ettirin - yuvalanmış UIScrollView beklendiği gibi çalışır!)

  • Bu, olay işleme için UIScrollView kodunu kullanamayacağınız anlamına gelir; RemorsefulScrollView'daki dört dokunma işleme yönteminin tümünü geçersiz kılmanız ve önce kendi işleminizi yapmanız, yalnızca superyatay kaydırmaya karar verdiyseniz olayı (UIScrollView) 'a iletmeniz gerekir.

  • Ancak geçmek zorunda touchesBeganEğer (daha sonra bu karar eğer bir taban gelecek yatay kaydırma için koordinat hatırlamak istiyorum, çünkü, UIScrollView için olan bir yatay kaydırma). touchesBeganUIScrollView'a daha sonra gönderemezsiniz çünkü touchesbağımsız değişkeni saklayamazsınız : bir sonraki touchesMovedolaydan önce değiştirilecek nesneleri içerir ve eski durumu yeniden oluşturamazsınız.

    Bu yüzden touchesBeganhemen UIScrollView'a geçmelisiniz , ancak touchesMovedyatay olarak kaydırmaya karar verene kadar bundan sonraki olayları gizleyeceksiniz . Hayır touchesMoved, kaydırma yok anlamına gelir, bu nedenle bu baş touchesBeganharfin bir zararı olmaz. Ancak delaysContentTouchesHAYIR olarak ayarlayın , böylece ek sürpriz zamanlayıcılar müdahale etmez.

    (Dışı mesajlar - senin aksine, UIScrollView olabilir düzgün dokunuşlar saklamak ve yeniden ve orijinal iletebilirsiniz touchesBeganolay sonradan O yayınlanmamış API'leri kullanarak bir haksız avantaja sahiptir, bunlar mutasyona önce teneke klon dokunmatik nesneleri böylece..)

  • Hep ileriye göz önüne alındığında touchesBegan, aynı zamanda iletmek zorunda touchesCancelledve touchesEnded. Sen çevirmek zorunda touchesEndediçine touchesCancelledUIScrollView yorumlamak çünkü, ancak, touchesBegan, touchesEndeddokunmatik tıklama olarak diziyi ve iç görünümüne iletmeyi ederim. Halihazırda uygun olayları kendiniz iletiyorsunuz, bu nedenle UIScrollView'ın hiçbir şeyi iletmesini istemezsiniz.

Temel olarak işte yapmanız gerekenler için sözde kod. Basit olması için, çoklu dokunma olayı meydana geldikten sonra yatay kaydırmaya asla izin vermem.

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

Bunu çalıştırmayı ve hatta derlemeyi denemedim (ve tüm sınıfı bir düz metin düzenleyicisine yazdım), ancak yukarıdakilerle başlayabilir ve umarım çalıştırabilirsiniz.

Gördüğüm tek gizli yakalama, RemorsefulScrollView'a herhangi bir UIScrollView olmayan alt görünüm eklerseniz, çocuk UIScrollView gibi dokunuşları her zaman işlemiyorsa, çocuğa ilettiğiniz dokunma olayları yanıtlayıcı zinciri aracılığıyla size geri dönebilir. Kurşun geçirmez bir RemorsefulScrollView uygulaması touchesXxxyeniden girişe karşı koruma sağlar .

İkinci strateji: Eğer herhangi bir nedenle yuvalanmış UIScrollViews işe yaramazsa veya düzeltilmesi çok zor olursa, yalnızca bir UIScrollView ile anlaşmaya çalışabilir, pagingEnabledözelliğini scrollViewDidScrolltemsilci yönteminizden anında değiştirebilir .

Çapraz kaydırmayı önlemek için önce contentOffset in hatırlamayı scrollViewWillBeginDraggingve scrollViewDidScrollçapraz bir hareket algılarsanız contentOffset'i kontrol edip sıfırlamayı denemelisiniz . Denenecek başka bir strateji, kullanıcının parmağının hangi yöne gittiğine karar verdikten sonra, yalnızca bir yönde kaydırmayı etkinleştirmek için contentSize'yi sıfırlamaktır. (UIScrollView, delege yöntemlerinden contentSize ve contentOffset ile uğraşmak konusunda oldukça bağışlayıcı görünüyor.)

O özensiz görseller iş ya da sonuç değil ise, geçersiz kılma zorunda touchesBegan, touchesMovedvb ve UIScrollView değil ileri diyagonal hareket olaylar. (Ancak bu durumda kullanıcı deneyimi yetersiz olacaktır, çünkü onları tek bir yöne zorlamak yerine çapraz hareketleri görmezden gelmek zorunda kalacaksınız. Gerçekten maceraperest hissediyorsanız, RevengeTouch gibi kendi UITouch'ınızı benzer şekilde yazabilirsiniz. -C düz eski C'dir ve dünyada C'den daha basit bir şey yoktur; kimse nesnelerin gerçek sınıfını kontrol etmediği sürece, ki ben kimsenin yapmadığını düşünürüm, herhangi bir sınıfı diğer sınıflar gibi gösterebilirsiniz. İstediğiniz herhangi bir koordinatla istediğiniz dokunuşları sentezleme imkanı.)

Yedekleme stratejisi: Three20 kitaplığında UIScrollView'in mantıklı bir yeniden uygulaması olan TTScrollView var . Maalesef, kullanıcıya çok doğal ve telefonla uyuşmuyor. Ancak, her UIScrollView kullanma girişimi başarısız olursa, özel kodlanmış bir kaydırma görünümüne geri dönebilirsiniz. Mümkünse buna karşı tavsiye ederim; UIScrollView kullanmak, gelecekteki iPhone işletim sistemi sürümlerinde nasıl gelişirse gelişsin, yerel görünüm ve hissi elde etmenizi sağlar.

Tamam, bu küçük makale biraz fazla uzadı. Birkaç gün önce ScrollingMadness üzerinde çalıştıktan sonra hala UIScrollView oyunlarına girdim.

Not: Bu çalışmalardan herhangi birini alırsanız ve paylaşmak istediğinizi düşünüyorsanız, lütfen ilgili kodu andreyvit@gmail.com adresinden bana e-posta ile gönderin, bunu ScrollingMadness hile çantama eklemekten mutluluk duyarım .

PPS Bu küçük makaleyi ScrollingMadness README'ye eklemek.


7
Bu yuvalanmış kaydırma görünümü davranışının artık SDK'da kullanıma hazır olduğunu ve komik bir iş
yapmaya

1
Andygeers yorum yaptı, sonra bunun doğru olmadığını anladı; Hala başlangıç ​​noktasından çapraz olarak sürükleyerek normal UIScrollView'i "kırabilirsiniz" ve sonra "kaydırıcıyı kaybettiniz" ve siz bırakana ve tanrı bilir nerede sona erene kadar yönlü kilidi yoksayacaktır.
Kalle

Sahne arkası açıklamaları için teşekkürler! Mattias Wadman'ın biraz daha az istilacı IMO gibi görünen aşağıdaki çözümüyle gittim.
Ortwin Gentz

UIScrollView pan hareketi tanıyıcısını açığa çıkardığı için bu cevabın güncellendiğini görmek harika olurdu. (Ama belki de bu orijinal kapsamın dışında.)
jtbandes

Bu gönderinin güncellenme olasılığı var mı? Harika gönderi ve katkılarınız için teşekkür ederim.
Pavan

56

Bu benim için her zaman işe yarar ...

scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * NumberOfPages, 1);

20
Bu soruya tökezleyen herkes için: Sanırım bu yanıt, soruyu açıklığa kavuşturan düzenlemeden önce yayınlandı. Bu, yalnızca yatay kaydırma yapmanıza izin verir , çapraz olarak değil dikey veya yatay kaydırma yapabilme sorununu ele almaz.
andygeers

Benim için bir cazibe gibi çalışıyor. Sayfalandırmalı çok uzun bir yatay scrollView ve her sayfada ihtiyacım olan dikey kaydırmayı işleyen bir UIWebView var.
Tom Redman

Mükemmel! Ancak birisi bunun nasıl çalıştığını açıklayabilir (contentSize yüksekliğini 1 olarak ayarlayarak)!
Praveen

Gerçekten işe yarıyor, ama neden ve nasıl çalışıyor? Bundan bir anlam çıkaran var mı?
Marko Francekovic

27

Benim gibi tembel insanlar için:

Ayarlamak scrollview.directionalLockEnabled içinYES

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

16

Bu sorunu çözmek için aşağıdaki yöntemi kullandım, umarım size yardımcı olur.

Önce denetleyiciler başlık dosyanızda aşağıdaki değişkenleri tanımlayın.

CGPoint startPos;
int     scrollDirection;

StartPos , temsilciniz scrollViewWillBeginDragging mesajını aldığında contentOffset değerini koruyacaktır . Yani bu yöntemde bunu yapıyoruz;

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

daha sonra bu değerleri scrollViewDidScroll mesajında kullanıcıların amaçladığı kaydırma yönünü belirlemek için kullanırız .

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

son olarak, kullanıcı sürüklemeyi bitirdiğinde tüm güncelleme işlemlerini durdurmalıyız;

 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    if (decelerate) {
       scrollDirection=3;
    }
 }

Hmm ... Aslında, daha fazla araştırmadan sonra - oldukça iyi çalışıyor , ancak sorun, yavaşlama aşamasında yön kontrolünü kaybetmesidir - bu nedenle parmağınızı çapraz olarak hareket ettirmeye başlarsanız, o zaman sadece yatay veya dikey olarak hareket ediyormuş gibi görünecektir. siz bırakana kadar hangi noktada bir süre çapraz yönde yavaşlayacak
andygeers

@andygeers, - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (decelerate) { scrollDirection=3; } }olarak değiştirilirse daha iyi çalışabilir - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (!decelerate) { scrollDirection=0; } }ve- (void)scrollViewDidEndDecelerating{ scrollDirection=0; }
cduck

Benim için çalışmadı. ContentOffset'in uçuşta ayarlanması, sayfalama davranışını yok ediyor gibi görünüyor. Mattias Wadman'ın çözümüyle gittim.
Ortwin Gentz

13

Burada mezar kazıyor olabilirim, ancak bugün aynı sorunu çözmeye çalışırken bu yazıya rastladım. İşte benim çözümüm, harika çalışıyor gibi görünüyor.

Bir ekran dolusu içerik görüntülemek istiyorum, ancak kullanıcının sola ve sağa yukarı 4 diğer ekran dolabından birine gitmesine izin veriyorum. 320x480 boyutunda ve 960x1440 içerik boyutunda tek bir UIScrollView var. İçerik uzaklığı 320,480'de başlar. ScrollViewDidEndDecelerating'de: 5 görünümü yeniden çiziyorum (merkez görünümler ve etrafındaki 4) ve içerik ofsetini 320.480'e sıfırladım.

İşte benim çözümümün eti, scrollViewDidScroll: yöntemi.

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

Muhtemelen, kullanıcı kaydırma görünümünü bıraktığında bu kodun iyi bir şekilde "yavaşlamadığını" göreceksiniz - artık ölmeyecektir. Yavaşlamak istemiyorsanız, bu sizin için iyi olabilir.
andygeers

4

Çocuklar, alt görünüm yüksekliğini kaydırma görünümü yüksekliği ve içerik boyutu yüksekliğiyle aynı yapmak kadar basit. Ardından dikey kaydırma devre dışı bırakılır.


2
Bu soruya tökezleyen herkes için: Sanırım bu yanıt, soruyu açıklığa kavuşturan düzenlemeden önce yayınlandı. Bu, yalnızca yatay olarak kaydırmanıza izin verir, çapraz olarak değil, dikey veya yatay olarak kaydırma yapabilme sorununu ele almaz.
andygeers

3

İşte UIScrollViewsadece ortogonal kaydırmalara izin veren bir sayfalı uygulamam . Emin ayarlamak için Be pagingEnablediçin 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

1
İOS 7'de de harika çalışıyor. Teşekkürler!
Ortwin Gentz

3

Bu sorunu da çözmek zorunda kaldım ve Andrey Tarantsov'un cevabı kesinlikle nasıl UIScrollViewsçalıştığını anlamak için pek çok yararlı bilgiye sahipken , çözümün biraz fazla karmaşık olduğunu düşünüyorum. Herhangi bir alt sınıflandırma veya dokunmatik yönlendirme içermeyen çözümüm aşağıdaki gibidir:

  1. Her yön için bir tane olmak üzere iki iç içe kaydırma görünümü oluşturun.
  2. UIPanGestureRecognizersHer yön için yine birer tane olmak üzere iki kukla oluşturun .
  3. UIScrollViewsSeçiciyi kullanarak , 'panGestureRecognizer özellikleri ile UIPanGestureRecognizerUIScrollView'unuzun istenen kaydırma yönüne dik yöne karşılık gelen kukla arasında hata bağımlılıkları oluşturun UIGestureRecognizer's -requireGestureRecognizerToFail:.
  4. -gestureRecognizerShouldBegin:Sahte UIPanGestureRecognizers'ınız için geri aramalarda, hareketin -translationInView: selector (sizinUIPanGestureRecognizers dokunuşunuz pan olarak kaydedilecek kadar çevrilene kadar bu delege geri aramasını çağırmayacaksınız). Hareket tanıyıcınızın hesaplanan yöne bağlı olarak başlamasına izin verin veya izin vermeyin, bu da dikey UIScrollView pan hareketi tanıyıcının başlamasına izin verilip verilmeyeceğini kontrol etmelidir.
  5. İçinde -gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:, UIPanGestureRecognizerskuklanızın kaydırmanın yanında çalışmasına izin vermeyin (eklemek istediğiniz fazladan bir davranışınız yoksa).

2

Yaptığım şey, görünüm ekranının boyutunda bir çerçeveye sahip bir sayfalama UIScrollView oluşturmak ve içerik boyutunu tüm içeriğim için gereken genişliğe ve 1 yüksekliğe ayarlamaktı (teşekkürler Tonetel). Daha sonra, her sayfaya çerçeveli UIScrollView sayfaları oluşturdum, görünüm ekranının boyutu ve her birinin içerik boyutunu ekranın genişliğine ve her biri için gerekli yüksekliğe ayarladım. Ardından, menü sayfalarının her birini sayfalama görünümüne ekledim. Sayfalama kaydırma görünümünde sayfalamanın etkinleştirildiğinden, ancak menü görünümlerinde devre dışı bırakıldığından (varsayılan olarak devre dışı bırakılmalıdır) emin olun ve gitmeniz iyi olur.

Sayfalandırma kaydırma görünümü artık yatay olarak kaydırılıyor, ancak içerik yüksekliği nedeniyle dikey olarak kaydırılmıyor. Her sayfa dikey olarak kaydırılır, ancak içerik boyutu kısıtlamaları nedeniyle yatay olarak değil.

Bu açıklama istenen bir şeyi bıraktıysa, kod burada:

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

2

Teşekkürler Tonetel. Yaklaşımınızı biraz değiştirdim, ancak yatay kaydırmayı önlemek için tam olarak ihtiyacım olan şey buydu.

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

2

Bu, benim için oldukça dengesiz olan gelişmiş bir icompot çözümü. Bu iyi çalışıyor ve uygulanması kolaydır:

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

2

Gelecekte referans olması için Andrey Tanasov'un cevabını temel aldım ve iyi çalışan aşağıdaki çözümü buldum.

Önce contentOffset'i depolamak için bir değişken tanımlayın ve bunu 0,0 koordinatına ayarlayın

var positionScroll = CGPointMake(0, 0)

Şimdi scrollView temsilcisine aşağıdakileri uygulayın:

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
        }

    }

}

Bu yardımcı olur umarım.


1

Dokümanlardan:

"varsayılan değer HAYIR'dır, bu da hem yatay hem de dikey yönde kaydırmaya izin verildiği anlamına gelir. Değer EVET ise ve kullanıcı bir genel yönde (yatay veya dikey) sürüklemeye başlarsa, kaydırma görünümü diğer yönde kaydırmayı devre dışı bırakır. "

Ben kullanıcı eğer ... önemli bir parçası" olduğunu düşünüyorum başlar bir genel yönde sürükleyerek". Yani, çapraz olarak sürüklemeye başlarlarsa, bu işe yaramaz. Konu yorumlama olduğunda bu dokümanlar her zaman güvenilir değildir - ama bu gördüğünüz şeye uyuyor gibi görünüyor.

Bu aslında mantıklı görünüyor. Sormak zorundayım - neden herhangi bir zamanda yalnızca yatay veya yalnızca dikey olarak sınırlandırmak istiyorsunuz? Belki de UIScrollView sizin için bir araç değildir?


1

Benim görüşüm 10 yatay görünümden oluşuyor ve bu görünümlerden bazıları birden çok dikey görünümden oluşuyor, bu nedenle şuna benzer bir şey elde edeceksiniz:


1.0   2.0   3.1    4.1
      2.1   3.1
      2.2
      2.3

Hem yatay hem de dikey eksende sayfalama etkinken

Görünüm ayrıca aşağıdaki özniteliklere sahiptir:

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

Şimdi çapraz kaydırmayı önlemek için şunu yapın:

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

Benim için bir charme gibi çalışıyor!


1
bunun sizin için nasıl çalıştığını anlamıyorum ... örnek bir proje yükleyebilir misiniz?
hfossli

1

Ve _isHorizontalScrolliçinde NO olarak sıfırlanmalıdır .touchesEndedtouchesCancelled



0

Bunu 3.0 beta sürümünde yapmayı denemediyseniz, denemenizi tavsiye ederim. Şu anda bir kaydırma görünümü içinde bir dizi tablo görünümü kullanıyorum ve beklendiği gibi çalışıyor. (Her neyse, kaydırma yapar. Tamamen farklı bir soruna çözüm ararken bu soruyu buldum ...)


0

Swift'i @AndreyTarantsov'da arayanlar için ikinci çözüm kodda şöyle:

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

burada yaptığımız şey, kaydırma görünümü sürüklenmeden hemen önce mevcut konumu korumak ve ardından x'in mevcut konumuna kıyasla x'in arttığını veya azaldığını kontrol etmektir. Evetse, pagingEnabled değerini true, hayır ise (y arttı / azaldı), ardından paging olarak ayarlayınEnabled öğesini false olarak ayarlayın.

Umarım yeni gelenler için yararlıdır!


0

ScrollViewDidBeginDragging (_ :) uygulayarak ve temeldeki UIPanGestureRecognizer üzerindeki hıza bakarak bunu çözmeyi başardım.

Not: Bu çözümle, köşegen olabilecek her pan, scrollView tarafından göz ardı edilecektir.

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

-3

Arayüzünüzü tasarlamak için arayüz oluşturucuyu kullanıyorsanız - bu oldukça kolay bir şekilde yapılabilir.

Arayüz oluşturucuda, UIScrollView'a tıklayın ve onu yalnızca tek yönlü kaydırmayla sınırlayan seçeneği (denetçiden) seçin.


1
IB'de bunu yapmak için seçenek yoktur. Bahsettiğiniz iki seçenek, gerçek kaydırma ile değil, kaydırma çubuğunun görüntülenmesi ile ilgisi olabilir. Dahası, Yön Kilidi, sadece kaydırma başladığında yönü kilitler.
Sophtware
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.