UICollectionView flowLayout umschließt Zellen nicht korrekt

79

Ich habe ein UICollectionViewmit einem FLowLayout. Es wird die meiste Zeit so funktionieren, wie ich es erwartet habe, aber hin und wieder wird eine der Zellen nicht richtig gewickelt. Zum Beispiel die Zelle, die sich in der ersten "Spalte" der dritten Zeile befinden sollte, wenn sie tatsächlich in der zweiten Zeile nachläuft und nur ein leerer Bereich vorhanden ist, in dem sie sich befinden sollte (siehe Abbildung unten). Alles, was Sie von dieser Rougezelle sehen können, ist die linke Seite (der Rest ist abgeschnitten) und die Stelle, an der sie sein sollte, ist leer.

Dies geschieht nicht konsequent; Es ist nicht immer dieselbe Zeile. Sobald es passiert ist, kann ich nach oben und dann zurück scrollen und die Zelle hat sich selbst repariert. Wenn ich auf die Zelle drücke (die mich per Push zur nächsten Ansicht bringt) und dann zurückspringe, sehe ich die Zelle an der falschen Position und springe dann zur richtigen Position.

Die Bildlaufgeschwindigkeit scheint es einfacher zu machen, das Problem zu reproduzieren. Wenn ich langsam scrolle, sehe ich immer noch hin und wieder die Zelle an der falschen Position, aber dann springt sie sofort an die richtige Position.

Das Problem begann, als ich die Abschnitte einfügte. Zuvor hatte ich die Zellen fast bündig mit den Sammlungsgrenzen (wenig oder keine Einfügungen) und ich bemerkte das Problem nicht. Dies bedeutete jedoch, dass rechts und links von der Sammlungsansicht leer war. Dh konnte nicht scrollen. Außerdem war die Bildlaufleiste nicht bündig nach rechts.

Ich kann das Problem sowohl auf dem Simulator als auch auf einem iPad 3 lösen.

Ich denke, das Problem tritt aufgrund der Einfügungen im linken und rechten Abschnitt auf ... Aber wenn der Wert falsch ist, würde ich erwarten, dass das Verhalten konsistent ist. Ich frage mich, ob dies ein Fehler bei Apple sein könnte. Oder vielleicht liegt dies an einem Aufbau der Einsätze oder Ähnlichem.

Abbildung des Problems und der Einstellungen


Follow-up : Ich verwende diese Antwort von Nick seit über 2 Jahren ohne Probleme (falls sich die Leute fragen, ob diese Antwort Lücken enthält - ich habe noch keine gefunden). Gut gemacht, Nick.

Lindon Fox
quelle

Antworten:

94

In der Implementierung von layoutAttributesForElementsInRect von UICollectionViewFlowLayout ist ein Fehler aufgetreten, der dazu führt, dass in bestimmten Fällen mit Attributeinfügungen ZWEI Attributobjekte für eine einzelne Zelle zurückgegeben werden. Eines der zurückgegebenen Attributobjekte ist ungültig (außerhalb der Grenzen der Sammlungsansicht) und das andere ist gültig. Unten finden Sie eine Unterklasse von UICollectionViewFlowLayout, die das Problem behebt, indem Zellen außerhalb der Grenzen der Sammlungsansicht ausgeschlossen werden.

// NDCollectionViewFlowLayout.h
@interface NDCollectionViewFlowLayout : UICollectionViewFlowLayout
@end

// NDCollectionViewFlowLayout.m
#import "NDCollectionViewFlowLayout.h"
@implementation NDCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
  NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
  NSMutableArray *newAttributes = [NSMutableArray arrayWithCapacity:attributes.count];
  for (UICollectionViewLayoutAttributes *attribute in attributes) {
    if ((attribute.frame.origin.x + attribute.frame.size.width <= self.collectionViewContentSize.width) &&
        (attribute.frame.origin.y + attribute.frame.size.height <= self.collectionViewContentSize.height)) {
      [newAttributes addObject:attribute];
    }
  }
  return newAttributes;
}
@end

Sehen Sie das .

