UIScrollView: क्षैतिज रूप से पेजिंग, लंबवत स्क्रॉलिंग?


88

मैं किस तरह से बाध्य कर सकता हूं UIScrollViewजिसमें पेजिंग और स्क्रॉलिंग केवल एक पल में लंबवत या क्षैतिज रूप से आगे बढ़ते हैं?

मेरी समझ यह है कि directionalLockEnabledसंपत्ति को यह हासिल करना चाहिए, लेकिन एक विकर्ण स्वाइप अभी भी दृश्य को एक अक्ष पर गति को प्रतिबंधित करने के बजाय तिरछे स्क्रॉल करने का कारण बनता है।

संपादित करें: स्पष्ट होने के लिए, मैं उपयोगकर्ता को क्षैतिज या लंबवत रूप से स्क्रॉल करने की अनुमति देना चाहता हूं, लेकिन दोनों एक साथ नहीं।


क्या आप केवल होरीज़ / वर्ट को स्क्रॉल करने के लिए कोई दृश्य देखना चाहते हैं या क्या आप चाहते हैं कि उपयोगकर्ता होरिज़ या वर्ट को स्क्रॉल करें, लेकिन दोनों एक साथ नहीं?
विल हार्टुंग

क्या आप मुझ पर एक एहसान कर सकते हैं और शीर्षक बदल सकते हैं ताकि यह थोड़ा अधिक रोचक और गैर-तुच्छ लग सके? "UIScrollView: क्षैतिज रूप से पेजिंग, लंबवत स्क्रॉलिंग" जैसा कुछ
एंड्री टारंटसोव

दुर्भाग्य से, मैंने इस सवाल को एक अपंजीकृत उपयोगकर्ता के रूप में कंप्यूटर पर पोस्ट किया है जिसकी अब मेरे पास पहुँच नहीं है (इससे पहले कि मैं घर पहुंच गया उसी नाम के तहत पंजीकरण से पहले), जिसका अर्थ है कि मैं इसे संपादित नहीं कर सकता। यह भी मुझे मूल प्रश्न को संपादित करने के बजाय निम्न-अप को समझाना चाहिए। इस बार स्टैक शिष्टाचार तोड़ने के लिए क्षमा करें!
निक

जवाबों:


117

आप बहुत कठिन स्थिति में हैं, मुझे कहना होगा।

ध्यान दें कि आपको pagingEnabled=YESपृष्ठों के बीच स्विच करने के लिए UIScrollView का उपयोग करने की आवश्यकता है , लेकिन आपको pagingEnabled=NOलंबवत स्क्रॉल करने की आवश्यकता है।

2 संभावित रणनीतियाँ हैं। मुझे नहीं पता कि कौन सा काम करेगा / इसे लागू करना आसान है, इसलिए दोनों का प्रयास करें।

पहला: नेस्क्रॉलव्यूड्स नेस्टेड। सच कहूँ, मैं अभी तक एक ऐसे व्यक्ति को देख रहा हूँ जिसे यह काम करने के लिए मिला है। हालाँकि मैंने व्यक्तिगत रूप से पर्याप्त प्रयास नहीं किया है, और मेरे अभ्यास से पता चलता है कि जब आप पर्याप्त प्रयास करते हैं, तो आप UIScrollView को अपनी इच्छानुसार कुछ भी कर सकते हैं

इसलिए रणनीति यह है कि बाहरी स्क्रॉल दृश्य को केवल क्षैतिज स्क्रॉलिंग संभालें, और आंतरिक स्क्रॉल दृश्य केवल ऊर्ध्वाधर स्क्रॉलिंग को संभालने के लिए। इसे पूरा करने के लिए, आपको पता होना चाहिए कि UIScrollView आंतरिक रूप से कैसे काम करता है। यह hitTestविधि को ओवरराइड करता है और हमेशा खुद को वापस करता है, ताकि सभी स्पर्श घटनाएं UIScrollView में जाएं। फिर अंदर touchesBegan, touchesMovedआदि यह जाँचता है कि क्या यह घटना में रुचि रखता है, और या तो इसे संभालता है या आंतरिक घटकों पर इसे पारित करता है।

