सवाल आप UICollectionView flowLayout में कक्षों के बीच अंतर निर्धारित कैसे करते हैं


मेरे पास एक प्रवाह लेआउट के साथ एक UICollectionView है और प्रत्येक सेल एक वर्ग है। मैं प्रत्येक पंक्ति पर प्रत्येक कोशिकाओं के बीच अंतर को कैसे निर्धारित करूं? मुझे इसके लिए उपयुक्त सेटिंग्स नहीं मिल रही हैं। मैं देखता हूं कि संग्रह दृश्य के लिए nib फ़ाइल पर एक मिनट अंतरण विशेषता है, लेकिन मैंने इसे 0 पर सेट किया है और कोशिकाएं भी चिपकती नहीं हैं।

enter image description here

कोई अन्य विचार?


44
2017-10-22 18:15


मूल


यह भी देखें stackoverflow.com/questions/22539979/... - Cœur


जवाब:


अद्यतन: इस उत्तर का स्विफ्ट संस्करण: https://github.com/fanpyi/UICollectionViewLeftAlignedLayout-Swift


ले रहा @ मैट का नेतृत्व मैंने अपने कोड को बीमा करने के लिए संशोधित किया है कि आइटम हमेशा गठबंधन छोड़ दिया जाता है। मैंने पाया कि यदि कोई आइटम स्वयं एक रेखा पर समाप्त होता है, तो यह प्रवाह लेआउट द्वारा केंद्रित होगा। मैंने इस मुद्दे को हल करने के लिए निम्नलिखित परिवर्तन किए हैं।

यह स्थिति केवल तभी होती है जब आपके पास चौड़ाई में भिन्न कोशिकाएं हों, जिसके परिणामस्वरूप निम्न जैसे लेआउट हो सकते हैं। आखिरी पंक्ति हमेशा व्यवहार के कारण संरेखित छोड़ देती है UICollectionViewFlowLayout, यह मुद्दा उन वस्तुओं में निहित है जो स्वयं किसी भी पंक्ति में हैं लेकिन अंतिम व्यक्ति हैं।

@ मैट कोड के साथ मैं देख रहा था।

enter image description here

उस उदाहरण में हम देखते हैं कि यदि कोशिकाएं स्वयं लाइन पर समाप्त होती हैं तो कोशिकाएं केंद्रित होती हैं। नीचे दिया गया कोड आपके संग्रह दृश्य को इस तरह दिखेगा।

enter image description here

#import "CWDLeftAlignedCollectionViewFlowLayout.h"

const NSInteger kMaxCellSpacing = 9;

@implementation CWDLeftAlignedCollectionViewFlowLayout

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:rect];
    for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) {
        if (nil == attributes.representedElementKind) {
            NSIndexPath* indexPath = attributes.indexPath;
            attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame;
        }
    }
    return attributesToReturn;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes* currentItemAttributes =
    [super layoutAttributesForItemAtIndexPath:indexPath];

    UIEdgeInsets sectionInset = [(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout sectionInset];

    if (indexPath.item == 0) { // first item of section
        CGRect frame = currentItemAttributes.frame;
        frame.origin.x = sectionInset.left; // first item of the section should always be left aligned
        currentItemAttributes.frame = frame;

        return currentItemAttributes;
    }

    NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
    CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame;
    CGFloat previousFrameRightPoint = previousFrame.origin.x + previousFrame.size.width + kMaxCellSpacing;

    CGRect currentFrame = currentItemAttributes.frame;
    CGRect strecthedCurrentFrame = CGRectMake(0,
                                              currentFrame.origin.y,
                                              self.collectionView.frame.size.width,
                                              currentFrame.size.height);

    if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) { // if current item is the first item on the line
        // the approach here is to take the current frame, left align it to the edge of the view
        // then stretch it the width of the collection view, if it intersects with the previous frame then that means it
        // is on the same line, otherwise it is on it's own new line
        CGRect frame = currentItemAttributes.frame;
        frame.origin.x = sectionInset.left; // first item on the line should always be left aligned
        currentItemAttributes.frame = frame;
        return currentItemAttributes;
    }

    CGRect frame = currentItemAttributes.frame;
    frame.origin.x = previousFrameRightPoint;
    currentItemAttributes.frame = frame;
    return currentItemAttributes;
}

