IOS पर बड़े और अनाड़ी UITableViewController से कैसे बचें?


36

IOS पर MVC- पैटर्न को लागू करते समय मुझे एक समस्या है। मैंने इंटरनेट पर खोज की है लेकिन लगता है कि इस समस्या का कोई अच्छा समाधान नहीं है।

कई UITableViewControllerकार्यान्वयन बल्कि बड़े होने लगते हैं। मैंने देखा ज्यादातर उदाहरण UITableViewControllerकार्यान्वयन <UITableViewDelegate>और <UITableViewDataSource>। ये कार्यान्वयन एक बड़ा कारण है जो बड़ा हो UITableViewControllerरहा है। एक समाधान यह होगा कि अलग-अलग कक्षाएं बनाई जाएं जो लागू हों <UITableViewDelegate>और <UITableViewDataSource>। बेशक इन वर्गों को इसका संदर्भ देना होगा UITableViewController। क्या इस समाधान का उपयोग करने में कोई कमियां हैं? सामान्य तौर पर मुझे लगता है कि आपको प्रतिनिधि पैटर्न का उपयोग करके अन्य "हेल्पर" कक्षाओं या इसी तरह की कार्यक्षमता को सौंपना चाहिए। क्या इस समस्या के समाधान के कोई सुस्थापित तरीके हैं?

मैं नहीं चाहता कि मॉडल में बहुत अधिक कार्यक्षमता हो, और न ही दृश्य। मेरा मानना ​​है कि तर्क वास्तव में नियंत्रक वर्ग में होना चाहिए, क्योंकि यह एमवीसी-पैटर्न के कोने में से एक है। लेकिन बड़ा सवाल यह है:

आपको MVC-कार्यान्वयन के नियंत्रक को छोटे प्रबंधनीय टुकड़ों में कैसे विभाजित करना चाहिए? (इस मामले में iOS में MVC पर लागू होता है)

इसे हल करने के लिए एक सामान्य पैटर्न हो सकता है, हालांकि मैं विशेष रूप से आईओएस के लिए एक समाधान की तलाश कर रहा हूं। कृपया इस समस्या को हल करने के लिए एक अच्छे पैटर्न का उदाहरण दें। कृपया तर्क दें कि आपका समाधान क्यों भयानक है।


1
"यह भी एक तर्क कि यह समाधान क्यों भयानक है।" :)
14

1
उस बिंदु के बगल में थोड़ा सा है, लेकिन UITableViewControllerयांत्रिकी मुझे काफी अजीब लगता है, इसलिए मैं समस्या से संबंधित हो सकता हूं। मैं वास्तव में खुशी है कि मैं उपयोग कर रहा हूँ MonoTouch, क्योंकि MonoTouch.Dialogविशेष रूप से यह बनाता है कि ज्यादा काम करने के लिए आसान iOS पर तालिकाओं के साथ। इस बीच, मैं उत्सुक हूं कि अन्य, अधिक जानकार लोग यहां क्या सुझाव दे सकते हैं ...
पेट्रीक

जवाबों:


43

मैं उपयोग करने से बचता हूं UITableViewController, क्योंकि यह एक ही वस्तु में बहुत सारी जिम्मेदारियां डालता है। इसलिए मैं UIViewControllerउप-स्रोत को डेटा स्रोत और प्रतिनिधि से अलग करता हूं । व्यू कंट्रोलर की जिम्मेदारी टेबल व्यू तैयार करना, डेटा के साथ डेटा स्रोत बनाना और उन चीजों को एक साथ हुक करना है। जिस तरह से टेबलव्यू का प्रतिनिधित्व किया जाता है उसे व्यू कंट्रोलर को बदले बिना बदला जा सकता है, और वास्तव में एक ही व्यू कंट्रोलर का उपयोग कई डेटा स्रोतों के लिए किया जा सकता है जो सभी इस पैटर्न का पालन करते हैं। इसी तरह, ऐप वर्कफ़्लो को बदलने का मतलब है कि टेबल पर क्या होता है, इसकी चिंता किए बिना व्यू कंट्रोलर में बदलाव।

