UIScrollView: เพจในแนวนอนเลื่อนในแนวตั้ง?


89

ฉันจะบังคับให้UIScrollViewเพจและการเลื่อนที่เปิดอยู่เพื่อเลื่อนเฉพาะในแนวตั้งหรือแนวนอนในช่วงเวลาที่กำหนดได้อย่างไร

ความเข้าใจของฉันคือdirectionalLockEnabledคุณสมบัติควรบรรลุสิ่งนี้ แต่การปัดในแนวทแยงยังคงทำให้มุมมองเลื่อนตามแนวทแยงมุมแทนที่จะ จำกัด การเคลื่อนไหวให้อยู่ที่แกนเดียว

แก้ไข: เพื่อให้ชัดเจนยิ่งขึ้นฉันต้องการอนุญาตให้ผู้ใช้เลื่อนในแนวนอนหรือแนวตั้ง แต่ไม่ใช่ทั้งสองอย่างพร้อมกัน


คุณต้องการ จำกัด มุมมองให้เลื่อนเฉพาะแนวนอน / แนวนอนหรือคุณต้องการให้ผู้ใช้เลื่อนแนวนอนหรือแนวนอน แต่ไม่ใช่ทั้งสองอย่างพร้อมกัน?
Will Hartung

คุณช่วยฉันและเปลี่ยนชื่อเรื่องเพื่อให้ดูน่าสนใจและไม่น่าสนใจมากขึ้นได้ไหม บางอย่างเช่น“ UIScrollView: การเพจในแนวนอนเลื่อนในแนวตั้ง?
Andrey Tarantsov

ขออภัยฉันโพสต์คำถามในฐานะผู้ใช้ที่ไม่ได้ลงทะเบียนบนคอมพิวเตอร์ที่ฉันไม่สามารถเข้าถึงได้อีกต่อไป (ก่อนลงทะเบียนด้วยชื่อเดิมเมื่อฉันกลับถึงบ้าน) ซึ่งหมายความว่าฉันไม่สามารถแก้ไขได้ สิ่งนี้ควรอธิบายฉันด้วยการติดตามลงล่างแทนที่จะแก้ไขคำถามเดิม ขอโทษที่ทำผิดมารยาทในกองซ้อนในครั้งนี้!
Nick

คำตอบ:


117

คุณอยู่ในสถานการณ์ที่ยากลำบากมากฉันต้องบอกว่า

โปรดทราบว่าคุณต้องใช้ UIScrollView pagingEnabled=YESเพื่อสลับระหว่างหน้าต่างๆ แต่คุณต้องpagingEnabled=NOเลื่อนในแนวตั้ง

มี 2 ​​กลยุทธ์ที่เป็นไปได้ ฉันไม่รู้ว่าอันไหนใช้งานได้ / ใช้งานง่ายกว่าดังนั้นลองทั้งสองอย่าง

ขั้นแรก: UIScrollViews ที่ซ้อนกัน ตรงไปตรงมาฉันยังไม่เห็นคนที่มีสิ่งนี้ในการทำงาน แต่ฉันไม่ได้พยายามอย่างหนักพอเองและแสดงให้เห็นว่าการปฏิบัติของฉันเมื่อคุณทำพยายามอย่างหนักพอที่คุณสามารถทำให้ UIScrollView ทำอะไรที่คุณต้องการ

ดังนั้นกลยุทธ์คือให้มุมมองการเลื่อนด้านนอกจัดการเฉพาะการเลื่อนในแนวนอนและมุมมองการเลื่อนด้านในเพื่อจัดการกับการเลื่อนแนวตั้งเท่านั้น คุณต้องรู้ว่า UIScrollView ทำงานอย่างไรภายใน มันจะลบล้างhitTestเมธอดและส่งกลับตัวเองเสมอเพื่อให้เหตุการณ์การสัมผัสทั้งหมดเข้าสู่ UIScrollView จากนั้นภายในtouchesBegan, touchesMovedฯลฯ มันตรวจสอบถ้ามันสนใจในเหตุการณ์ที่เกิดขึ้นและทั้งจับหรือผ่านมันไปยังส่วนประกอบภายใน