@end

68
2018-03-21 17:51



ऑफ-विषय प्रश्न, मैं आपके जैसे टैग यूआई तत्व को कैसे कार्यान्वित कर सकता हूं, सुझाव देने के लिए कोई ओपन सोर्स प्रोजेक्ट? धन्यवाद - x.y
लक्ष्य के बिना UIButton का उपयोग करें (या यदि आपको आवश्यकता हो तो लक्ष्य के साथ), गिटहब पर बहुत सारे कस्टम UIButtons हैं, बस उद्देश्य-सी श्रेणी में "बटन" खोजें - GangstaGraham
आप मेरे स्क्रीनशॉट में क्या देख रहे हैं UICollectionViewCellए के भीतर है UICollectionView। स्पर्श घटनाओं को प्रतिनिधि तरीकों से संभाला जाता है। फिर वे नीले रंग के अलावा एक खिंचाव पृष्ठभूमि का उपयोग करके स्टाइल कर रहे हैं, यह केवल एक सेल के भीतर एक छवि है। जो मुझे पता है उससे कोई ओपन सोर्स स्टफ नहीं है लेकिन यह एक के साथ बहुत आसान है UICollectionView, कठिन हिस्सा उन्हें इस तरह संरेखित करने के लिए मिल रहा था, और यह आपके लिए यहां किया गया है;) - Chris Wagner
इस लिए आपका बहुत - बहुत धन्यवाद! यह अविश्वसनीय रूप से सहायक था और मुझे एक टन बचाया। मैं सोच रहा था कि अगर आपको इस प्रवाह को क्षैतिज रूप से बनाने के बारे में कोई सलाह है, क्योंकि मैं वर्तमान में UITableViewCells में UICollectionViews डाल रहा हूं और लंबवत दिशा को अवरुद्ध किया जा रहा है (और वैसे भी अच्छा ux नहीं होगा)। - AdamG
कोड में संभावित सुधार के लिए यहां देखें: stackoverflow.com/review/suggested-edits/4003993 - Robert Harvey♦


अधिकतम इंटरिटिम स्पेसिंग प्राप्त करने के लिए, उपclass UICollectionViewFlowLayout और ओवरराइड करें layoutAttributesForElementsInRect: तथा layoutAttributesForItemAtIndexPath:

उदाहरण के लिए, एक आम समस्या यह है: संग्रह दृश्य की पंक्तियां बाएं-उचित वाली अंतिम पंक्ति को छोड़कर, सही और बाएं उचित हैं। मान लें कि हम चाहते हैं सब लाइनों को बाएं-उचित होने के लिए, ताकि उनके बीच की जगह है, मान लें, 10 अंक। यहां एक आसान तरीका है (आपके UICollectionViewFlowLayout उपclass में):

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray* arr = [super layoutAttributesForElementsInRect:rect];
    for (UICollectionViewLayoutAttributes* atts in arr) {
        if (nil == atts.representedElementKind) {
            NSIndexPath* ip = atts.indexPath;
            atts.frame = [self layoutAttributesForItemAtIndexPath:ip].frame;
        }
    }
    return arr;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes* atts =
    [super layoutAttributesForItemAtIndexPath:indexPath];

    if (indexPath.item == 0) // degenerate case 1, first item of section
        return atts;

    NSIndexPath* ipPrev =
    [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];

    CGRect fPrev = [self layoutAttributesForItemAtIndexPath:ipPrev].frame;
    CGFloat rightPrev = fPrev.origin.x + fPrev.size.width + 10;
    if (atts.frame.origin.x <= rightPrev) // degenerate case 2, first item of line
        return atts;

    CGRect f = atts.frame;
    f.origin.x = rightPrev;
    atts.frame = f;
    return atts;
}