Andere Antworten schlagen vor, YES von shouldInvalidateLayoutForBoundsChange zurückzugeben. Dies führt jedoch zu unnötigen Neuberechnungen und löst das Problem nicht einmal vollständig.

Meine Lösung löst den Fehler vollständig und sollte keine Probleme verursachen, wenn Apple die Grundursache behebt.

Nick Snyder
quelle
5
Zu Ihrer Information, dieser Fehler tritt auch beim horizontalen Scrollen auf. Wenn Sie x durch y und width durch height ersetzen, funktioniert dieser Patch.
Patrick Tescher
Vielen Dank! Ich habe gerade angefangen, mit collectionView zu spielen (wenn Sie Apple darüber spammen möchten , hier ist die rdar-Referenz openradar.appspot.com/12433891 )
Vinzzz
1
@richarddas Nein, Sie möchten nicht überprüfen, ob sich die Rechtecke überschneiden. Tatsächlich schneiden alle Zellen (gültig oder ungültig) die Grenzen der Sammlungsansicht. Sie möchten überprüfen, ob ein Teil des Rect außerhalb der Grenzen liegt, was mein Code tut.
Nick Snyder
2
@Rpranata Ab iOS 7.1 wurde dieser Fehler nicht behoben. Seufzer.
schmelzend
2
Vielleicht verwende ich das falsch, aber unter iOS 8.3 in Swift führt dies dazu, dass Unteransichten auf der rechten Seite, die früher abgeschnitten wurden, überhaupt nicht angezeigt werden. Irgendjemand anderes?
Sudo
8

Fügen Sie dies in den viewController ein, dem die Sammlungsansicht gehört

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];
    [self.collectionView.collectionViewLayout invalidateLayout];
}
Peter Lapisu
quelle
Wo legst du das hin?
Fatuhoku
In den viewController, dem die Sammlungsansicht gehört
Peter Lapisu
3
Mein Problem war, dass die Zellen vollständig verschwanden. Diese Lösung hat geholfen - dies führt jedoch zu unnötigen Nachladevorgängen. Immer noch funktioniert es jetzt .. Danke!
Pawi
1
es verursacht eine Endlosschleife, wenn ich vom viewController aus anrufe
Hofi
Wie von DHennessy13 festgestellt , ist diese aktuelle Lösung gut, kann jedoch unvollständig sein, da sie das Layout beim Drehen des Bildschirms ungültig macht (und in den meisten Fällen nicht). Eine Verbesserung könnte darin bestehen, ein Flag zu setzen, um invalidateLayoutnur einmal.
Cœur
7

Ich habe ähnliche Probleme in meiner iPhone-Anwendung entdeckt. Die Suche im Apple Dev Forum brachte mir diese geeignete Lösung, die in meinem Fall funktioniert hat und wahrscheinlich auch in Ihrem Fall:

Unterklasse UICollectionViewFlowLayoutund Überschreibung, shouldInvalidateLayoutForBoundsChangeum zurückzukehren YES.

//.h
@interface MainLayout : UICollectionViewFlowLayout
@end

und

//.m
#import "MainLayout.h"
@implementation MainLayout
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    return YES;
}
@end
xxtesaxx
quelle
Dies löst das Problem, das ich mit diesem Problem habe, nur teilweise. Die Zelle wird tatsächlich an die richtige Stelle verschoben, wenn die Zeile angezeigt wird. Kurz bevor es erscheint, erscheint jedoch immer noch eine Zelle an der Seite.
Daniel Wood
Vorsicht - Wenn Sie dies tun, wird das Layout bei jedem Bildlauf ausgeführt. Dies kann die Leistung erheblich beeinträchtigen.
Fatuhoku
7

Eine schnelle Version von Nick Snyders Antwort:

class NDCollectionViewFlowLayout : UICollectionViewFlowLayout {
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)
        let contentSize = collectionViewContentSize
        return attributes?.filter { $0.frame.maxX <= contentSize.width && $0.frame.maxY < contentSize.height }
    }
}
Patrick Pijnappel
quelle
1
Dadurch ist die CollectionViewCell vollständig verschwunden. Gibt es eine andere mögliche Lösung?
Giggs
5