ในการตัดสินใจว่าจะต้องจัดการการสัมผัสหรือจะส่งต่อ UIScrollView จะเริ่มจับเวลาเมื่อคุณสัมผัสครั้งแรก:

  • หากคุณไม่ได้ขยับนิ้วมากนักภายใน 150 มิลลิวินาทีมันจะส่งต่อเหตุการณ์ไปยังมุมมองด้านใน

  • หากคุณขยับนิ้วอย่างมากภายใน 150 มิลลิวินาทีจะเริ่มเลื่อน (และไม่ส่งต่อเหตุการณ์ไปยังมุมมองด้านใน)

    สังเกตว่าเมื่อคุณแตะตาราง (ซึ่งเป็นคลาสย่อยของมุมมองการเลื่อน) และเริ่มเลื่อนทันทีแถวที่คุณแตะจะไม่ถูกไฮไลต์

  • หากคุณยังไม่ได้ย้ายนิ้วของคุณอย่างมีนัยสำคัญภายใน 150ms และ UIScrollView เริ่มผ่านเหตุการณ์ที่เกิดขึ้นไปยังมุมมองด้านใน แต่แล้วคุณได้ย้ายนิ้วไกลพอสำหรับการเลื่อนไปเริ่มต้น UIScrollView เรียกtouchesCancelledในมุมมองด้านในและเริ่มต้นการเลื่อน

    สังเกตว่าเมื่อคุณแตะโต๊ะให้ใช้นิ้วของคุณค้างไว้เล็กน้อยแล้วเริ่มเลื่อนแถวที่คุณแตะจะถูกไฮไลต์ก่อน

ลำดับเหตุการณ์เหล่านี้สามารถเปลี่ยนแปลงได้โดยการกำหนดค่าของ UIScrollView:

  • ถ้าdelaysContentTouchesไม่ใช่จะไม่มีการใช้ตัวจับเวลา - เหตุการณ์จะไปที่ตัวควบคุมด้านในทันที (แต่จะถูกยกเลิกหากคุณขยับนิ้วไปไกลพอ)
  • ถ้าcancelsTouchesไม่ใช่เมื่อเหตุการณ์ถูกส่งไปยังคอนโทรลแล้วการเลื่อนจะไม่เกิดขึ้น

หมายเหตุว่ามันเป็น UIScrollView ที่ได้รับทั้งหมด touchesBegin , touchesMoved, touchesEndedและtouchesCanceledกิจกรรมต่างๆจาก CocoaTouch (เพราะมันhitTestบอกให้ทำเช่นนั้น) จากนั้นส่งต่อไปยังมุมมองภายในหากต้องการตราบเท่าที่ต้องการ

ตอนนี้คุณรู้ทุกอย่างเกี่ยวกับ UIScrollView แล้วคุณสามารถปรับเปลี่ยนพฤติกรรมของมันได้ ฉันสามารถเดิมพันได้ว่าคุณต้องการให้ความสำคัญกับการเลื่อนในแนวตั้งดังนั้นเมื่อผู้ใช้แตะที่มุมมองและเริ่มขยับนิ้วของเขา (แม้เพียงเล็กน้อย) มุมมองจะเริ่มเลื่อนในแนวตั้ง แต่เมื่อผู้ใช้เลื่อนนิ้วในแนวนอนไปไกลพอคุณต้องการยกเลิกการเลื่อนแนวตั้งและเริ่มการเลื่อนในแนวนอน

คุณต้องการซับคลาส UIScrollView ภายนอกของคุณ (เช่นคุณตั้งชื่อคลาสของคุณว่า RemorsefulScrollView) ดังนั้นแทนที่จะเป็นพฤติกรรมเริ่มต้นมันจะส่งต่อเหตุการณ์ทั้งหมดไปยังมุมมองภายในทันทีและเมื่อตรวจพบการเคลื่อนไหวในแนวนอนที่สำคัญเท่านั้นมันจะเลื่อน