कारण यह इतना आसान है कि हम वास्तव में लेआउट की भारी उठाने नहीं कर रहे हैं; हम उस लेआउट कार्य का लाभ उठा रहे हैं जो UICollectionViewFlowLayout पहले से ही हमारे लिए किया गया है। यह पहले ही तय कर चुका है कि प्रत्येक पंक्ति में कितनी वस्तुएं जाती हैं; यदि आप देखते हैं कि मेरा क्या मतलब है, तो हम केवल उन पंक्तियों को पढ़ रहे हैं और वस्तुओं को एक साथ जोड़ रहे हैं।


32
2017-11-06 20:02



सिर्फ एक सवाल है क्योंकि मेरे पास अभी आपके कोड का परीक्षण करने की संभावना नहीं है। क्या यह उत्तर मुझे यहां आने वाली समस्या को ठीक करने में मदद करेगा? stackoverflow.com/questions/13411609/... - Joakim Engstrom
बहुत बढ़िया, यहां कोड के लंबवत संस्करण को जोड़ा गया: stackoverflow.com/questions/14482882/...  भयानक कोड के लिए धन्यवाद - Rob R.
समाधान के लिए धन्यवाद। एक मामूली बिंदु: प्रति पंक्ति क्षैतिज वस्तुओं के बड़े सेट के साथ संग्रह दृश्य होने पर इस कोड के भीतर रिकर्स महंगा हो सकता है (संग्रहView.contentSize.width सेल चौड़ाई से बहुत बड़ा है)। - ricosrealm


विचार करने के लिए कुछ बातें हैं:

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

  2. परिवर्तन करने के बाद अपनी स्टोरीबोर्ड / xib फ़ाइल सहेजें, और ऐप को पुनर्निर्माण करना सुनिश्चित करें। उस चरण को याद करना मुश्किल नहीं है, और फिर आप अपने सिर को खरोंच कर रहे हैं कि आपके परिवर्तनों का कोई प्रभाव क्यों नहीं प्रतीत होता है।

  3. UICollectionViewFlowLayout एक minimumInteritemSpacing संपत्ति, जो आप आईबी में स्थापित कर रहे हैं। लेकिन संग्रह के प्रतिनिधि भी हो सकते हैं अंतर-वस्तु अंतर को निर्धारित करने के लिए एक विधि। उस विधि ट्रम्प की लेआउट की संपत्ति है, इसलिए यदि आप इसे अपने प्रतिनिधि में लागू करते हैं तो आपके लेआउट की संपत्ति का उपयोग नहीं किया जाएगा।

  4. याद रखें कि रिक्ति एक है न्यूनतम रिक्ति। लेआउट उस नंबर का उपयोग करेगा (चाहे वह संपत्ति से या प्रतिनिधि विधि से आता है) सबसे छोटी स्वीकार्य जगह के रूप में, लेकिन यदि यह लाइन पर स्थान बचे हुए है तो यह एक बड़ी जगह का उपयोग कर सकता है। तो यदि, उदाहरण के लिए, आप न्यूनतम रिक्ति को 0 पर सेट करते हैं, तो आप अभी भी आइटम के बीच कुछ पिक्सल देख सकते हैं। यदि आप वास्तव में वस्तुओं को कैसे स्थानांतरित करते हैं, इस पर अधिक नियंत्रण चाहते हैं तो आपको शायद एक अलग लेआउट का उपयोग करना चाहिए (संभवतः अपनी खुद की रचना में से एक)।


16
2017-10-22 19:18



