आप बहुत कठिन स्थिति में हैं, मुझे कहना होगा।
ध्यान दें कि आपको pagingEnabled=YES
पृष्ठों के बीच स्विच करने के लिए UIScrollView का उपयोग करने की आवश्यकता है , लेकिन आपको pagingEnabled=NO
लंबवत स्क्रॉल करने की आवश्यकता है।
2 संभावित रणनीतियाँ हैं। मुझे नहीं पता कि कौन सा काम करेगा / इसे लागू करना आसान है, इसलिए दोनों का प्रयास करें।
पहला: नेस्क्रॉलव्यूड्स नेस्टेड। सच कहूँ, मैं अभी तक एक ऐसे व्यक्ति को देख रहा हूँ जिसे यह काम करने के लिए मिला है। हालाँकि मैंने व्यक्तिगत रूप से पर्याप्त प्रयास नहीं किया है, और मेरे अभ्यास से पता चलता है कि जब आप पर्याप्त प्रयास करते हैं, तो आप UIScrollView को अपनी इच्छानुसार कुछ भी कर सकते हैं ।
इसलिए रणनीति यह है कि बाहरी स्क्रॉल दृश्य को केवल क्षैतिज स्क्रॉलिंग संभालें, और आंतरिक स्क्रॉल दृश्य केवल ऊर्ध्वाधर स्क्रॉलिंग को संभालने के लिए। इसे पूरा करने के लिए, आपको पता होना चाहिए कि UIScrollView आंतरिक रूप से कैसे काम करता है। यह hitTest
विधि को ओवरराइड करता है और हमेशा खुद को वापस करता है, ताकि सभी स्पर्श घटनाएं UIScrollView में जाएं। फिर अंदर touchesBegan
, touchesMoved
आदि यह जाँचता है कि क्या यह घटना में रुचि रखता है, और या तो इसे संभालता है या आंतरिक घटकों पर इसे पारित करता है।
यह तय करने के लिए कि स्पर्श को संभाला जाना है या आगे भेजा जाना है, जब आप पहली बार स्पर्श करते हैं तो UIScrollView एक टाइमर शुरू करता है:
यदि आपने अपनी उंगली को 150ms के भीतर स्थानांतरित नहीं किया है, तो यह ईवेंट को आंतरिक दृश्य में पास करता है।
यदि आपने अपनी उंगली को 150ms के भीतर स्थानांतरित कर दिया है, तो यह स्क्रॉल करना शुरू कर देता है (और कभी भी ईवेंट को आंतरिक दृश्य में पास नहीं करता है)।
ध्यान दें कि जब आप किसी तालिका को छूते हैं (जो स्क्रॉल दृश्य का एक उपवर्ग है) और तुरंत स्क्रॉल करना शुरू करें, तो आपने जिस पंक्ति को छुआ है वह कभी भी हाइलाइट नहीं किया गया है।
यदि आपने 150ms के भीतर अपनी उंगली को महत्वपूर्ण रूप से नहीं हिलाया है और UIScrollView ने घटनाओं को आंतरिक दृश्य में पास करना शुरू कर दिया है, लेकिन फिर आप स्क्रॉल करने के लिए उंगली को काफी दूर ले गए हैं, तो UIScrollView touchesCancelled
आंतरिक दृश्य पर कॉल करता है और स्क्रॉल करना शुरू करता है।
ध्यान दें कि जब आप किसी तालिका को स्पर्श करते हैं, तो अपनी उंगली को थोड़ा सा पकड़ें और फिर स्क्रॉल करना शुरू करें, जिस पंक्ति को आपने छुआ है उसे पहले हाइलाइट किया गया है, लेकिन बाद में डी-हाइलाइट किया गया है।
घटनाओं के इन अनुक्रमों को UIScrollView के विन्यास से बदला जा सकता है:
- यदि
delaysContentTouches
NO है, तो कोई टाइमर का उपयोग नहीं किया जाता है - घटनाएं तुरंत आंतरिक नियंत्रण में जाती हैं (लेकिन यदि आप अपनी उंगली को काफी दूर ले जाते हैं तो रद्द कर दिया जाता है)
- यदि
cancelsTouches
NO है, तो एक बार घटनाओं को नियंत्रण में भेजने के बाद, स्क्रॉलिंग कभी नहीं होगी।
ध्यान दें कि यह UIScrollView है कि प्राप्त करता है सब touchesBegin
, touchesMoved
, touchesEnded
और touchesCanceled
CocoaTouch से घटनाओं (क्योंकि इसके hitTest
कि ऐसा करना बताता है)। यह तब तक उन्हें आंतरिक दृश्य के लिए अग्रेषित करता है यदि वह चाहता है, जब तक वह चाहता है।
अब जब आपको UIScrollView के बारे में सब कुछ पता है, तो आप इसके व्यवहार को बदल सकते हैं। मैं शर्त लगा सकता हूं कि आप ऊर्ध्वाधर स्क्रॉलिंग को वरीयता देना चाहते हैं, ताकि एक बार उपयोगकर्ता दृश्य को छू ले और अपनी उंगली को हिलाना शुरू कर दे (थोड़ा भी), दृश्य ऊर्ध्वाधर दिशा में स्क्रॉल करना शुरू कर देता है; लेकिन जब उपयोगकर्ता अपनी उंगली को क्षैतिज दिशा में काफी दूर ले जाता है, तो आप ऊर्ध्वाधर स्क्रॉल रद्द करना और क्षैतिज स्क्रॉलिंग शुरू करना चाहते हैं।
आप अपने बाहरी UIScrollView को उप-वर्ग करना चाहते हैं (कहते हैं, आप अपने वर्ग का नाम RemorsefulScrollView) रखते हैं, ताकि डिफ़ॉल्ट व्यवहार के बजाय यह सभी घटनाओं को आंतरिक दृश्य के लिए तुरंत आगे बढ़ा दे, और केवल जब महत्वपूर्ण क्षैतिज गति का पता चलता है तो स्क्रॉल।
RemorsefulScrollView कैसे बनाते हैं?
ऐसा लगता है कि ऊर्ध्वाधर स्क्रॉलिंग को अक्षम करना और delaysContentTouches
NO पर सेट करना काम करने के लिए नेस्टेड UIScrollViews बनाना चाहिए। दुर्भाग्य से, यह नहीं करता है; UIScrollView तेजी से गतियों के लिए कुछ अतिरिक्त फ़िल्टरिंग करता है (जिसे अक्षम नहीं किया जा सकता है), ताकि भले ही UIScrollView को केवल क्षैतिज रूप से स्क्रॉल किया जा सके, यह हमेशा (और अनदेखा) फास्ट पर्याप्त ऊर्ध्वाधर गति को खाएगा।
प्रभाव इतना गंभीर है कि एक नेस्टेड स्क्रॉल दृश्य के अंदर ऊर्ध्वाधर स्क्रॉलिंग अनुपयोगी है। (ऐसा प्रतीत होता है कि आपको वास्तव में यह सेटअप मिल गया है, इसलिए इसे आज़माएं: 150ms के लिए एक उंगली पकड़ें, और फिर इसे ऊर्ध्वाधर दिशा में ले जाएं - नेस्टेड UIScrollView तब उम्मीद के मुताबिक काम करता है!)
इसका मतलब है कि आप इवेंट हैंडलिंग के लिए UIScrollView के कोड का उपयोग नहीं कर सकते हैं; super
यदि आपको क्षैतिज स्क्रॉलिंग के साथ जाने का निर्णय लिया गया है, तो आपको रिमोरसेफुलसर्च व्यू में सभी चार टच हैंडलिंग विधियों को ओवरराइड करना होगा और पहले अपना स्वयं का प्रसंस्करण करना होगा, केवल ईवेंट को (UIScrollView) को अग्रेषित करना होगा।
लेकिन अगर आप पास करना touchesBegan
, क्योंकि आप इसे (यदि आप बाद में तय यह एक आधार भविष्य क्षैतिज स्क्रॉल के लिए समन्वय याद रखना चाहते हैं UIScrollView करने के लिए है एक क्षैतिज स्क्रॉल)। आप touchesBegan
UIScrollView को बाद में भेजने में सक्षम नहीं होंगे , क्योंकि आप इस touches
तर्क को संग्रहीत नहीं कर सकते हैं : इसमें ऐसी वस्तुएँ शामिल हैं जिन्हें अगली touchesMoved
घटना से पहले म्यूट किया जाएगा , और आप पुरानी स्थिति को पुन: उत्पन्न नहीं कर सकते।
तो आपको touchesBegan
तुरंत UIScrollView को पास करना होगा, लेकिन touchesMoved
जब तक आप क्षैतिज रूप से स्क्रॉल करने का निर्णय नहीं लेते हैं, तब तक आप इससे किसी भी आगे की घटनाओं को छिपाएंगे। नो का touchesMoved
मतलब नो स्क्रॉलिंग है, इसलिए यह शुरुआती touchesBegan
कोई नुकसान नहीं करेगा। लेकिन delaysContentTouches
NO पर सेट करें, ताकि कोई अतिरिक्त आश्चर्यचकित होने वाला समय बाधित न हो।
(ऑफटॉपिक - आपके विपरीत, UIScrollView बाद में सही तरीके से स्पर्श को स्टोर कर सकता है और touchesBegan
बाद में मूल घटना को पुन: उत्पन्न और अग्रेषित कर सकता है । इसमें अप्रकाशित एपीआई का उपयोग करने का अनुचित लाभ है, इसलिए वे उत्परिवर्तित होने से पहले वस्तुओं को स्पर्श कर सकते हैं।)
यह देखते हुए कि आप हमेशा आगे कि touchesBegan
, आप भी अग्रेषित करने के लिए है touchesCancelled
और touchesEnded
। आप चालू करने की आवश्यकता touchesEnded
में touchesCancelled
, तथापि, क्योंकि UIScrollView व्याख्या touchesBegan
, touchesEnded
एक स्पर्श क्लिक के रूप में अनुक्रम, और भीतरी दृश्य को अग्रेषित करेंगे। आप पहले से ही अपने आप को उचित घटनाओं को अग्रेषित कर रहे हैं, इसलिए आप कभी नहीं चाहते कि UIScrollView कुछ भी आगे बढ़ाए।
मूल रूप से यहाँ आपको क्या करने की आवश्यकता है के लिए छद्मकोड है। सादगी के लिए, मैं मल्टीटच घटना होने के बाद कभी भी क्षैतिज स्क्रॉलिंग की अनुमति नहीं देता।
@interface RemorsefulScrollView : UIScrollView {
CGPoint _originalPoint;
BOOL _isHorizontalScroll, _isMultitouch;
UIView *_currentChild;
}
@end
#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];
if (_isHorizontalScroll)
return;
if ([touches count] == [[event touchesForView:self] count]) {
_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];
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 में इस छोटे निबंध को जोड़ना।