ios - ऑटो लेआउट का उपयोग कर UICollectionView में कक्षों का एक आयाम निर्दिष्ट करना




objective-c autolayout (4)

आईओएस 8 में UICollectionViewFlowLayout स्वचालित रूप से अपने स्वयं के सामग्री आकार के आधार पर कोशिकाओं का आकार बदलने का समर्थन करता है। यह उनकी सामग्री के अनुसार चौड़ाई और ऊंचाई दोनों में कोशिकाओं का आकार बदलता है।

क्या सभी कोशिकाओं की चौड़ाई (या ऊंचाई) के लिए एक निश्चित मान निर्दिष्ट करना संभव है और अन्य आयामों का आकार बदलने की अनुमति है?

एक साधारण उदाहरण के लिए सेल में एक बहु-लाइन लेबल पर विचार करें जिसमें सेल के किनारों पर स्थित बाधाएं होती हैं। मल्टी-लाइन लेबल को पाठ को समायोजित करने के विभिन्न तरीकों का आकार बदल सकता है। सेल को संग्रह दृश्य की चौड़ाई भरनी चाहिए और तदनुसार इसकी ऊंचाई समायोजित करना चाहिए। इसके बजाए, कोशिकाओं को खतरनाक रूप से आकार दिया जाता है और यह तब भी क्रैश का कारण बनता है जब सेल आकार संग्रह दृश्य के गैर-स्क्रोल करने योग्य आयाम से बड़ा होता है।

आईओएस 8 विधि प्रणाली पेश करता है systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority: संग्रह में प्रत्येक सेल के लिए लेआउट अनुमानित आकार में गुजरने वाले सेल पर इस विधि को कॉल करता है। मुझे समझ में आता है कि सेल पर इस विधि को ओवरराइड करना होगा, दिए गए आकार में पास होना होगा और आवश्यक क्षैतिज बाधा सेट करें और लंबवत बाधा को कम प्राथमिकता दें। इस तरह क्षैतिज आकार लेआउट में मान सेट पर तय किया गया है और लंबवत आकार लचीला हो सकता है।

कुछ इस तरह:

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    UICollectionViewLayoutAttributes *attributes = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
    attributes.size = [self systemLayoutSizeFittingSize:layoutAttributes.size withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    return attributes;
}

हालांकि, इस विधि द्वारा दिए गए आकार पूरी तरह से अजीब हैं। इस विधि पर प्रलेखन मेरे लिए बहुत अस्पष्ट है और स्थिरांक UILayoutFittingCompressedSize UILayoutFittingExpandedSize का उपयोग करने का उल्लेख करता है जो केवल शून्य आकार और एक बहुत बड़ा प्रतिनिधित्व करता है।

क्या इस विधि का size पैरामीटर वास्तव में दो स्थिरांक में पास करने का एक तरीका है? क्या किसी दिए गए आकार के लिए उचित ऊंचाई प्राप्त करने की अपेक्षा करने वाले व्यवहार को प्राप्त करने का कोई तरीका नहीं है?

वैकल्पिक समाधान

1) बाधाओं को जोड़ना जो सेल के लिए एक विशिष्ट चौड़ाई निर्दिष्ट करेगा, सही लेआउट प्राप्त करता है। यह एक खराब समाधान है क्योंकि उस बाधा को सेल के संग्रह दृश्य के आकार पर सेट किया जाना चाहिए जिसका इसका कोई सुरक्षित संदर्भ नहीं है। जब सेल कॉन्फ़िगर किया गया है तो उस बाधा के लिए मूल्य पारित किया जा सकता है, लेकिन यह पूरी तरह से counterintuitive लगता है। यह भी अजीब है क्योंकि किसी सेल या सीधे सामग्री दृश्य में बाधाओं को जोड़ना कई समस्याएं पैदा कर रहा है।

2) एक टेबल व्यू का प्रयोग करें। तालिका दृश्य बॉक्स के बाहर इस तरह से काम करते हैं क्योंकि कोशिकाओं की निश्चित चौड़ाई होती है, लेकिन यह कई कॉलमों में निश्चित चौड़ाई वाली कोशिकाओं के साथ आईपैड लेआउट जैसी अन्य स्थितियों को समायोजित नहीं करेगी।


ऐसा लगता है कि आप जो पूछ रहे हैं वह UITollectionView का उपयोग करने के लिए UITollectionView का उपयोग करने का एक तरीका है। यदि यह वास्तव में आप चाहते हैं, तो ऐसा करने का सही तरीका एक कस्टम UICollectionViewLayout उपclass (शायद SBTableLayout तरह कुछ) के साथ है।