Ich hatte dieses Problem auch bei einem grundlegenden Rasteransichtslayout mit Einfügungen für Ränder. Das begrenzte Debugging, das ich jetzt durchgeführt habe, ist die Implementierung - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rectin meiner UICollectionViewFlowLayout-Unterklasse und die Protokollierung der Rückgabe der Superklassenimplementierung, was das Problem deutlich zeigt.

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *attrsList = [super layoutAttributesForElementsInRect:rect];

    for (UICollectionViewLayoutAttributes *attrs in attrsList) {
        NSLog(@"%f %f", attrs.frame.origin.x, attrs.frame.origin.y);
    }

    return attrsList;
}

Durch die Implementierung - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPathkann ich auch feststellen, dass die falschen Werte für itemIndexPath.item == 30 zurückgegeben werden. Dies ist der Faktor 10 der Anzahl der Zellen pro Zeile in meiner Rasteransicht. Ich bin mir nicht sicher, ob dies relevant ist.

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
    UICollectionViewLayoutAttributes *attrs = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];

    NSLog(@"initialAttrs: %f %f atIndexPath: %d", attrs.frame.origin.x, attrs.frame.origin.y, itemIndexPath.item);

    return attrs;
}

Da ich nicht genug Zeit für mehr Debugging habe, hat die Problemumgehung, die ich jetzt durchgeführt habe, die Breite meiner Sammlungsansichten um einen Betrag reduziert, der dem linken und rechten Rand entspricht. Ich habe einen Header, der immer noch die volle Breite benötigt, also habe ich clipsToBounds = NO in meiner Sammlungsansicht gesetzt und dann auch die linken und rechten Einfügungen entfernt, scheint zu funktionieren. Damit die Kopfzeilenansicht dann an Ort und Stelle bleibt, müssen Sie die Rahmenverschiebung und -größe in den Layoutmethoden implementieren, die mit der Rückgabe von layoutAttributes für die Kopfzeilenansicht beauftragt sind.

Monowerker
quelle
Vielen Dank für die zusätzlichen Informationen @monowerker. Ich denke, mein Problem begann, als ich die Einfügungen hinzufügte (ich habe dies der Frage hinzugefügt). Ich werde Ihre Debugging-Methoden ausprobieren und sehen, ob sie mir etwas sagen. Ich könnte auch deine Arbeit ausprobieren.
Lindon Fox
Dies ist höchstwahrscheinlich ein Fehler in UICFL / UICL. Ich werde versuchen, ein Radar abzulegen, wenn ich Zeit habe. Hier ist eine Diskussion mit einigen Rdar-Nummern, auf die Sie verweisen können. twitter.com/steipete/status/258323913279410177
Monowerker
4

Ich habe Apple einen Fehlerbericht hinzugefügt. Was für mich funktioniert, ist, den unteren AbschnittInset auf einen Wert zu setzen, der kleiner als der obere Einschub ist.

DrMickeyLauer
quelle
3

Ich hatte das gleiche Problem mit dem Entladen von Zellen auf dem iPhone mit einem UICollectionViewFlowLayoutund war daher froh, Ihren Beitrag zu finden. Ich weiß, dass Sie das Problem auf einem iPad haben, aber ich poste dies, weil ich denke, dass es ein allgemeines Problem mit dem ist UICollectionView. Also hier ist, was ich herausgefunden habe.

Ich kann bestätigen, dass das sectionInsetfür dieses Problem relevant ist. Daneben hat das headerReferenceSizeauch Einfluss darauf, ob eine Zelle verdrängt wird oder nicht. (Dies ist sinnvoll, da es zur Berechnung des Ursprungs benötigt wird.)

Leider müssen auch unterschiedliche Bildschirmgrößen berücksichtigt werden. Beim Herumspielen mit den Werten für diese beiden Eigenschaften stellte ich fest, dass eine bestimmte Konfiguration entweder auf beiden (3,5 "und 4"), auf keiner oder nur auf einer der Bildschirmgrößen funktioniert. Normalerweise keiner von ihnen. (Dies ist auch sinnvoll, da die Grenzen derUICollectionView ich aufgrund Veränderungen keine Unterschiede zwischen Netzhaut und Nicht-Netzhaut festgestellt habe.)