यह तय करने के लिए कि स्पर्श को संभाला जाना है या आगे भेजा जाना है, जब आप पहली बार स्पर्श करते हैं तो UIScrollView एक टाइमर शुरू करता है:

  • यदि आपने अपनी उंगली को 150ms के भीतर स्थानांतरित नहीं किया है, तो यह ईवेंट को आंतरिक दृश्य में पास करता है।

  • यदि आपने अपनी उंगली को 150ms के भीतर स्थानांतरित कर दिया है, तो यह स्क्रॉल करना शुरू कर देता है (और कभी भी ईवेंट को आंतरिक दृश्य में पास नहीं करता है)।

    ध्यान दें कि जब आप किसी तालिका को छूते हैं (जो स्क्रॉल दृश्य का एक उपवर्ग है) और तुरंत स्क्रॉल करना शुरू करें, तो आपने जिस पंक्ति को छुआ है वह कभी भी हाइलाइट नहीं किया गया है।

  • यदि आपने 150ms के भीतर अपनी उंगली को महत्वपूर्ण रूप से नहीं हिलाया है और UIScrollView ने घटनाओं को आंतरिक दृश्य में पास करना शुरू कर दिया है, लेकिन फिर आप स्क्रॉल करने के लिए उंगली को काफी दूर ले गए हैं, तो UIScrollView touchesCancelledआंतरिक दृश्य पर कॉल करता है और स्क्रॉल करना शुरू करता है।

    ध्यान दें कि जब आप किसी तालिका को स्पर्श करते हैं, तो अपनी उंगली को थोड़ा सा पकड़ें और फिर स्क्रॉल करना शुरू करें, जिस पंक्ति को आपने छुआ है उसे पहले हाइलाइट किया गया है, लेकिन बाद में डी-हाइलाइट किया गया है।

घटनाओं के इन अनुक्रमों को UIScrollView के विन्यास से बदला जा सकता है:

  • यदि delaysContentTouchesNO है, तो कोई टाइमर का उपयोग नहीं किया जाता है - घटनाएं तुरंत आंतरिक नियंत्रण में जाती हैं (लेकिन यदि आप अपनी उंगली को काफी दूर ले जाते हैं तो रद्द कर दिया जाता है)
  • यदि cancelsTouchesNO है, तो एक बार घटनाओं को नियंत्रण में भेजने के बाद, स्क्रॉलिंग कभी नहीं होगी।

ध्यान दें कि यह UIScrollView है कि प्राप्त करता है सब touchesBegin , touchesMoved, touchesEndedऔर touchesCanceledCocoaTouch से घटनाओं (क्योंकि इसके hitTestकि ऐसा करना बताता है)। यह तब तक उन्हें आंतरिक दृश्य के लिए अग्रेषित करता है यदि वह चाहता है, जब तक वह चाहता है।

अब जब आपको UIScrollView के बारे में सब कुछ पता है, तो आप इसके व्यवहार को बदल सकते हैं। मैं शर्त लगा सकता हूं कि आप ऊर्ध्वाधर स्क्रॉलिंग को वरीयता देना चाहते हैं, ताकि एक बार उपयोगकर्ता दृश्य को छू ले और अपनी उंगली को हिलाना शुरू कर दे (थोड़ा भी), दृश्य ऊर्ध्वाधर दिशा में स्क्रॉल करना शुरू कर देता है; लेकिन जब उपयोगकर्ता अपनी उंगली को क्षैतिज दिशा में काफी दूर ले जाता है, तो आप ऊर्ध्वाधर स्क्रॉल रद्द करना और क्षैतिज स्क्रॉलिंग शुरू करना चाहते हैं।

आप अपने बाहरी UIScrollView को उप-वर्ग करना चाहते हैं (कहते हैं, आप अपने वर्ग का नाम RemorsefulScrollView) रखते हैं, ताकि डिफ़ॉल्ट व्यवहार के बजाय यह सभी घटनाओं को आंतरिक दृश्य के लिए तुरंत आगे बढ़ा दे, और केवल जब महत्वपूर्ण क्षैतिज गति का पता चलता है तो स्क्रॉल।