दूसरी ओर, यदि आप वास्तव में पूछ रहे हैं कि डिफ़ॉल्ट UICollectionViewFlowLayout के साथ ऐसा करने का एक साफ तरीका है, तो मुझे विश्वास है कि कोई रास्ता नहीं है। आईओएस 8 के स्वयं आकार के कोशिकाओं के साथ भी, यह सीधा नहीं है। जैसा कि आप कहते हैं, मौलिक समस्या यह है कि प्रवाह लेआउट की मशीनरी एक आयाम को ठीक करने का कोई तरीका नहीं देती है और दूसरा जवाब देती है। (इसके अतिरिक्त, यहां तक ​​कि यदि आप कर सकते हैं, तो बहु-रेखा लेबलों के आकार के लिए दो लेआउट पास की आवश्यकता के आसपास अतिरिक्त जटिलता होगी। यह स्व-आकार देने वाली कोशिकाओं को सिस्टम के लिए एक कॉल के माध्यम से सभी आकारों की गणना करने के लिए कैसे फिट करना चाहिए LayoutSizeFittingSize।)

हालांकि, यदि आप अभी भी फ्लो लेआउट के साथ एक टेबलव्यू-जैसे लेआउट बनाना चाहते हैं, तो सेल के साथ जो अपना आकार निर्धारित करते हैं, और संग्रह दृश्य की चौड़ाई के लिए स्वाभाविक रूप से प्रतिक्रिया देते हैं, बेशक यह संभव है। अभी भी गन्दा रास्ता है। मैंने इसे "साइजिंग सेल" के साथ किया है, यानी, एक गैर-प्रदर्शित यूआईसीओलेक्शन व्यूसेल कि नियंत्रक केवल सेल आकार की गणना के लिए रहता है।

इस दृष्टिकोण के दो भाग हैं। संग्रह भाग की चौड़ाई लेने और सेल की ऊंचाई की गणना करने के लिए आकार देने वाले सेल का उपयोग करके, सही सेल आकार की गणना करने के लिए संग्रह दृश्य प्रतिनिधि के लिए पहला भाग संग्रह दृश्य प्रतिनिधि के लिए है।

आपके UICollectionViewDelegateFlowLayout में, आप इस तरह की एक विधि लागू करते हैं:

func collectionView(collectionView: UICollectionView,
  layout collectionViewLayout: UICollectionViewLayout,
  sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
  // NOTE: here is where we say we want cells to use the width of the collection view
   let requiredWidth = collectionView.bounds.size.width

   // NOTE: here is where we ask our sizing cell to compute what height it needs
  let targetSize = CGSize(width: requiredWidth, height: 0)
  /// NOTE: populate the sizing cell's contents so it can compute accurately
  self.sizingCell.label.text = items[indexPath.row]
  let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
  return adequateSize
}

यह संग्रहण दृश्य को संलग्न संग्रह दृश्य के आधार पर सेल की चौड़ाई निर्धारित करने का कारण बनता है, लेकिन फिर आकार की गणना करने के लिए आकार देने वाले सेल से पूछता है।

दूसरा भाग आकार की गणना करने के लिए आकार देने वाले सेल को अपनी स्वयं की एएल बाधाओं का उपयोग करना है। यह होना चाहिए जितना कठिन होना चाहिए, जिस तरह से बहु-पंक्ति UILabel के प्रभावी ढंग से दो-चरण लेआउट प्रक्रिया की आवश्यकता होती है। काम preferredLayoutSizeFittingSize विधि में किया जाता है preferredLayoutSizeFittingSize , जो ऐसा है:

 /*
 Computes the size the cell will need to be to fit within targetSize.

 targetSize should be used to pass in a width.

 the returned size will have the same width, and the height which is
 calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
 can fit within that width.

 */
 func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {

   // save original frame and preferredMaxLayoutWidth
   let originalFrame = self.frame
   let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth

   // assert: targetSize.width has the required width of the cell

   // step1: set the cell.frame to use that width
   var frame = self.frame
   frame.size = targetSize
   self.frame = frame

   // step2: layout the cell
   self.setNeedsLayout()
   self.layoutIfNeeded()
   self.label.preferredMaxLayoutWidth = self.label.bounds.size.width

   // assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width

   // step3: compute how tall the cell needs to be

   // this causes the cell to compute the height it needs, which it does by asking the 
   // label what height it needs to wrap within its current bounds (which we just set).
   let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)

   // assert: computedSize has the needed height for the cell

   // Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
   let newSize = CGSize(width:targetSize.width,height:computedSize.height)

   // restore old frame and preferredMaxLayoutWidth
   self.frame = originalFrame
   self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth

   return newSize
 }

(यह कोड "उन्नत संग्रह दृश्य" पर WWDC2014 सत्र के नमूना कोड से ऐप्पल नमूना कोड से अनुकूलित किया गया है।)