Am Ende habe ich das sectionInsetund headerReferenceSizeabhängig von der Bildschirmgröße eingestellt. Ich habe ungefähr 50 Kombinationen ausprobiert, bis ich Werte gefunden habe, bei denen das Problem nicht mehr auftrat und das Layout visuell akzeptabel war. Es ist sehr schwierig, Werte zu finden, die für beide Bildschirmgrößen geeignet sind.

Zusammenfassend kann ich Ihnen nur empfehlen, mit den Werten herumzuspielen, diese auf verschiedenen Bildschirmgrößen zu überprüfen und zu hoffen, dass Apple dieses Problem behebt.

Masa
quelle
3

Ich habe gerade ein ähnliches Problem mit Zellen festgestellt, die nach dem Scrollen von UICollectionView unter iOS 10 verschwunden sind (unter iOS 6-9 gab es keine Probleme).

Unterklasse von UICollectionViewFlowLayout und überschreibende Methode layoutAttributesForElementsInRect: funktioniert in meinem Fall nicht.

Die Lösung war einfach genug. Derzeit verwende ich eine Instanz von UICollectionViewFlowLayout und setze sowohl itemSize als auch EstimatedItemSize (ich habe zuvor nicht verwendet) und setze sie auf eine Größe ungleich Null. Die tatsächliche Größe wird in der Methode collectionView: layout: sizeForItemAtIndexPath: berechnet.

Außerdem habe ich einen Aufruf der invalidateLayout-Methode aus layoutSubviews entfernt, um unnötiges Neuladen zu vermeiden.

Andrey Seredkin
quelle
Wo wurden die Artikelgröße und die geschätzte Artikelgröße im uicollectionviewflowlayout festgelegt?
Garrett Cox
UICollectionViewFlowLayout * flowLayout = [[UICollectionViewFlowLayout alloc] init]; [flowLayout setItemSize: CGSizeMake (200, 200)]; [flowLayout setEstimatedItemSize: CGSizeMake (200, 200)]; self.collectionView = [[UICollectionView-Zuweisung] initWithFrame: CGRectZero collectionViewLayout: flowLayout];
Andrey Seredkin
Das Einstellen von
EstimatedItemSize
2

Ich habe gerade ein ähnliches Problem festgestellt, aber eine ganz andere Lösung gefunden.

Ich verwende eine benutzerdefinierte Implementierung von UICollectionViewFlowLayout mit einem horizontalen Bildlauf. Ich erstelle auch benutzerdefinierte Rahmenpositionen für jede Zelle.

Das Problem, das ich hatte, war, dass [super layoutAttributesForElementsInRect: rect] nicht alle UICollectionViewLayoutAttributes zurückgab, die auf dem Bildschirm angezeigt werden sollten. Bei Aufrufen von [self.collectionView reloadData] würden einige der Zellen plötzlich ausgeblendet.

Am Ende habe ich ein NSMutableDictionary erstellt, das alle bisher gesehenen UICollectionViewLayoutAttributes zwischengespeichert hat und dann alle Elemente enthält, von denen ich weiß, dass sie angezeigt werden sollen.

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {

    NSArray * originAttrs = [super layoutAttributesForElementsInRect:rect];
    NSMutableArray * attrs = [NSMutableArray array];
    CGSize calculatedSize = [self calculatedItemSize];

    [originAttrs enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * attr, NSUInteger idx, BOOL *stop) {
        NSIndexPath * idxPath = attr.indexPath;
        CGRect itemFrame = [self frameForItemAtIndexPath:idxPath];
        if (CGRectIntersectsRect(itemFrame, rect))
        {
            attr = [self layoutAttributesForItemAtIndexPath:idxPath];
            [self.savedAttributesDict addAttribute:attr];
        }
    }];

    // We have to do this because there is a bug in the collection view where it won't correctly return all of the on screen cells.
    [self.savedAttributesDict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSArray * cachedAttributes, BOOL *stop) {

        CGFloat columnX = [key floatValue];
        CGFloat leftExtreme = columnX; // This is the left edge of the element (I'm using horizontal scrolling)
        CGFloat rightExtreme = columnX + calculatedSize.width; // This is the right edge of the element (I'm using horizontal scrolling)

        if (leftExtreme <= (rect.origin.x + rect.size.width) || rightExtreme >= rect.origin.x) {
            for (UICollectionViewLayoutAttributes * attr in cachedAttributes) {
                [attrs addObject:attr];
            }
        }
    }];

    return attrs;
}