भयानक उत्तर के लिए धन्यवाद। मुझे लगता है कि मैं जो अधिक देख रहा हूं वह अधिकतम इंटेरिटम स्पेसिंग है, क्या ऐसा मूल्य है? - adit
नहीं, अधिकतम मूल्य नहीं है। यदि आप फ्लो लेआउट के बारे में सोचते हैं, तो आप देखेंगे क्यों। प्रवाह लेआउट एक पंक्ति पर कई वस्तुओं को रखता है जो कि न्यूनतम आइटम रिक्ति का सम्मान करते समय फिट हो सकता है। उस समय, वहाँ होगा n पिक्सेल छोड़ दिया, जहां n 0 से अधिक या उसके बराबर है और अगले आइटम के आकार से कम न्यूनतम अंतर है। लेआउट तब उन एन पिक्सल लेता है और उन्हें समान रूप से पंक्तियों पर आइटम को स्थानांतरित करने के लिए वितरित करता है। यदि आप अधिकतम रिक्ति सेट करते हैं तो यह ऐसा नहीं कर सका। हालांकि, आप स्पेस आइटम्स को अपना खुद का लेआउट लिख सकते हैं, हालांकि आप चाहें। - Caleb
+1, आप सेट कर सकते हैं minimumInteritemSpacing तथा minimumLineSpacing साथ ही, आप जो हासिल करने की कोशिश कर रहे हैं उसके आधार पर। - iDev
क्या आप लेआउट के प्रतिनिधि में सटीक विधि की पहचान कर सकते हैं जो क्षैतिज अंतर-वस्तु अंतर निर्धारित कर सकता है। - Drux
@Drux क्षमा करें - खराब शब्द। मैं इसे ठीक कर दूंगा। वहां एक है UICollectionViewDelegateFlowLayout प्रोटोकॉल कि संग्रह दृश्य का प्रतिनिधि प्रवाह लेआउट का समर्थन करने के लिए अपनाया जा सकता है। विधि देखें – collectionView:layout:minimumInteritemSpacingForSectionAtIndex:। - Caleb


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

बस इस तरह मॉड्यूलो का उपयोग करें (मैं अपने न्यूनतम इंटरिटैम स्पेसिंग मान को अधिकतम मान के रूप में भी उपयोग कर रहा हूं):

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes* currentItemAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
    NSInteger numberOfItemsPerLine = floor([self collectionViewContentSize].width / [self itemSize].width);

    if (indexPath.item % numberOfItemsPerLine != 0)
    {
        NSInteger cellIndexInLine = (indexPath.item % numberOfItemsPerLine);

        CGRect itemFrame = [currentItemAttributes frame];
        itemFrame.origin.x = ([self itemSize].width * cellIndexInLine) + ([self minimumInteritemSpacing] * cellIndexInLine);
        currentItemAttributes.frame = itemFrame;
    }

    return currentItemAttributes;
}

4
2017-09-06 18:57



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


बाएं-औचित्य का एक आसान तरीका लेआउट को संशोधित करना है AttributesForElementsInRect: UICollectionViewFlowLayout के अपने उप-वर्ग में:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *allLayoutAttributes = [super layoutAttributesForElementsInRect:rect];
    CGRect prevFrame = CGRectMake(-FLT_MAX, -FLT_MAX, 0, 0);
    for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes)
    {
        //fix blur
        CGRect theFrame = CGRectIntegral(layoutAttributes.frame);

        //left justify
        if(prevFrame.origin.x > -FLT_MAX &&
           prevFrame.origin.y >= theFrame.origin.y &&
           prevFrame.origin.y <= theFrame.origin.y) //workaround for float == warning
        {
            theFrame.origin.x = prevFrame.origin.x +
                                prevFrame.size.width + 
                                EXACT_SPACE_BETWEEN_ITEMS;
        }
        prevFrame = theFrame;

        layoutAttributes.frame = theFrame;
    }
    return allLayoutAttributes;
}

3
2018-03-13 23:26





क्रिस समाधान का तेज़ संस्करण।