ध्यान देने के लिए कुछ बिंदु। लेबल की चौड़ाई की गणना और सेट करने के लिए, यह पूरे सेल के लेआउट को मजबूर करने के लिए लेआउट IfNeeded () का उपयोग कर रहा है। लेकिन यह पर्याप्त नहीं है। मेरा मानना ​​है कि आपको preferredMaxLayoutWidth सेट करने की भी आवश्यकता है ताकि लेबल ऑटो लेआउट के साथ उस चौड़ाई का उपयोग करेगा। और फिर लेबल को ध्यान में रखते हुए सेल को अपनी ऊंचाई की गणना करने के लिए systemLayoutSizeFittingSize का उपयोग कर सकते हैं।

क्या मुझे यह दृष्टिकोण पसंद है? नहीं!! यह बहुत जटिल लगता है, और यह दो बार लेआउट करता है। लेकिन जब तक प्रदर्शन कोई मुद्दा नहीं बनता है, तब तक मैं इसे कोड में दो बार परिभाषित करने के बजाय रनटाइम पर लेआउट दो बार करना चाहता हूं, जो कि एकमात्र अन्य विकल्प प्रतीत होता है।

मेरी आशा यह है कि आखिरकार आत्म-आकार देने वाली कोशिकाएं अलग-अलग काम करेंगी और यह सब बहुत आसान हो जाएगी।

उदाहरण परियोजना इसे काम पर दिखा रही है।

लेकिन क्यों न केवल आत्म आकार देने वाली कोशिकाओं का उपयोग करें?

सिद्धांत रूप में, "स्वयं आकार देने वाली कोशिकाओं" के लिए आईओएस 8 की नई सुविधाओं को यह अनावश्यक बनाना चाहिए। यदि आपने ऑटो लेआउट (एएल) वाले सेल को परिभाषित किया है, तो संग्रह दृश्य को आकार देने के लिए पर्याप्त स्मार्ट होना चाहिए और स्वयं को सही तरीके से बाहर रखना चाहिए। प्रैक्टिस में, मैंने ऐसा कोई उदाहरण नहीं देखा है जो बहु-लाइन लेबल के साथ काम करने के लिए प्राप्त कर चुका है। मुझे लगता है कि this आंशिक रूप से है क्योंकि आत्म-आकार देने वाला सेल तंत्र अभी भी छोटी है।

लेकिन मैं शर्त लगाता हूं कि यह ज्यादातर ऑटो लेआउट और लेबल की सामान्य चाल की वजह से है, जो कि UILabels को मूल रूप से दो-चरणीय लेआउट प्रक्रिया की आवश्यकता होती है। यह मुझे स्पष्ट नहीं है कि आप स्वयं आकार देने वाली कोशिकाओं के साथ दोनों चरणों को कैसे कर सकते हैं।

और जैसे मैंने कहा, यह वास्तव में एक अलग लेआउट के लिए एक नौकरी है। यह फ्लो लेआउट के सार का हिस्सा है कि यह चौड़ाई को ठीक करने के बजाय आकार की चीजें रखता है और उन्हें अपनी ऊंचाई चुनने देता है।

और पसंदीदा LayoutAttributesFittingAttributes के बारे में क्या:?

preferredLayoutAttributesFittingAttributes: विधि एक लाल हेरिंग है, मुझे लगता है। यह केवल नए आत्म-आकार देने वाले सेल तंत्र के साथ उपयोग किया जाना है। तो यह तब तक जवाब नहीं है जब तक कि तंत्र अविश्वसनीय है।

और systemlayoutSizeFittingSize के साथ क्या हो रहा है :?

आप सही हैं कि दस्तावेज़ उलझन में हैं।

systemLayoutSizeFittingSize: पर दस्तावेज़ systemLayoutSizeFittingSize: और systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: दोनों सुझाव देते हैं कि आपको केवल UILayoutFittingCompressedSize और UILayoutFittingExpandedSize को UILayoutFittingExpandedSize रूप में पास करना चाहिए। हालांकि, विधि हस्ताक्षर स्वयं, हेडर टिप्पणियां, और कार्यों का व्यवहार इंगित करता है कि वे targetSize पैरामीटर के सटीक मान का जवाब दे रहे हैं।

वास्तव में, यदि आप नए स्वयं आकार के सेल तंत्र को सक्षम करने के लिए UICollectionViewFlowLayoutDelegate.estimatedItemSize सेट करते हैं, तो यह UICollectionViewFlowLayoutDelegate.estimatedItemSize आकार के रूप में पारित होने लगता है। और UILabel.systemLayoutSizeFittingSize समान सटीक मान वापस करने लगता है। यह संदिग्ध है, यह देखते हुए कि systemLayoutSizeFittingSize लिए तर्क एक मोटा लक्ष्य माना जाता है और आकार के लिए तर्क sizeThatFits: अधिकतम आकार निर्धारित आकार माना जाता है।