Hier ist die Kategorie für NSMutableDictionary, in der die UICollectionViewLayoutAttributes korrekt gespeichert werden.

#import "NSMutableDictionary+CDBCollectionViewAttributesCache.h"

@implementation NSMutableDictionary (CDBCollectionViewAttributesCache)

- (void)addAttribute:(UICollectionViewLayoutAttributes*)attribute {

    NSString *key = [self keyForAttribute:attribute];

    if (key) {

        if (![self objectForKey:key]) {
            NSMutableArray *array = [NSMutableArray new];
            [array addObject:attribute];
            [self setObject:array forKey:key];
        } else {
            __block BOOL alreadyExists = NO;
            NSMutableArray *array = [self objectForKey:key];

            [array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *existingAttr, NSUInteger idx, BOOL *stop) {
                if ([existingAttr.indexPath compare:attribute.indexPath] == NSOrderedSame) {
                    alreadyExists = YES;
                    *stop = YES;
                }
            }];

            if (!alreadyExists) {
                [array addObject:attribute];
            }
        }
    } else {
        DDLogError(@"%@", [CDKError errorWithMessage:[NSString stringWithFormat:@"Invalid UICollectionVeiwLayoutAttributes passed to category extension"] code:CDKErrorInvalidParams]);
    }
}

- (NSArray*)attributesForColumn:(NSUInteger)column {
    return [self objectForKey:[NSString stringWithFormat:@"%ld", column]];
}

- (void)removeAttributesForColumn:(NSUInteger)column {
    [self removeObjectForKey:[NSString stringWithFormat:@"%ld", column]];
}

- (NSString*)keyForAttribute:(UICollectionViewLayoutAttributes*)attribute {
    if (attribute) {
        NSInteger column = (NSInteger)attribute.frame.origin.x;
        return [NSString stringWithFormat:@"%ld", column];
    }

    return nil;
}

@end
Endama
quelle
Ich verwende auch horizontales Scrollen. Es gelingt mir, das Problem mithilfe Ihrer Lösung zu beheben. Nachdem ich jedoch zu einer anderen Ansicht übergegangen bin und zurückgekehrt bin, schien die Inhaltsgröße falsch zu sein, wenn zusätzliche Elemente vorhanden sind, die nicht gleichmäßig in Spalten unterteilt sind.
Morph85
Ich habe eine Lösung gefunden, um das Problem zu beheben, bei dem Zellen nach dem Ausführen des Segues und zurück zur Sammlungsansicht ausgeblendet werden. Versuchen Sie, EstimatedItemSize nicht in collectionViewFlowLayout festzulegen. setSize direkt einstellen.
Morph85
2

Die obigen Antworten funktionieren bei mir nicht, aber nach dem Herunterladen der Bilder habe ich sie ersetzt

[self.yourCollectionView reloadData]

mit

[self.yourCollectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];

Um zu aktualisieren und alle Zellen korrekt anzuzeigen, können Sie es versuchen.

Yao Li
quelle
0

Dies kann etwas spät sein, aber stellen Sie sicher, dass Sie Ihre Attribute prepare()nach Möglichkeit festlegen.

Mein Problem war, dass die Zellen ausgelegt waren und dann aktualisiert wurden layoutAttributesForElements. Dies führte zu einem Flackereffekt, wenn neue Zellen in Sicht kamen.

Durch Verschieben der gesamten Attributlogik prepareund anschließendes Einstellen wurde UICollectionViewCell.apply()das Flimmern beseitigt und eine butterweiche Zelle mit der Anzeige cell erstellt

Michael
quelle