จะทำให้ RemorsefulScrollView ทำงานได้อย่างไร?

  • ดูเหมือนว่าการปิดใช้งานการเลื่อนแนวตั้งและการตั้งค่าdelaysContentTouchesเป็น NO ควรทำให้ UIScrollViews ที่ซ้อนกันทำงานได้ น่าเสียดายที่มันไม่; ดูเหมือนว่า UIScrollView จะทำการกรองเพิ่มเติมสำหรับการเคลื่อนไหวที่รวดเร็ว (ซึ่งไม่สามารถปิดใช้งานได้) ดังนั้นแม้ว่า UIScrollView จะสามารถเลื่อนได้เฉพาะในแนวนอน แต่ก็จะกินการเคลื่อนไหวในแนวตั้งที่เร็วพอ (และละเว้น) เสมอ

    เอฟเฟกต์รุนแรงมากจนไม่สามารถใช้การเลื่อนแนวตั้งภายในมุมมองการเลื่อนที่ซ้อนกันได้ (ดูเหมือนว่าคุณได้รับการตั้งค่านี้อย่างแน่นอนดังนั้นลองใช้: กดนิ้วค้างไว้ 150ms จากนั้นเลื่อนไปในแนวตั้ง - UIScrollView ที่ซ้อนกันจะทำงานตามที่คาดไว้!)

  • ซึ่งหมายความว่าคุณไม่สามารถใช้รหัสของ UIScrollView ในการจัดการเหตุการณ์ คุณต้องแทนที่วิธีการจัดการแบบสัมผัสทั้งสี่วิธีใน RemorsefulScrollView และทำการประมวลผลของคุณเองก่อนโดยจะส่งต่อเหตุการณ์ไปที่super(UIScrollView) เท่านั้นหากคุณตัดสินใจที่จะใช้การเลื่อนในแนวนอน

  • แต่คุณต้องผ่านtouchesBeganไป UIScrollView เพราะคุณต้องการที่จะจำฐานพิกัดสำหรับการเลื่อนแนวนอนในอนาคต (ถ้าคุณตัดสินใจในภายหลังว่ามันคือเลื่อนแนวนอน) คุณจะไม่สามารถส่งtouchesBeganไปยัง UIScrollView ในภายหลังได้เนื่องจากคุณไม่สามารถจัดเก็บtouchesอาร์กิวเมนต์ได้เนื่องจากมีวัตถุที่จะถูกกลายพันธุ์ก่อนtouchesMovedเหตุการณ์ถัดไปและคุณไม่สามารถสร้างสถานะเก่าได้

    ดังนั้นคุณต้องส่งผ่านtouchesBeganไปยัง UIScrollView ทันที แต่คุณจะซ่อนtouchesMovedเหตุการณ์เพิ่มเติมจากเหตุการณ์นั้นจนกว่าคุณจะตัดสินใจเลื่อนในแนวนอน ไม่ได้touchesMovedหมายความว่าไม่มีการเลื่อนดังนั้นการเริ่มต้นนี้touchesBeganจะไม่เป็นอันตราย แต่อย่าตั้งค่าdelaysContentTouchesเป็นไม่ใช่เพื่อไม่ให้ตัวจับเวลาแปลกใจเพิ่มเติมรบกวน

    (Offtopic - แตกต่างจากคุณ UIScrollView สามารถจัดเก็บการสัมผัสได้อย่างถูกต้องและสามารถสร้างซ้ำและส่งต่อtouchesBeganเหตุการณ์ดั้งเดิมในภายหลังได้ซึ่งมีข้อได้เปรียบที่ไม่เป็นธรรมในการใช้ API ที่ไม่ได้เผยแพร่ดังนั้นจึงสามารถโคลนวัตถุสัมผัสก่อนที่จะกลายพันธุ์ได้)

  • ระบุว่าคุณมักจะไปข้างหน้าtouchesBeganคุณยังมีการส่งต่อและtouchesCancelled touchesEndedคุณต้องเปิดtouchesEndedเข้าไปtouchesCancelledแต่เนื่องจาก UIScrollView จะแปลความหมายtouchesBegan, touchesEndedลำดับเป็นสัมผัสคลิกและจะส่งต่อไปยังมุมมองภายใน คุณกำลังส่งต่อเหตุการณ์ที่เหมาะสมด้วยตัวเองอยู่แล้วดังนั้นคุณจึงไม่ต้องการให้ UIScrollView ส่งต่อสิ่งใด ๆ

โดยทั่วไปนี่คือรหัสเทียมสำหรับสิ่งที่คุณต้องทำ เพื่อความง่ายฉันไม่อนุญาตให้มีการเลื่อนในแนวนอนหลังจากเหตุการณ์มัลติทัชเกิดขึ้น

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

ฉันไม่ได้พยายามเรียกใช้หรือแม้แต่รวบรวมสิ่งนี้ (และพิมพ์ทั้งชั้นในโปรแกรมแก้ไขข้อความธรรมดา) แต่คุณสามารถเริ่มต้นด้วยข้างต้นและหวังว่าจะทำให้มันใช้งานได้