और अधिक संसाधनों

हालांकि यह सोचने में दुखी है कि इस तरह की नियमित आवश्यकता के लिए "शोध संसाधन" की आवश्यकता होनी चाहिए, मुझे लगता है कि यह करता है। अच्छे उदाहरण और चर्चाएं हैं:


कोड की कुछ पंक्तियों में आईओएस 9 में ऐसा करने का एक आसान तरीका - क्षैतिज तरीका उदाहरण (इसकी ऊंचाई दृश्य संग्रह की ऊंचाई को ठीक करना):

आत्म-आकार देने वाले सेल को सक्षम करने के लिए estimatedItemSize ItemSize के साथ अपने संग्रह दृश्य फ़्लो लेआउट को इनिट करें:

self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.estimatedItemSize = CGSizeMake(1, 1);

कलेक्शन व्यू लेआउट प्रतिनिधि (आपके व्यू कंट्रोलर में ज्यादातर समय) को कार्यान्वित करें, संग्रह दृश्य collectionView:layout:sizeForItemAtIndexPath: :। यहां लक्ष्य संग्रह दृश्य आयाम में निश्चित ऊंचाई (या चौड़ाई) सेट करना है। 10 मान कुछ भी हो सकता है, लेकिन आपको इसे उस मान पर सेट करना चाहिए जो बाधाओं को तोड़ता नहीं है:

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(UICollectionViewLayout *)collectionViewLayout
  sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return CGSizeMake(10, CGRectGetHeight(collectionView.bounds));
}

अपने कस्टम सेल को ओवरराइड करें LayoutAttributesFittingAttributes preferredLayoutAttributesFittingAttributes: विधि, यह भाग वास्तव में आपके ऑटो लेआउट बाधाओं और ऊंचाई जो आपने अभी सेट की है, के आधार पर आपकी गतिशील सेल चौड़ाई की गणना करता है:

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
    UICollectionViewLayoutAttributes *attributes = [layoutAttributes copy];
    float desiredWidth = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].width;
    CGRect frame = attributes.frame;
    frame.size.width = desiredWidth;
    attributes.frame = frame;
    return attributes;
}

यहां कुछ अन्य उत्तरों की तुलना में ऐसा करने का एक क्लीनर तरीका है, और यह अच्छी तरह से काम करता है। यह निष्पादक होना चाहिए (संग्रह दृश्य तेजी से लोड होते हैं, कोई अनावश्यक ऑटो लेआउट पास इत्यादि नहीं), और इसमें कोई निश्चित संग्रह दृश्य चौड़ाई जैसी कोई 'जादू संख्या' नहीं है। संग्रह दृश्य आकार बदलना, उदाहरण के लिए रोटेशन पर, और फिर लेआउट को अमान्य करना भी बहुत अच्छा काम करना चाहिए।

1. निम्नलिखित प्रवाह लेआउट उपclass बनाएँ

class HorizontallyFlushCollectionViewFlowLayout: UICollectionViewFlowLayout {

    // Don't forget to use this class in your storyboard (or code, .xib etc)

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
        let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes
        guard let collectionView = collectionView else { return attributes }
        attributes?.bounds.size.width = collectionView.bounds.width - sectionInset.left - sectionInset.right
        return attributes
    }

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let allAttributes = super.layoutAttributesForElementsInRect(rect)
        return allAttributes?.flatMap { attributes in
            switch attributes.representedElementCategory {
            case .Cell: return layoutAttributesForItemAtIndexPath(attributes.indexPath)
            default: return attributes
            }
        }
    }
}

2. स्वचालित आकार बदलने के लिए अपने संग्रह दृश्य को पंजीकृत करें

// The provided size should be a plausible estimate of the actual
// size. You can set your item size in your storyboard
// to a good estimate and use the code below. Otherwise,
// you can provide it manually too, e.g. CGSize(width: 100, height: 100)

flowLayout.estimatedItemSize = flowLayout.itemSize

3. अपने सेल सबक्लास में पूर्वनिर्धारित चौड़ाई + कस्टम ऊंचाई का उपयोग करें

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    layoutAttributes.bounds.size.height = systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    return layoutAttributes
}

हां इसे स्वचालित रूप से ऑटो लेआउट का उपयोग करके और स्टोरीबोर्ड या xib में बाधाओं को सेट करके किया जा सकता है। चौड़ाई के आकार के लिए आपको लगातार बने रहने के लिए बाधा जोड़ने की आवश्यकता है और ऊंचाई से अधिक या बराबर सेट करें।
http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/

http://www.cocoanetics.com/2013/08/variable-sized-items-in-uicollectionview/
उम्मीद है कि यह सहायक होगा और आपकी समस्या का समाधान करेगा।





autolayout