मैंने अलग-अलग ऑब्जेक्ट्स में UITableViewDataSourceऔर UITableViewDelegateप्रोटोकॉल को अलग करने की कोशिश की है , लेकिन यह आमतौर पर एक गलत विभाजन के रूप में समाप्त होता है, क्योंकि प्रतिनिधि पर लगभग हर विधि डेटा स्रोत में खोदने की आवश्यकता होती है (उदाहरण के लिए चयन पर, प्रतिनिधि को यह जानने की जरूरत है कि ऑब्जेक्ट का प्रतिनिधित्व किस वस्तु द्वारा किया जाता है। चयनित पंक्ति)। तो मैं एक ही वस्तु के साथ समाप्त होता है जो डेटासोर्स और डेलिगेट दोनों है। यह ऑब्जेक्ट हमेशा एक विधि प्रदान करता है -(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPathजो डेटा स्रोत और प्रतिनिधि पहलुओं दोनों को यह जानने की आवश्यकता है कि वे क्या काम कर रहे हैं।

यह मेरा "स्तर 0" चिंताओं का पृथक्करण है। यदि मुझे एक ही तालिका दृश्य में विभिन्न प्रकार की वस्तुओं का प्रतिनिधित्व करना है, तो स्तर 1 व्यस्त हो जाता है। एक उदाहरण के रूप में, कल्पना करें कि आपको संपर्क ऐप लिखना था - एक ही संपर्क के लिए, आपके पास पंक्तियाँ हो सकती हैं जो फ़ोन नंबर का प्रतिनिधित्व करती हैं, अन्य पंक्तियों का प्रतिनिधित्व करती हैं, दूसरों को ईमेल पते का प्रतिनिधित्व करती हैं, और इसी तरह। मैं इस दृष्टिकोण से बचना चाहता हूं:

- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
  id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
  if ([object isKindOfClass: [PhoneNumber class]]) {
    //configure phone number cell
  }
  else if …
}

दो समाधानों ने खुद को अब तक प्रस्तुत किया है। एक गतिशील रूप से चयनकर्ता का निर्माण करना है:

- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
  id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
  NSString *cellSelectorName = [NSString stringWithFormat: @"tableView:cellFor%@AtIndexPath:", [object class]];
  SEL cellSelector = NSSelectorFromString(cellSelectorName);
  return [self performSelector: cellSelector withObject: tableView withObject: object];
}

- (UITableViewCell *)tableView: (UITableView *)tableView cellForPhoneNumberAtIndexPath: (NSIndexPath *)indexPath {
  // configure phone number cell
}

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

@interface PhoneNumber (TableViewRepresentation)

- (UITableViewCell *)tableView: (UITableView *)tableView representationAsCellForRowAtIndexPath: (NSIndexPath *)indexPath;

@end

@interface Address (TableViewRepresentation)

//more of the same…

@end

फिर अपने डेटा स्रोत वर्ग में:

- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
  id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
  return [object tableView: tableView representationAsCellForRowAtIndexPath: indexPath];
}

इसका मतलब यह है कि किसी भी डेटा स्रोत को फोन नंबर, पते आदि को प्रदर्शित करने की आवश्यकता है जो टेबल व्यू सेल के लिए किसी भी वस्तु का प्रतिनिधित्व कर सकता है। डेटा स्रोत को अब प्रदर्शित होने वाली वस्तु के बारे में कुछ भी जानने की आवश्यकता नहीं है।

"लेकिन रुकिए," मैं एक काल्पनिक अंतःसंबंधक विशेषण सुनता हूं, "जो एमवीसी को नहीं तोड़ता ? क्या आप एक मॉडल वर्ग में विवरण नहीं डाल रहे हैं?"

नहीं, यह MVC को नहीं तोड़ता है। आप इस मामले में श्रेणियों के बारे में सोच सकते हैं कि डेकोरेटर का कार्यान्वयन ; तो PhoneNumberएक मॉडल वर्ग है, लेकिन PhoneNumber(TableViewRepresentation)एक दृश्य श्रेणी है। डेटा स्रोत (एक नियंत्रक वस्तु) मॉडल और दृश्य के बीच मध्यस्थता करता है, इसलिए एमवीसी वास्तुकला अभी भी रखती है।

आप एप्पल के ढांचे में सजावट के रूप में श्रेणियों के इस उपयोग को देख सकते हैं। NSAttributedStringएक मॉडल वर्ग है, जिसमें कुछ पाठ और विशेषताएँ होती हैं। AppKit प्रदान करता है NSAttributedString(AppKitAdditions)और UIKit NSAttributedString(NSStringDrawing), सज्जाकार श्रेणियां प्रदान करता है जो इन मॉडल वर्गों के लिए ड्राइंग व्यवहार जोड़ते हैं।