สิ่งเดียวที่ฉันเห็นคือหากคุณเพิ่มมุมมองของเด็กที่ไม่ใช่ UIScrollView ลงใน RemorsefulScrollView เหตุการณ์การสัมผัสที่คุณส่งต่อไปยังเด็กอาจส่งกลับมาหาคุณผ่านทางลูกโซ่การตอบกลับหากเด็กไม่ได้จัดการกับการสัมผัสเสมอเหมือนที่ UIScrollView ทำ การใช้งาน RemorsefulScrollView แบบกันกระสุนจะป้องกันการtouchesXxxย้อนกลับ

กลยุทธ์ที่สอง:หาก UIScrollViews ซ้อนกันด้วยเหตุผลบางประการไม่ได้ผลหรือพิสูจน์ได้ยากเกินไปที่จะทำให้ถูกต้องคุณสามารถลองใช้ UIScrollView เพียงอันเดียวโดยเปลี่ยนpagingEnabledคุณสมบัติได้ทันทีจากscrollViewDidScrollวิธีการมอบหมายของคุณ

เพื่อป้องกันการเลื่อนในแนวทแยงคุณควรลองจดจำ contentOffset ก่อน scrollViewWillBeginDraggingและตรวจสอบและรีเซ็ต contentOffset ภายในscrollViewDidScrollหากคุณตรวจพบการเคลื่อนไหวในแนวทแยง อีกวิธีหนึ่งที่ควรลองคือการรีเซ็ต contentSize เพื่อเปิดใช้งานการเลื่อนในทิศทางเดียวเท่านั้นเมื่อคุณตัดสินใจว่านิ้วของผู้ใช้จะไปในทิศทางใด (ดูเหมือนว่า UIScrollView จะให้อภัยเกี่ยวกับการเล่นซอกับ contentSize และ contentOffset จากวิธีการมอบหมาย)

หากไม่ได้ทำงานหรือผลลัพธ์ในภาพเลอะเทอะคุณต้องแทนที่touchesBegan,touchesMovedฯลฯ และกิจกรรมการเคลื่อนไหวไม่ทแยงไปข้างหน้าเพื่อ UIScrollView (อย่างไรก็ตามประสบการณ์ของผู้ใช้จะไม่ดีพอในกรณีนี้เนื่องจากคุณจะต้องเพิกเฉยต่อการเคลื่อนไหวในแนวทแยงแทนที่จะบังคับให้ไปในทิศทางเดียวหากคุณรู้สึกอยากผจญภัยจริงๆคุณสามารถเขียน UITouch ที่มีลักษณะเหมือนกันได้เช่น RevengeTouch วัตถุประสงค์ -C เป็น C แบบเก่าธรรมดาและไม่มีอะไรที่น่าเบื่อในโลกนี้ไปกว่า C ตราบใดที่ไม่มีใครตรวจสอบคลาสที่แท้จริงของวัตถุซึ่งฉันเชื่อว่าไม่มีใครทำคุณสามารถทำให้คลาสใด ๆ ดูเหมือนคลาสอื่น ๆ ได้สิ่งนี้จะเปิดขึ้น ความเป็นไปได้ในการสังเคราะห์สัมผัสที่คุณต้องการด้วยพิกัดที่คุณต้องการ)

กลยุทธ์การสำรองข้อมูล:มี TTScrollView ซึ่งเป็นการนำ UIScrollView มาใช้ใหม่อย่างมีเหตุผลในไลบรารี Three20อย่างมีเหตุผล น่าเสียดายที่ผู้ใช้รู้สึกไม่เป็นธรรมชาติและไม่ใช้ไอโฟน แต่หากความพยายามในการใช้ UIScrollView ทุกครั้งล้มเหลวคุณสามารถถอยกลับไปใช้มุมมองแบบเลื่อนที่กำหนดเองได้ ฉันขอแนะนำให้ต่อต้านถ้าเป็นไปได้; การใช้ UIScrollView ทำให้แน่ใจว่าคุณจะได้รับรูปลักษณ์ดั้งเดิมไม่ว่าจะพัฒนาไปอย่างไรในเวอร์ชัน iPhone OS ในอนาคต

เอาล่ะบทความเล็ก ๆ น้อย ๆ นี้ยาวไปหน่อย ฉันยังคงอยู่ในเกม UIScrollView หลังจากทำงานกับ ScrollingMadness เมื่อหลายวันก่อน

PS ถ้าคุณได้รับการใด ๆ ของการทำงานเหล่านี้และรู้สึกเหมือนการแบ่งปันโปรดอีเมลฉันรหัสที่เกี่ยวข้องที่ andreyvit@gmail.com ผมมีความสุขรวมมันเข้าไปในของฉันถุง ScrollingMadness ของเทคนิค