RemorsefulScrollView कैसे बनाते हैं?

  • ऐसा लगता है कि ऊर्ध्वाधर स्क्रॉलिंग को अक्षम करना और delaysContentTouchesNO पर सेट करना काम करने के लिए नेस्टेड UIScrollViews बनाना चाहिए। दुर्भाग्य से, यह नहीं करता है; UIScrollView तेजी से गतियों के लिए कुछ अतिरिक्त फ़िल्टरिंग करता है (जिसे अक्षम नहीं किया जा सकता है), ताकि भले ही UIScrollView को केवल क्षैतिज रूप से स्क्रॉल किया जा सके, यह हमेशा (और अनदेखा) फास्ट पर्याप्त ऊर्ध्वाधर गति को खाएगा।

    प्रभाव इतना गंभीर है कि एक नेस्टेड स्क्रॉल दृश्य के अंदर ऊर्ध्वाधर स्क्रॉलिंग अनुपयोगी है। (ऐसा प्रतीत होता है कि आपको वास्तव में यह सेटअप मिल गया है, इसलिए इसे आज़माएं: 150ms के लिए एक उंगली पकड़ें, और फिर इसे ऊर्ध्वाधर दिशा में ले जाएं - नेस्टेड UIScrollView तब उम्मीद के मुताबिक काम करता है!)

  • इसका मतलब है कि आप इवेंट हैंडलिंग के लिए UIScrollView के कोड का उपयोग नहीं कर सकते हैं; superयदि आपको क्षैतिज स्क्रॉलिंग के साथ जाने का निर्णय लिया गया है, तो आपको रिमोरसेफुलसर्च व्यू में सभी चार टच हैंडलिंग विधियों को ओवरराइड करना होगा और पहले अपना स्वयं का प्रसंस्करण करना होगा, केवल ईवेंट को (UIScrollView) को अग्रेषित करना होगा।

  • लेकिन अगर आप पास करना touchesBegan, क्योंकि आप इसे (यदि आप बाद में तय यह एक आधार भविष्य क्षैतिज स्क्रॉल के लिए समन्वय याद रखना चाहते हैं UIScrollView करने के लिए है एक क्षैतिज स्क्रॉल)। आप touchesBeganUIScrollView को बाद में भेजने में सक्षम नहीं होंगे , क्योंकि आप इस touchesतर्क को संग्रहीत नहीं कर सकते हैं : इसमें ऐसी वस्तुएँ शामिल हैं जिन्हें अगली touchesMovedघटना से पहले म्यूट किया जाएगा , और आप पुरानी स्थिति को पुन: उत्पन्न नहीं कर सकते।

    तो आपको touchesBeganतुरंत UIScrollView को पास करना होगा, लेकिन touchesMovedजब तक आप क्षैतिज रूप से स्क्रॉल करने का निर्णय नहीं लेते हैं, तब तक आप इससे किसी भी आगे की घटनाओं को छिपाएंगे। नो का touchesMovedमतलब नो स्क्रॉलिंग है, इसलिए यह शुरुआती touchesBeganकोई नुकसान नहीं करेगा। लेकिन delaysContentTouchesNO पर सेट करें, ताकि कोई अतिरिक्त आश्चर्यचकित होने वाला समय बाधित न हो।

    (ऑफटॉपिक - आपके विपरीत, UIScrollView बाद में सही तरीके से स्पर्श को स्टोर कर सकता है और touchesBeganबाद में मूल घटना को पुन: उत्पन्न और अग्रेषित कर सकता है । इसमें अप्रकाशित एपीआई का उपयोग करने का अनुचित लाभ है, इसलिए वे उत्परिवर्तित होने से पहले वस्तुओं को स्पर्श कर सकते हैं।)

  • यह देखते हुए कि आप हमेशा आगे कि 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 की तरह स्पर्श नहीं करता है। बुलेट-प्रूफ रिमॉर्सफुलसक्रोल व्यू कार्यान्वयन रीट्री के खिलाफ रक्षा करेगा touchesXxx