डेटा स्रोत और तालिका दृश्य प्रतिनिधि के रूप में काम करने वाले वर्ग के लिए एक अच्छा नाम क्या है?
जोहान कार्लसन ने

1
@JohanKarlsson मैं अक्सर इसे डेटा स्रोत कहता हूं। यह शायद थोड़ा टेढ़ा है, लेकिन मैं दोनों को अक्सर यह जानने के लिए जोड़ती हूं कि मेरा "डेटा स्रोत" Apple की अधिक प्रतिबंधित परिभाषा पर एक अनुकूलन है।

1
यह लेख: objc.io/issue-1/table-views.html कई सेल प्रकारों को संभालने का एक तरीका प्रस्तावित करता है जिससे आप cellForPhotoAtIndexPathडेटा स्रोत की विधि में सेल वर्ग को काम करते हैं , फिर एक उपयुक्त फैक्टरी विधि कहते हैं। यदि पाठ्यक्रम विशेष रूप से विशेष पंक्तियों पर कब्जा कर लेता है, तो केवल पाठ्यक्रम ही संभव है। देखने-बनाने वाली श्रेणियों-मॉडल पर आपकी प्रणाली व्यवहार में बहुत अधिक सुरुचिपूर्ण है, मुझे लगता है, हालांकि यह एमवीसी के लिए एक अपरंपरागत दृष्टिकोण है! :)
बेन्जी XVI

1
मैंने इस पैटर्न को github.com/yonglam/TableViewPattern पर प्रदर्शित करने का प्रयास किया है । आशा है कि यह किसी के लिए उपयोगी है।
एंड्रयू

1
मैं गतिशील चयनकर्ता दृष्टिकोण के लिए एक निश्चित संख्या में वोट करूंगा । यह बहुत खतरनाक है क्योंकि केवल रन टाइम पर ही मुद्दे सामने आते हैं। यह सुनिश्चित करने के लिए कोई स्वचालित तरीका नहीं है कि दिए गए चयनकर्ता मौजूद हैं और यह सही ढंग से टाइप किया गया है और इस तरह का दृष्टिकोण अंततः टूट जाएगा और बनाए रखने के लिए एक बुरा सपना है। अन्य दृष्टिकोण, हालांकि, बहुत चालाक है।
एमकेओ

3

लोग UIViewController / UITableViewController में बहुत कुछ पैक करते हैं।

एक अन्य वर्ग (विचार नियंत्रक नहीं) के लिए प्रतिनिधि आमतौर पर ठीक काम करता है। प्रतिनिधियों को आवश्यक रूप से व्यू कंट्रोलर के लिए एक संदर्भ की आवश्यकता नहीं है, क्योंकि सभी प्रतिनिधि तरीके एक संदर्भ पास हो जाते हैं UITableView, लेकिन उन्हें किसी भी डेटा तक पहुंच की आवश्यकता होगी, जिसके लिए वे प्रतिनिधि हैं।

लंबाई कम करने के लिए पुनर्गठन के लिए कुछ विचार:

  • यदि आप कोड में टेबल व्यू सेल का निर्माण कर रहे हैं, तो उन्हें nib फाइल या स्टोरीबोर्ड से लोड करने पर विचार करें। स्टोरीबोर्ड प्रोटोटाइप और स्थिर तालिका कोशिकाओं की अनुमति देते हैं - यदि आप परिचित नहीं हैं तो उन सुविधाओं की जांच करें

  • यदि आपके प्रतिनिधि तरीकों में बहुत सारे 'if' स्टेटमेंट्स (या स्टेटमेंट्स स्विच) हैं, तो यह एक क्लासिक संकेत है कि आप कुछ रिफ्लेक्शन कर सकते हैं

यह हमेशा मुझे थोड़ा अजीब लगा कि UITableViewDataSourceडेटा के सही बिट पर एक हैंडल प्राप्त करने और इसे दिखाने के लिए एक दृश्य को कॉन्फ़िगर करने के लिए जिम्मेदार था । एक अच्छा रीफैक्टरिंग पॉइंट हो सकता है cellForRowAtIndexPathकि आप सेल में प्रदर्शित होने वाले डेटा पर एक हैंडल प्राप्त करें , फिर सेल व्यू के निर्माण को किसी अन्य डेलिगेट (जैसे CellViewDelegateया इससे मिलता-जुलता है) को सौंपें जो उचित डेटा आइटम में पास हो जाए।