PPS การเพิ่มบทความเล็ก ๆ นี้ใน ScrollingMadness README


7
โปรดทราบว่าพฤติกรรมการดูเลื่อนที่ซ้อนกันนี้ใช้งานได้ทันทีใน SDK โดยไม่จำเป็นต้องทำธุรกิจตลก
andygeers

1
+1 andygeers แสดงความคิดเห็นแล้วรู้ว่ามันไม่ถูกต้อง คุณยังคงสามารถ "ทำลาย" UIScrollView ปกติได้โดยการลากในแนวทแยงมุมจากจุดเริ่มต้นจากนั้นคุณ "ดึงตัวเลื่อนเสีย" และมันจะเพิกเฉยต่อการล็อคทิศทางจนกว่าคุณจะปล่อยและท้ายที่สุดพระเจ้าก็รู้ว่าอยู่ที่ไหน
Kalle

ขอบคุณสำหรับคำอธิบายเบื้องหลังที่ยอดเยี่ยม! ฉันใช้วิธีแก้ปัญหาของ Mattias Wadman ด้านล่างซึ่งดูเหมือน IMO ที่รุกรานน้อยกว่าเล็กน้อย
Ortwin Gentz

จะเป็นการดีที่จะเห็นคำตอบนี้ได้รับการอัปเดตในขณะนี้ที่ UIScrollView จะแสดงตัวจดจำท่าทางสัมผัสของกระทะ (แต่อาจจะอยู่นอกขอบเขตเดิม)
jtbandes

มีความเป็นไปได้ที่โพสต์นี้จะได้รับการอัปเดตหรือไม่? โพสต์ที่ดีและขอขอบคุณสำหรับข้อมูลของคุณ
Pavan

56

สิ่งนี้ใช้ได้กับฉันทุกครั้ง ...

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

20
สำหรับใครก็ตามที่สะดุดกับคำถามนี้: ฉันคิดว่าคำตอบนี้ถูกโพสต์ก่อนการแก้ไขที่ชี้แจงคำถาม วิธีนี้จะช่วยให้คุณสามารถเลื่อนได้ในแนวนอนเท่านั้น แต่ไม่ได้ช่วยแก้ปัญหาในการเลื่อนในแนวตั้งหรือแนวนอน แต่ไม่ใช่ในแนวทแยงมุม
andygeers

ใช้งานได้เหมือนมีเสน่ห์สำหรับฉัน ฉันมี scrollView แนวนอนที่ยาวมากพร้อมเพจจิ้งและ UIWebView ในแต่ละเพจซึ่งจัดการการเลื่อนแนวตั้งที่ฉันต้องการ
Tom Redman

ยอดเยี่ยม! แต่ใครสามารถอธิบายวิธีการทำงานนี้ (การตั้งค่า contentSize height เป็น 1)!
ประวีณ

มันใช้งานได้จริง แต่ทำไมและอย่างไร? มีใครเข้าใจได้บ้างไหม
Marko Francekovic

27

สำหรับคนขี้เกียจอย่างฉัน:

ตั้งค่าscrollview.directionalLockEnabledเป็นYES

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

ฉันใช้วิธีการต่อไปนี้เพื่อแก้ปัญหานี้หวังว่าจะช่วยคุณได้

ขั้นแรกกำหนดตัวแปรต่อไปนี้ในไฟล์ส่วนหัวคอนโทรลเลอร์ของคุณ

CGPoint startPos;
int     scrollDirection;

startPosจะเก็บค่าcontentOffset ไว้เมื่อผู้รับมอบสิทธิ์ของคุณได้รับข้อความscrollViewWillBeginDragging ดังนั้นในวิธีนี้เราทำสิ่งนี้

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

จากนั้นเราจะใช้ค่าเหล่านี้เพื่อกำหนดทิศทางการเลื่อนที่ผู้ใช้ต้องการในข้อความ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];
    }
 }

ในที่สุดเราต้องหยุดการดำเนินการอัปเดตทั้งหมดเมื่อผู้ใช้ลากปลาย;

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

อืม ... อันที่จริงหลังจากการตรวจสอบมากขึ้น - มันทำงานสวยดี แต่ปัญหาก็คือว่ามันสูญเสียการควบคุมทิศทางในช่วงชะลอตัว - ดังนั้นถ้าคุณเริ่มต้นการเลื่อนนิ้วของคุณแนวทแยงมุมนั้นจะปรากฏเพียงย้ายในแนวนอนหรือแนวตั้งจนกว่าคุณจะปล่อยเมื่อถึงจุดนั้นมันจะชะลอตัวลงในแนวทแยงชั่วขณะ
andygeers