दूसरी रणनीति: यदि किसी कारण से नेस्टेड UIScrollViews काम नहीं करते हैं या सही होने के लिए बहुत कठिन साबित होते हैं, तो आप pagingEnabledअपने scrollViewDidScrollप्रतिनिधि विधि से मक्खी पर अपनी संपत्ति को स्विच करते हुए, केवल एक UIScrollView के साथ प्राप्त करने का प्रयास कर सकते हैं ।

विकर्ण स्क्रॉलिंग को रोकने के लिए, आपको पहले सामग्री को याद रखने की कोशिश करनी चाहिए scrollViewWillBeginDraggingऔर scrollViewDidScrollयदि आप विकर्ण आंदोलन का पता लगाते हैं तो सामग्री की जाँच करें और अंदर रीसेट करें । कोशिश करने के लिए एक और रणनीति है कि सामग्री को रीसेट करें केवल एक दिशा में स्क्रॉल करने में सक्षम करें, एक बार जब आप तय करते हैं कि उपयोगकर्ता की उंगली किस दिशा में जा रही है। (UIScrollView अपने प्रतिनिधि विधियों से सामग्री और सामग्री के साथ फ़िडलिंग के बारे में बहुत क्षमाशील लगता है।)

यदि वह काम नहीं करता है या मैला दृश्य में परिणाम करता है, तो आपको ओवरराइड करना होगा touchesBegan, touchesMovedआदि और विकर्ण आंदोलन की घटनाओं को UIScrollView में आगे नहीं बढ़ाना होगा। (हालांकि, इस मामले में उपयोगकर्ता का अनुभव सब-ऑप्टिमल होगा, क्योंकि आपको उन्हें एक ही दिशा में मजबूर करने के बजाय विकर्ण आंदोलनों को अनदेखा करना होगा। यदि आप वास्तव में साहसी महसूस कर रहे हैं, तो आप अपना खुद का यूआईटॉक लुकलाइक लिख सकते हैं, रिवेंजटच जैसे कुछ। -C सादा पुराना C है, और C की तुलना में दुनिया में कुछ भी अधिक ducktypeful नहीं है; जब तक कोई भी वस्तुओं की वास्तविक कक्षा की जांच नहीं करता है, जो मुझे लगता है कि कोई भी ऐसा नहीं करता है, आप किसी भी वर्ग को किसी भी अन्य वर्ग की तरह बना सकते हैं। आप चाहते हैं कि किसी भी निर्देशांक के साथ किसी भी स्पर्श को संश्लेषित करने की संभावना।)

बैकअप रणनीति: TTScrollView, तीन20 पुस्तकालय में UIScrollView का एक समझदार कार्यान्वयन है । दुर्भाग्य से यह उपयोगकर्ता के लिए बहुत ही अप्राकृतिक और गैर-iphonish लगता है। लेकिन अगर UIScrollView का उपयोग करने का हर प्रयास विफल हो जाता है, तो आप एक कस्टम-कोडेड स्क्रॉल दृश्य पर वापस आ सकते हैं। यदि संभव हो तो मैं इसके खिलाफ सिफारिश करता हूं; UIScrollView का उपयोग करने से यह सुनिश्चित होता है कि आपको देशी रूप-रंग दिख रहा है, फिर चाहे वह भविष्य के iPhone OS संस्करणों में विकसित हो।

ठीक है, इस छोटे से निबंध को बहुत लंबा समय मिला। मैं अभी भी कई दिनों पहले स्क्रॉलिंगमैडनेस पर काम करने के बाद UIScrollView गेम में हूँ।

पुनश्च अगर आपको इनमें से कोई भी काम मिलता है और साझा करने का मन करता है, तो कृपया मुझे andreyvit@gmail.com पर संबंधित कोड ई-मेल करें, मैं ख़ुशी से इसे मेरी स्क्रॉलिंग बैग की चाल में शामिल करूँगा ।

PPS स्क्रॉलिंगमाडनेस README में इस छोटे निबंध को जोड़ना।


7
ध्यान दें कि यह नेस्टेड स्क्रोलव्यू व्यवहार अब SDK में बॉक्स से बाहर काम करता है, किसी मज़ेदार व्यवसाय की ज़रूरत नहीं है
andygeers