यह एक अच्छा जवाब है। हालाँकि मेरे सिर में कुछ सवाल उठते हैं। आपको खराब डिज़ाइन होने के लिए बहुत सारे if-statement (या स्विच-स्टेटमेंट) क्यों मिलते हैं? क्या आप वास्तव में मतलब है, बहुत से नेस्टेड- और स्विच-स्टेटमेंट? अगर-या स्विच-स्टेटमेंट से बचने के लिए आप रि-फैक्टर कैसे करते हैं?
जोहान कर्ल्ससन

@JohanKarlsson एक तकनीक बहुरूपता के माध्यम से है। यदि आपको एक प्रकार की वस्तु के साथ एक चीज करने की आवश्यकता है और किसी अन्य प्रकार की चीज के साथ, तो उन वस्तुओं को अलग-अलग वर्ग बनाएं और उन्हें आपके लिए काम चुनने दें।

@GrahamLee हाँ, मैं बहुरूपता जानता हूँ ;-) हालाँकि मुझे यकीन नहीं है कि इस संदर्भ में इसे कैसे लागू किया जाए। कृपया इस पर विस्तार से बताएं।
जोहान कार्लसन ने

@JohanKarlsson ने किया;)

2

यहाँ लगभग इसी तरह की समस्या का सामना करते हुए मैं वर्तमान में क्या कर रहा हूँ:

  • XXX से संबंधित डेटा को XXXDataSource वर्ग में ले जाएं (जो कि BaseDataSource से प्राप्त होता है: NSObject)। BaseDataSource कुछ सुविधाजनक तरीके प्रदान करता है - (NSUInteger)rowsInSection:(NSUInteger)sectionNum;, जैसे उप-वर्ग डेटा लोडिंग विधि को ओवरराइड करता है (क्योंकि ऐप्स में आमतौर पर कुछ प्रकार की ऑफली कैश लोड विधि होती है, - (void)loadDataWithUpdateBlock:(LoadProgressBlock)dataLoadBlock completion:(LoadCompletionBlock)completionBlock;ताकि हम UI को लोडप्रोग्रेडब्लॉक में प्राप्त कैश्ड डेटा के साथ अपडेट कर सकें, जबकि हम नेटवर्क पर जानकारी अपडेट कर रहे हैं, और पूर्ण ब्लॉक में हम नए डेटा के साथ UI को रीफ्रेश करते हैं और यदि कोई है तो प्रोग्रेस इंडिकेटर हटा दें। वे वर्ग UITableViewDataSourceप्रोटोकॉल के अनुरूप नहीं हैं ।

  • BaseTableViewController (जो कि UITableViewDataSourceऔर UITableViewDelegateप्रोटोकॉल के अनुरूप है ) में मेरे पास BaseDataSource का संदर्भ है, जिसे मैं नियंत्रक init के दौरान बनाता हूं। में UITableViewDataSourceनियंत्रक का हिस्सा मैं बस डेटा स्रोत (जैसे से मान - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.tableViewDataSource sectionsCount]; })।

यहाँ मेरा सेलफ़ोरर बेस क्लास में है (उपवर्गों में ओवरराइड करने की आवश्यकता नहीं है):

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellIdentifier = [NSString stringWithFormat:@"%@%@", NSStringFromClass([self class]), @"TableViewCell"];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell) {
        cell = [self createCellForIndexPath:indexPath withCellIdentifier:cellIdentifier];
    }
    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}