@andygeers มันอาจจะทำงานได้ดีขึ้นถ้า- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (decelerate) { scrollDirection=3; } }เปลี่ยนเป็น- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (!decelerate) { scrollDirection=0; } }และ- (void)scrollViewDidEndDecelerating{ scrollDirection=0; }
cduck

ไม่ได้ผลสำหรับฉัน การตั้งค่า contentOffset ในเที่ยวบินดูเหมือนจะทำลายพฤติกรรมการเพจ ฉันไปกับโซลูชันของ Mattias Wadman
Ortwin Gentz

13

ฉันอาจจะฝังศพที่นี่ แต่ฉันสะดุดกับโพสต์นี้ในวันนี้ขณะที่ฉันพยายามแก้ปัญหาเดียวกัน นี่เป็นวิธีแก้ปัญหาของฉันดูเหมือนจะใช้งานได้ดี

ฉันต้องการแสดงเนื้อหาที่เป็นหน้าจอ แต่อนุญาตให้ผู้ใช้เลื่อนไปยังหนึ่งใน 4 หน้าจออื่น ๆ โดยขึ้นลงทางซ้ายและขวา ฉันมี UIScrollView เดียวที่มีขนาด 320x480 และขนาดเนื้อหา 960x1440 การชดเชยเนื้อหาเริ่มต้นที่ 320,480 ใน scrollViewDidEndDecelerating: ฉันวาด 5 มุมมองใหม่ (มุมมองตรงกลางและมุมมอง 4 รอบ) และรีเซ็ตการชดเชยเนื้อหาเป็น 320,480

นี่คือเนื้อของวิธีแก้ปัญหาของฉันวิธี 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);
    }
}

คุณอาจจะพบว่ารหัสนี้ไม่ได้ "ชะลอตัว" เมื่อผู้ใช้ปล่อยมุมมองแบบเลื่อน - มันจะหยุดตาย หากคุณไม่ต้องการลดความเร็วสิ่งนี้อาจจะดีสำหรับคุณ
andygeers

4

พวกเขาทำได้ง่ายเพียงแค่ทำให้ความสูงของมุมมองย่อยเหมือนกับความสูงของมุมมองการเลื่อนและความสูงของขนาดเนื้อหา จากนั้นการเลื่อนแนวตั้งจะถูกปิดใช้งาน


2
สำหรับใครก็ตามที่สะดุดกับคำถามนี้: ฉันคิดว่าคำตอบนี้ถูกโพสต์ก่อนการแก้ไขที่ชี้แจงคำถาม วิธีนี้จะช่วยให้คุณสามารถเลื่อนได้ในแนวนอนเท่านั้นไม่ได้ช่วยแก้ปัญหาในการเลื่อนในแนวตั้งหรือแนวนอน แต่ไม่ใช่ในแนวทแยงมุม
andygeers

3

นี่คือการนำเพจไปใช้งานของฉันUIScrollViewที่อนุญาตให้เลื่อนแบบตั้งฉากเท่านั้น ให้แน่ใจว่าการตั้งค่าไปpagingEnabledYES

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
ใช้งานได้ดีบน iOS 7 ด้วยขอบคุณ!
Ortwin Gentz

3