1
+ 1'ygeers टिप्पणी करते हैं, तो एहसास हुआ कि यह सही नहीं है; आप अभी भी शुरुआती बिंदु से तिरछे खींचकर नियमित UIScrollView को "ब्रेक" कर सकते हैं और फिर आपने "स्क्रोलर हार" को खींच लिया है और यह दिशात्मक ताला को तब तक अनदेखा करेगा जब तक आप रिलीज नहीं करते हैं और अंत में भगवान को पता है कि कहां है।
कलल

महान पीछे के स्पष्टीकरण के लिए धन्यवाद! मैं नीचे मैटियस वडमन के समाधान के साथ गया था जो थोड़ा कम आक्रामक आईएमओ दिखता है।
Ortwin Gentz

इस उत्तर को अब अपडेट करके देखना बहुत अच्छा होगा कि UIScrollView अपने पैन जेस्चर पहचानकर्ता को उजागर करता है। (लेकिन शायद यह मूल दायरे से बाहर है।)
jtbandes

क्या इस पोस्ट के अपडेट होने की संभावना है? शानदार पोस्ट और आपके इनपुट के लिए धन्यवाद।
पावन

56

यह मेरे लिए हर काम करता है ...

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

20
किसी और के लिए इस सवाल पर ठोकर खाई: मुझे लगता है कि यह जवाब संपादित करने से पहले पोस्ट किया गया था जिसने सवाल को स्पष्ट किया। यह आपको केवल क्षैतिज रूप से स्क्रॉल करने की अनुमति देगा , यह लंबवत या क्षैतिज रूप से स्क्रॉल करने में सक्षम होने के मुद्दे को संबोधित नहीं करता है लेकिन तिरछे नहीं।
अयर्जर्स

मेरे लिए एक आकर्षण की तरह काम करता है। पेजिंग के साथ मेरे पास बहुत लंबा क्षैतिज स्क्रॉल दृश्य है, और प्रत्येक पृष्ठ पर एक UIWebView है, जो मुझे आवश्यक ऊर्ध्वाधर स्क्रॉल को संभालता है।
टॉम रेडमैन

अति उत्कृष्ट! लेकिन क्या कोई समझा सकता है कि यह कैसे काम करता है (सामग्री सेट करना 1 से ऊंचाई)!
प्रवीण

यह वास्तव में काम करता है, लेकिन यह क्यों और कैसे काम करता है? क्या कोई इससे कुछ मतलब निकाल सकता है?
मार्को फ्रांसकोविक

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

उसके बाद हम स्क्रॉल वैल्यूड्रेसक्रॉल संदेश में उपयोगकर्ताओं को इच्छित स्क्रॉल दिशा निर्धारित करने के लिए इन मानों का उपयोग करते हैं ।