class PazLeftAlignedCollectionViewFlowLayout : UICollectionViewFlowLayout {
    var maxCellSpacing = 14.0
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
        if var attributesToReturn = super.layoutAttributesForElementsInRect(rect) as? Array<UICollectionViewLayoutAttributes> {
            for attributes in attributesToReturn {
                if attributes.representedElementKind == nil {
                    let indexPath = attributes.indexPath
                    attributes.frame = self.layoutAttributesForItemAtIndexPath(indexPath).frame;
                }
            }
            return attributesToReturn;
        }
        return super.layoutAttributesForElementsInRect(rect)
    }

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
        let currentItemAttributes = super.layoutAttributesForItemAtIndexPath(indexPath)

        if let collectionViewFlowLayout = self.collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
            let sectionInset = collectionViewFlowLayout.sectionInset
            if (indexPath.item == 0) { // first item of section
                var frame = currentItemAttributes.frame;
                frame.origin.x = sectionInset.left; // first item of the section should always be left aligned
                currentItemAttributes.frame = frame;

                return currentItemAttributes;
            }

            let previousIndexPath = NSIndexPath(forItem:indexPath.item-1, inSection:indexPath.section)
            let previousFrame = self.layoutAttributesForItemAtIndexPath(previousIndexPath).frame;
            let previousFrameRightPoint = Double(previousFrame.origin.x) + Double(previousFrame.size.width) + self.maxCellSpacing

            let currentFrame = currentItemAttributes.frame
            var width : CGFloat = 0.0
            if let collectionViewWidth = self.collectionView?.frame.size.width {
                width = collectionViewWidth
            }
            let strecthedCurrentFrame = CGRectMake(0,
                currentFrame.origin.y,
                width,
                currentFrame.size.height);

            if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) { // if current item is the first item on the line
                // the approach here is to take the current frame, left align it to the edge of the view
                // then stretch it the width of the collection view, if it intersects with the previous frame then that means it
                // is on the same line, otherwise it is on it's own new line
                var frame = currentItemAttributes.frame;
                frame.origin.x = sectionInset.left; // first item on the line should always be left aligned
                currentItemAttributes.frame = frame;
                return currentItemAttributes;
            }

            var frame = currentItemAttributes.frame;
            frame.origin.x = CGFloat(previousFrameRightPoint)
            currentItemAttributes.frame = frame;
        }
        return currentItemAttributes;
    }
}

इसका उपयोग करने के लिए निम्नलिखित कार्य करें:

    override func viewDidLoad() {
    super.viewDidLoad()
    self.collectionView.collectionViewLayout = self.layout
}
var layout : PazLeftAlignedCollectionViewFlowLayout {
    var layout = PazLeftAlignedCollectionViewFlowLayout()
    layout.itemSize = CGSizeMake(220.0, 230.0)
    layout.minimumLineSpacing = 12.0
    return layout
}

2
2017-11-07 20:02



इस कोड ने वास्तव में मेरी मदद की। बहुत बहुत धन्यवाद। - Tommy


क्रिस वाग्नेर के उत्तर के आधार पर रुचि रखने वाले लोगों के लिए एक क्लीनर स्विफ्ट संस्करण:

class AlignLeftFlowLayout: UICollectionViewFlowLayout {

    var maximumCellSpacing = CGFloat(9.0)

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
        let attributesToReturn = super.layoutAttributesForElementsInRect(rect) as? [UICollectionViewLayoutAttributes]

        for attributes in attributesToReturn ?? [] {
            if attributes.representedElementKind == nil {
                attributes.frame = self.layoutAttributesForItemAtIndexPath(attributes.indexPath).frame
            }
        }

        return attributesToReturn
    }

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
        let curAttributes = super.layoutAttributesForItemAtIndexPath(indexPath)
        let sectionInset = (self.collectionView?.collectionViewLayout as UICollectionViewFlowLayout).sectionInset

        if indexPath.item == 0 {
            let f = curAttributes.frame
            curAttributes.frame = CGRectMake(sectionInset.left, f.origin.y, f.size.width, f.size.height)
            return curAttributes
        }

        let prevIndexPath = NSIndexPath(forItem: indexPath.item-1, inSection: indexPath.section)
        let prevFrame = self.layoutAttributesForItemAtIndexPath(prevIndexPath).frame
        let prevFrameRightPoint = prevFrame.origin.x + prevFrame.size.width + maximumCellSpacing

        let curFrame = curAttributes.frame
        let stretchedCurFrame = CGRectMake(0, curFrame.origin.y, self.collectionView!.frame.size.width, curFrame.size.height)

        if CGRectIntersectsRect(prevFrame, stretchedCurFrame) {
            curAttributes.frame = CGRectMake(prevFrameRightPoint, curFrame.origin.y, curFrame.size.width, curFrame.size.height)
        } else {
            curAttributes.frame = CGRectMake(sectionInset.left, curFrame.origin.y, curFrame.size.width, curFrame.size.height)
        }

        return curAttributes
    }
}

1
2018-03-11 14:52