ฉันต้องแก้ปัญหานี้ด้วยและในขณะที่คำตอบของ Andrey Tarantsov มีข้อมูลที่เป็นประโยชน์มากมายสำหรับการทำความเข้าใจวิธีการUIScrollViewsทำงาน แต่ฉันรู้สึกว่าวิธีแก้ปัญหานั้นซับซ้อนเกินไปเล็กน้อย วิธีแก้ปัญหาของฉันซึ่งไม่เกี่ยวข้องกับคลาสย่อยหรือการส่งต่อแบบสัมผัสมีดังนี้:

  1. สร้างมุมมองการเลื่อนที่ซ้อนกันสองมุมมองสำหรับแต่ละทิศทาง
  2. สร้างหุ่นสองตัวUIPanGestureRecognizersอีกครั้งสำหรับแต่ละทิศทาง
  3. สร้างการอ้างอิงความล้มเหลวระหว่างUIScrollViewsคุณสมบัติ 'panGestureRecognizer และหุ่นจำลองที่UIPanGestureRecognizerสอดคล้องกับทิศทางที่ตั้งฉากกับทิศทางการเลื่อนที่ต้องการของ UIScrollView โดยใช้UIGestureRecognizer's -requireGestureRecognizerToFail:ตัวเลือก
  4. ในการ-gestureRecognizerShouldBegin:เรียกกลับสำหรับ UIPanGestureRecognizers จำลองของคุณให้คำนวณทิศทางเริ่มต้นของการแพนโดยใช้ตัวเลือก -translationInView: ท่าทางสัมผัส (คุณUIPanGestureRecognizersจะไม่เรียกการเรียกกลับของผู้รับมอบสิทธิ์นี้จนกว่าการสัมผัสของคุณจะแปลได้เพียงพอที่จะลงทะเบียนเป็นกระทะ) อนุญาตหรือไม่อนุญาตให้เครื่องมือจดจำท่าทางของคุณเริ่มต้นขึ้นอยู่กับทิศทางที่คำนวณซึ่งในทางกลับกันควรควบคุมว่าจะอนุญาตให้เริ่มต้นระบบจดจำท่าทางสัมผัสของ UIScrollView ที่ตั้งฉากได้หรือไม่
  5. ใน-gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:ไม่อนุญาตให้หุ่นของคุณUIPanGestureRecognizersจะทำงานควบคู่ไปกับการเลื่อน (ยกเว้นกรณีที่คุณมีบางพฤติกรรมพิเศษที่คุณต้องการเพิ่ม)

2

สิ่งที่ฉันทำคือสร้าง UIScrollView แบบเพจที่มีกรอบขนาดของหน้าจอมุมมองและกำหนดขนาดเนื้อหาเป็นความกว้างที่จำเป็นสำหรับเนื้อหาทั้งหมดของฉันและความสูง 1 (ขอบคุณ Tonetel) จากนั้นฉันสร้างเมนู UIScrollView เพจพร้อมเฟรมที่กำหนดให้กับแต่ละเพจขนาดของหน้าจอมุมมองและตั้งค่าขนาดเนื้อหาของแต่ละเพจเป็นความกว้างของหน้าจอและความสูงที่จำเป็นสำหรับแต่ละเพจ จากนั้นฉันก็เพิ่มหน้าเมนูแต่ละหน้าในมุมมองการเพจ ตรวจสอบให้แน่ใจว่าเปิดใช้งานการเพจในมุมมองการเลื่อนเพจ แต่ปิดใช้งานในมุมมองเมนู (ควรปิดใช้งานโดยค่าเริ่มต้น) และคุณควรจะไป

ตอนนี้มุมมองการเลื่อนเพจจะเลื่อนในแนวนอน แต่ไม่ใช่แนวตั้งเนื่องจากความสูงของเนื้อหา แต่ละหน้าจะเลื่อนในแนวตั้ง แต่ไม่ใช่แนวนอนเนื่องจากมีข้อ จำกัด ด้านขนาดเนื้อหาเช่นกัน

นี่คือรหัสหากคำอธิบายนั้นเหลือสิ่งที่ต้องการ:

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

ขอบคุณ Tonetel ฉันปรับเปลี่ยนแนวทางของคุณเล็กน้อย แต่เป็นสิ่งที่ฉันต้องการเพื่อป้องกันการเลื่อนในแนวนอน

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

2

นี่เป็นโซลูชันที่ปรับปรุงแล้วของ icompot ซึ่งค่อนข้างไม่เสถียรสำหรับฉัน อันนี้ใช้งานได้ดีและใช้งานง่าย:

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

สำหรับการอ้างอิงในอนาคตฉันสร้างขึ้นจากคำตอบของ Andrey Tanasov และหาวิธีแก้ปัญหาต่อไปนี้ที่ใช้งานได้

ขั้นแรกกำหนดตัวแปรเพื่อเก็บ contentOffset และตั้งค่าเป็นพิกัด 0,0

var positionScroll = CGPointMake(0, 0)

ตอนนี้ใช้สิ่งต่อไปนี้ในตัวแทน 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
        }

    }

}

หวังว่านี่จะช่วยได้


1

จากเอกสาร:

"ค่าเริ่มต้นคือ NO ซึ่งหมายความว่าอนุญาตให้เลื่อนได้ทั้งในแนวนอนและแนวตั้งหากค่าเป็นใช่และผู้ใช้เริ่มลากไปในทิศทางเดียว (แนวนอนหรือแนวตั้ง) มุมมองการเลื่อนจะปิดใช้งานการเลื่อนไปในทิศทางอื่น "