- (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, यह बेहतर काम कर सकते हैं अगर - (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 सेट करना पेजिंग व्यवहार को नष्ट करने के लिए लगता है। मैं मट्टियास वाडमैन के समाधान के साथ गया।
ऑर्टविन गेंट्ज़

13

मैं यहाँ ग्रेडिंग कर सकता हूँ, लेकिन मैं आज इस पोस्ट पर ठोकर खा गया क्योंकि मैं उसी समस्या को हल करने की कोशिश कर रहा था। यहाँ मेरा समाधान है, लगता है महान काम कर रहा है।

मैं एक स्क्रीनफुल कंटेंट प्रदर्शित करना चाहता हूं, लेकिन उपयोगकर्ता को 4 अन्य स्क्रीनफुल में से एक को स्क्रॉल करने की अनुमति देता है, ऊपर बाएं और दाएं। मेरे पास एक एकल UIScrollView है जिसका आकार 320x480 है और एक सामग्री 960x1440 है। सामग्री ऑफसेट 320,480 से शुरू होती है। ScrollViewDidEndDecelerating: में, मैं 5 विचार (केंद्र विचार और इसके चारों ओर 4) को फिर से तैयार करता हूं, और सामग्री ऑफसेट को 320,480 पर रीसेट करता हूं।

यहां मेरे समाधान का मांस है, स्क्रॉलव्यूडसक्रोल: विधि।

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

आप शायद पाएंगे कि जब उपयोगकर्ता स्क्रॉल दृश्य को जाने देता है, तो यह कोड अच्छी तरह से "डीटलरेट" नहीं करता है - यह मृत हो जाएगा। यदि आप मंदी नहीं चाहते हैं तो यह आपके लिए ठीक हो सकता है।
अयर्जर्स

4

दोस्तों यह उतना ही सरल है जितना की सबव्यू ऊंचाई को स्क्रॉल व्यू की ऊँचाई और सामग्री के आकार की ऊँचाई के समान बनाना। फिर ऊर्ध्वाधर स्क्रॉलिंग अक्षम है।


2
किसी और के लिए इस सवाल पर ठोकर खाई: मुझे लगता है कि यह जवाब संपादित करने से पहले पोस्ट किया गया था जिसने सवाल को स्पष्ट किया। यह आपको केवल क्षैतिज रूप से स्क्रॉल करने की अनुमति देगा, यह लंबवत या क्षैतिज रूप से स्क्रॉल करने में सक्षम होने के मुद्दे को संबोधित नहीं करता है लेकिन तिरछे नहीं।
अयर्जर्स

3

यहाँ एक पृष्ठांकित का मेरा कार्यान्वयन है UIScrollViewजो केवल ऑर्थोगोनल स्क्रॉल की अनुमति देता है। सेट pagingEnabledकरने के लिए सुनिश्चित करें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
आईओएस 7 पर भी बढ़िया काम करता है। धन्यवाद!
17-28 बजे ऑर्टविन गेंटज़

3

मुझे भी इस समस्या को हल करना पड़ा है, और एंड्री टारनटोसव के जवाब में निश्चित रूप से यह समझने के लिए बहुत उपयोगी जानकारी है कि कैसे UIScrollViewsकाम करते हैं, मुझे लगता है कि समाधान थोड़ा जटिल है। मेरा समाधान, जिसमें किसी भी उपवर्ग या स्पर्श अग्रेषण को शामिल नहीं किया गया है, इस प्रकार है:

  1. दो नेस्टेड स्क्रॉल दृश्य बनाएं, प्रत्येक दिशा के लिए एक।
  2. दो डमी बनाएं UIPanGestureRecognizers, फिर से प्रत्येक दिशा के लिए एक।
  3. चयनकर्ता का उपयोग करके अपने UIScrollView की वांछित स्क्रॉलिंग दिशा के लंबवत दिशा के अनुरूप UIScrollViews'panGestureRecognizer गुण और डमी के बीच विफलता निर्भरता बनाएँ ।UIPanGestureRecognizerUIGestureRecognizer's -requireGestureRecognizerToFail:
  4. में -gestureRecognizerShouldBegin:अपने डमी UIPanGestureRecognizers के लिए कॉलबैक, पैन इशारा के -translationInView का उपयोग करने का प्रारंभिक दिशा की गणना: चयनकर्ता (अपनेUIPanGestureRecognizers जब तक अपने स्पर्श एक पैन के रूप में रजिस्टर करने के लिए पर्याप्त अनुवादित कर दिया है इस प्रतिनिधि कॉलबैक फोन नहीं होगा)। अपने जेस्चर पहचानकर्ता को परिकलित दिशा के आधार पर शुरू करने की अनुमति दें या अस्वीकार करें, जो बदले में नियंत्रण करना चाहिए कि क्या सीधा UIScrollView पैन जेस्चर पहचानकर्ता को शुरू करने की अनुमति दी जाएगी या नहीं।
  5. में -gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:, अपने डमी की अनुमति नहीं है UIPanGestureRecognizers(जब तक आप कुछ अतिरिक्त व्यवहार आप जोड़ना चाहते थे है) स्क्रॉल के साथ चलाने के लिए।

2

मैंने जो कुछ भी किया था, वह दृश्य स्क्रीन के आकार के फ्रेम के साथ एक पेजिंग UIScrollView बनाया गया था और मेरी सभी सामग्री और 1 की ऊंचाई (धन्यवाद टोनटेल) के लिए आवश्यक सामग्री का आकार निर्धारित किया था। फिर मैंने मेनू 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

धन्यवाद टोनटेल। मैंने आपके दृष्टिकोण को थोड़ा संशोधित किया, लेकिन क्षैतिज स्क्रॉलिंग को रोकने के लिए मुझे वास्तव में यही चाहिए था।

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

भविष्य के संदर्भ के लिए, मैंने एंड्री तानसोव के उत्तर पर निर्माण किया और निम्नलिखित समाधान के साथ आया जो ठीक काम करता है।

पहले सामग्री को संग्रहीत करने के लिए एक चर को परिभाषित करें और इसे 0,0 निर्देशांक पर सेट करें

var positionScroll = CGPointMake(0, 0)

अब स्क्रॉलव्यू प्रतिनिधि में निम्नलिखित को लागू करें:

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 है, जिसका अर्थ है कि क्षैतिज और ऊर्ध्वाधर दोनों दिशाओं में स्क्रॉलिंग की अनुमति है। यदि मान YES है और उपयोगकर्ता एक सामान्य दिशा (क्षैतिज या लंबवत) में खींचना शुरू करता है, तो स्क्रॉल दृश्य दूसरी दिशा में स्क्रॉल को अक्षम कर देता है। "

मुझे लगता है कि महत्वपूर्ण हिस्सा है "अगर ... उपयोगकर्ता शुरू होता है एक सामान्य दिशा में खींचना देता है"। इसलिए यदि वे तिरछे खींचना शुरू करते हैं, तो यह किक नहीं करता है। ऐसा नहीं है कि ये डॉक्स हमेशा विश्वसनीय होते हैं जब यह व्याख्या की बात आती है - लेकिन यह आपको जो दिख रहा है उसके साथ फिट बैठता है।

यह वास्तव में समझदार लगता है। मुझे पूछना है - आप किसी भी समय केवल क्षैतिज या केवल ऊर्ध्वाधर तक सीमित क्यों करना चाहते हैं? शायद 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
समझ में नहीं आता कि यह आपके लिए कैसे काम कर सकता है ... क्या आप एक नमूना-परियोजना अपलोड कर सकते हैं?
22



0

यदि आपने 3.0 बीटा में ऐसा करने की कोशिश नहीं की है, तो मैं आपको इसे शॉट देने की सलाह देता हूं। मैं वर्तमान में स्क्रॉल दृश्य के अंदर तालिका दृश्यों की एक श्रृंखला का उपयोग कर रहा हूं, और यह अपेक्षा के अनुरूप काम करता है। (ठीक है, वैसे भी स्क्रॉल करता है। यह सवाल तब मिला जब पूरी तरह से अलग मुद्दे को ठीक करने की तलाश में ...)


0

स्विफ्ट में देखने वालों के लिए @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
        }
    }