configCell उपवर्गों द्वारा ओवरराइड किया जाना चाहिए और CreateCell रिटर्न UITableViewCell हो सकता है, इसलिए यदि आप कस्टम सेल चाहते हैं, तो इसे भी ओवरराइड करें।

  • आधार चीजों को कॉन्फ़िगर करने के बाद (वास्तव में, पहली परियोजना में जो इस तरह की योजना का उपयोग करता है, उसके बाद इस हिस्से का पुन: उपयोग किया जा सकता है) BaseTableViewControllerउपवर्गों के लिए क्या शेष है:

    • ओवरराइड कॉन्फिगरसेल (यह आमतौर पर डेटा को स्रोत पथ के लिए ऑब्जेक्ट के लिए पूछने के लिए बदल देता है और इसे सेल के कॉन्फिगर के लिए फीड करता है ।WithXXX: विधि या ऑब्जेक्ट का UITableViewCell प्रतिनिधित्व प्राप्त करना जैसे कि उपयोगकर्ता 4051 के उत्तर में)

    • ओवरराइड कियाSelectRowAtIndexPath: (जाहिर है)

    • BaseDataSource उपवर्ग लिखें, जो मॉडल के आवश्यक भाग के साथ काम करने का ख्याल रखता है (मान लीजिए कि 2 वर्ग हैं Accountऔर Languageइसलिए, उप- वर्ग खाता दाता स्रोत और LanguageDataSource होगा)।

और यह सब टेबल व्यू भाग के लिए है। मैं जरूरत पड़ने पर GitHub में कुछ कोड पोस्ट कर सकता हूं।

संपादित करें: कुछ recomendations http://www.objc.io/issue-1/lighter-view-controllers.html (जो इस प्रश्न से जुड़ा है) और टेबलव्यू कंट्रौलर्स के बारे में साथी लेख में पाया जा सकता है ।


2

इस पर मेरा विचार है कि मॉडल को ऑब्जेक्ट की एक सरणी देने की आवश्यकता होती है जिसे ViewModel या viewData कहा जाता है जो सेलकोन्फिग्यूरेटर में एनकैप्सुलेटेड होता है। CellConfigurator सेलइंफो को इसे हटाने और सेल को कॉन्फ़िगर करने के लिए आवश्यक रखता है। यह सेल को कुछ डेटा देता है ताकि सेल अपने आप को कॉन्फ़िगर कर सके। यदि आप CellConfigurators को रखने वाले कुछ SectionConfigurator ऑब्जेक्ट को जोड़ते हैं तो यह खंड के साथ भी काम करता है। मैंने कुछ समय पहले ही सेल को केवल व्यूडेटा देने के लिए इसका उपयोग शुरू किया था और सेल को हटाने के साथ व्यूकंट्रोलर डील हुई थी। लेकिन मैंने एक लेख पढ़ा जो इस गिटहब रेपो की ओर इशारा करता है।

https://github.com/fastred/ConfigurableTableViewController

यह आपके द्वारा संपर्क करने के तरीके को बदल सकता है।


2

मैंने हाल ही में UITableView के प्रतिनिधियों और डेटा स्रोतों को लागू करने के तरीके के बारे में एक लेख लिखा है: http://gosuwachu.gitlab.io/2014/01/12/uitableview-controller/

मुख्य विचार अलग-अलग वर्गों में जिम्मेदारियों को विभाजित करने के लिए है, जैसे सेल फैक्टरी, अनुभाग कारखाना, और उस मॉडल के लिए कुछ सामान्य इंटरफ़ेस प्रदान करते हैं जिसे UITableView प्रदर्शित करने जा रहा है। नीचे दिए गए चित्र यह सब बताते हैं:

यहाँ छवि विवरण दर्ज करें


यह लिंक अब काम नहीं कर रहा है।
कोएन

1

एसओएलआईडी सिद्धांतों के बाद इन जैसी किसी भी तरह की समस्याओं का समाधान होगा।

आपके पास करने के लिए अपनी कक्षाओं चाहते हैं बस एक ही जिम्मेदारी है, तो आप अलग परिभाषित करना चाहिए DataSourceऔर Delegateवर्गों और बस इंजेक्षन करने के लिए उन्हें tableViewमालिक (हो सकता है UITableViewControllerया UIViewControllerया कुछ और)। इस तरह आप चिंता से अलग हो जाते हैं

लेकिन अगर आप बस साफ और पढ़ने योग्य कोड चाहते हैं और उस बड़े पैमाने पर व्यू कॉन्ट्रोलर फ़ाइल से छुटकारा पाना चाहते हैं और आप स्विफ़ में हैं , तो आप उसके लिए extensions का उपयोग कर सकते हैं । एकल वर्ग के विस्तार को विभिन्न फाइलों में लिखा जा सकता है और उनमें से सभी एक-दूसरे तक पहुंचते हैं। जैसा कि मैंने उल्लेख किया है कि यह वास्तव में SoC समस्या है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.