ฉันคิดว่าส่วนสำคัญคือ“ ถ้า ... ผู้ใช้เริ่มต้นลากไปในทิศทางเดียวทั่วไป" ดังนั้นหากพวกเขาเริ่มลากตามแนวทแยงมุมสิ่งนี้จะไม่เกิดขึ้นไม่ใช่ว่าเอกสารเหล่านี้จะเชื่อถือได้เสมอเมื่อต้องตีความ - แต่ดูเหมือนว่าจะเข้ากับสิ่งที่คุณเห็น

สิ่งนี้ดูเหมือนจะสมเหตุสมผล ฉันต้องถามว่าทำไมคุณถึงต้องการ จำกัด เฉพาะแนวนอนหรือแนวตั้งเท่านั้นเมื่อใดก็ได้ บางที UIScrollView อาจไม่ใช่เครื่องมือสำหรับคุณ?


1

มุมมองของฉันประกอบด้วยมุมมองแนวนอน 10 มุมมองและบางมุมมองเหล่านั้นประกอบด้วยมุมมองแนวตั้งหลายมุมมองดังนั้นคุณจะได้สิ่งนี้:


1.0   2.0   3.1    4.1
      2.1   3.1
      2.2
      2.3

เมื่อเปิดใช้เพจจิ้งทั้งในแกนแนวนอนและแนวตั้ง

มุมมองยังมีแอตทริบิวต์ต่อไปนี้:

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

ตอนนี้เพื่อป้องกันการเลื่อนในแนวทแยงให้ทำสิ่งนี้:

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

ใช้งานได้เหมือนเสน่ห์สำหรับฉัน!


1
ไม่เข้าใจว่าสิ่งนี้จะใช้ได้ผลกับคุณอย่างไร ... คุณช่วยอัปโหลดโครงการตัวอย่างได้ไหม
hfossli



0

หากคุณยังไม่ได้ลองทำสิ่งนี้ใน 3.0 เบต้าขอแนะนำให้ลองทำดู ขณะนี้ฉันกำลังใช้ชุดมุมมองตารางภายในมุมมองแบบเลื่อนและทำงานได้ตามที่คาดไว้ (เอาล่ะเลื่อนก็เจอคำถามนี้เมื่อต้องการแก้ไขปัญหาที่แตกต่างไปจากเดิมโดยสิ้นเชิง ... )


0

สำหรับผู้ที่มองหา Swift ที่ @AndreyTarantsov โซลูชันที่สองจะเป็นเช่นนี้ในรหัส:

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

สิ่งที่เรากำลังทำที่นี่คือรักษาตำแหน่งปัจจุบันก่อนที่จะลาก scrollview จากนั้นตรวจสอบว่า x เพิ่มขึ้นหรือลดลงเมื่อเทียบกับตำแหน่งปัจจุบันของ x ถ้าใช่ให้ตั้งค่า pagingEnabled เป็น true หากไม่มี (y เพิ่มขึ้น / ลดลง) จากนั้นตั้งค่าเป็น pagingEnabled เป็น false

หวังว่าจะเป็นประโยชน์กับผู้มาใหม่!


0

ฉันจัดการเพื่อแก้ปัญหานี้โดยใช้ scrollViewDidBeginDragging (_ :) และดูความเร็วของ UIPanGestureRecognizer ที่อยู่เบื้องหลัง

หมายเหตุ: ด้วยวิธีนี้การเลื่อนทุกบานที่เป็นเส้นทแยงมุมจะถูกละเว้นโดย 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
    }
}

-3

หากคุณใช้ตัวสร้างอินเทอร์เฟซเพื่อออกแบบอินเทอร์เฟซของคุณสิ่งนี้สามารถทำได้ค่อนข้างง่าย

ในตัวสร้างอินเทอร์เฟซเพียงคลิกที่ UIScrollView แล้วเลือกตัวเลือก (ในตัวตรวจสอบ) ที่ จำกัด ให้เลื่อนได้เพียงทางเดียว


1
ไม่มีตัวเลือกใน IB ให้ทำเช่นนี้ สองตัวเลือกที่คุณอาจอ้างถึงเกี่ยวข้องกับการแสดงแถบเลื่อนไม่ใช่การเลื่อนจริง นอกจากนี้ Direction Lock จะล็อคทิศทางเมื่อการเลื่อนเริ่มขึ้นเท่านั้น
Sophtware
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.