हम यहां क्या कर रहे हैं स्क्रॉल करने से पहले वर्तमान स्थिति को बनाए रखने के लिए खींचा जाता है और फिर यह जांचें कि क्या x की वर्तमान स्थिति की तुलना में x बढ़ गया है या कम हो गया है। यदि हाँ, तो पेजिंग को सही पर सेट करें, यदि कोई नहीं (y में वृद्धि / कमी हुई है) तो पेजिंग पर सेट करें

आशा है कि नए comers के लिए उपयोगी है!


0

मैं स्क्रॉलव्यूडीडबिनग्रेगिंग (_ :) लागू करके और अंतर्निहित UIPANGestureRecognizer पर वेग को देखते हुए इसे हल करने में कामयाब रहा।

एनबी: इस समाधान के साथ, विकर्ण वाले हर पैन को स्क्रॉलव्यू द्वारा अनदेखा किया जाएगा।

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
ऐसा करने के लिए आईबी में कोई विकल्प नहीं हैं। जिन दो विकल्पों का आप उल्लेख कर रहे हैं, उन्हें स्क्रॉल बार के प्रदर्शन के साथ करना होगा, न कि वास्तविक स्क्रॉलिंग से। इसके अलावा, एक बार स्क्रॉलिंग शुरू होने के बाद दिशा लॉक केवल दिशा को लॉक करता है।
सोफ्टवेयर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.