Das dynamische Festlegen des Layouts in UICollectionView führt zu unerklärlichen Änderungen von contentOffset

70

Laut Apples Dokumentation (und auf der WWDC 2012 angepriesen) ist es möglich, das Layout UICollectionViewdynamisch einzustellen und die Änderungen sogar zu animieren:

Normalerweise geben Sie beim Erstellen einer Sammlungsansicht ein Layoutobjekt an, aber Sie können das Layout einer Sammlungsansicht auch dynamisch ändern. Das Layoutobjekt wird in der Eigenschaft collectionViewLayout gespeichert. Durch das Festlegen dieser Eigenschaft wird das Layout sofort direkt aktualisiert, ohne die Änderungen zu animieren. Wenn Sie die Änderungen animieren möchten, müssen Sie stattdessen die Methode setCollectionViewLayout: animiert: aufrufen.

In der Praxis habe ich jedoch festgestellt, UICollectionViewdass dies unerklärliche und sogar ungültige Änderungen an den contentOffsetZellen bewirkt , die dazu führen , dass sich Zellen falsch bewegen, wodurch die Funktion praktisch unbrauchbar wird. Um das Problem zu veranschaulichen, habe ich den folgenden Beispielcode zusammengestellt, der an einen Standard-Controller für die Sammlungsansicht angehängt werden kann, der in ein Storyboard eingefügt wurde:

#import <UIKit/UIKit.h>

@interface MyCollectionViewController : UICollectionViewController
@end

@implementation MyCollectionViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"CELL"];
    self.collectionView.collectionViewLayout = [[UICollectionViewFlowLayout alloc] init];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 1;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"CELL" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor whiteColor];
    return cell;
}

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"contentOffset=(%f, %f)", self.collectionView.contentOffset.x, self.collectionView.contentOffset.y);
    [self.collectionView setCollectionViewLayout:[[UICollectionViewFlowLayout alloc] init] animated:YES];
    NSLog(@"contentOffset=(%f, %f)", self.collectionView.contentOffset.x, self.collectionView.contentOffset.y);
}

@end

Die Steuerung setzt einen Standard UICollectionViewFlowLayoutin viewDidLoadund zeigt eine einzelne Zelle auf dem Bildschirm. Wenn die Zellen ausgewählt sind, erstellt der Controller einen weiteren Standard UICollectionViewFlowLayoutund setzt ihn in der Sammlungsansicht mit dem animated:YESFlag. Das erwartete Verhalten ist, dass sich die Zelle nicht bewegt. Das tatsächliche Verhalten ist jedoch, dass die Zelle außerhalb des Bildschirms gescrollt wird. Zu diesem Zeitpunkt ist es nicht einmal möglich, die Zelle wieder auf dem Bildschirm zu scrollen.

Ein Blick auf das Konsolenprotokoll zeigt, dass sich das contentOffset unerklärlicherweise geändert hat (in meinem Projekt von (0, 0) auf (0, 205)). Ich habe eine Lösung für die Lösung für den nicht animierten Fall veröffentlicht ( i.e. animated:NO) veröffentlicht, aber da ich eine Animation benötige, bin ich sehr interessiert zu wissen, ob jemand eine Lösung oder Problemumgehung für den animierten Fall hat.

Als Randnotiz habe ich benutzerdefinierte Layouts getestet und das gleiche Verhalten erhalten.

Timothy Moose
quelle
ENDLICH GELÖST. @Isaacliu hat die richtige Antwort gegeben. Ich habe die moderne Swift-Version eingefügt.
Fattie

Antworten:

33

Ich habe mir seit Tagen die Haare ausgezogen und eine Lösung für meine Situation gefunden, die helfen könnte. In meinem Fall habe ich ein kollabierendes Fotolayout wie in der Foto-App auf dem iPad. Es werden Alben mit den Fotos übereinander angezeigt. Wenn Sie auf ein Album tippen, werden die Fotos erweitert. Ich habe also zwei separate UICollectionViewLayouts und wechsle zwischen ihnen um. [self.collectionView setCollectionViewLayout:myLayout animated:YES]Ich hatte Ihr genaues Problem mit den vor der Animation springenden Zellen und erkannte, dass es das war contentOffset. Ich habe alles mit dem versuchtcontentOffset aber es sprang immer noch während der Animation. Die oben genannte Lösung von Tyler hat funktioniert, aber die Animation war immer noch durcheinander.

Dann bemerkte ich, dass es nur passiert, wenn ein paar Alben auf dem Bildschirm waren, nicht genug, um den Bildschirm auszufüllen. Mein Layout überschreibt-(CGSize)collectionViewContentSize wie empfohlen . Wenn nur wenige Alben vorhanden sind, ist die Inhaltsgröße der Sammlungsansicht geringer als die Inhaltsgröße der Ansichten. Das verursacht den Sprung, wenn ich zwischen den Sammlungslayouts umschalte.

Also habe ich in meinen Layouts eine Eigenschaft namens minHeight festgelegt und sie auf die Höhe der übergeordneten Sammlungsansichten festgelegt. Dann überprüfe ich die Höhe, bevor ich zurückkomme. -(CGSize)collectionViewContentSizeIch stelle sicher, dass die Höhe> = die Mindesthöhe ist.

Keine echte Lösung, aber jetzt funktioniert es einwandfrei. Ich würde versuchen, das einzustellencontentSize Ansicht Ihrer Sammlung auf mindestens die Länge der enthaltenen Ansicht festzulegen.

Bearbeiten: Manicaesar hat eine einfache Problemumgehung hinzugefügt, wenn Sie von UICollectionViewFlowLayout erben:

-(CGSize)collectionViewContentSize { //Workaround
    CGSize superSize = [super collectionViewContentSize];
    CGRect frame = self.collectionView.frame;
    return CGSizeMake(fmaxf(superSize.width, CGRectGetWidth(frame)), fmaxf(superSize.height, CGRectGetHeight(frame)));
}
Tassinari
quelle
Ich habe mit einigen davon gespielt und bin zu dem Schluss gekommen, dass Ihre Problemumgehung nur dann zuverlässig ist, wenn die contentSize genau der Größe der collectionView entspricht. Ich denke, es funktioniert für nicht scrollende Sammlungsansichten.
Timothy Moose
Es ist fast richtig, das einzige, was Sie anscheinend benötigen, um sicherzustellen, dass die collectionViewContentSize-Methode eine Größe zurückgibt, die mindestens so groß ist wie die Sammlungsansicht. Mein Layout wird vertikal gescrollt, sodass ich einfach sichergestellt habe, dass ich eine Größe mit der richtigen Breite zurückgebe, gefolgt vom Maximum der berechneten Größe durch mein Layout und der Höhe der Sammlungsansicht.
RougeExciter
5
Wenn Sie von UICollectionViewFlowLayout erben, ist dies so einfach wie: - (CGSize) collectionViewContentSize {// Problemumgehung CGSize superSize = [super collectionViewContentSize]; CGRect frame = self.collectionView.frame; return CGSizeMake (fmaxf (superSize.width, CGRectGetWidth (Frame)), fmaxf (superSize.height, CGRectGetHeight (Frame)); }
Manicaesar
1
Manicaesar, das macht man den Trick. Es sollte zu einer Antwort hinzugefügt werden, damit es nicht in den Kommentaren verloren geht. Ich habe es in der Antwort hinzugefügt.
Joris Mans
5
Schauen Sie sich targetContentOffsetForProposedContentOffset: und targetContentOffsetForProposedContentOffset: withScrollingVelocity: in Ihrer Layout-Unterklasse an.
ıɾuǝʞ
38

UICollectionViewLayoutenthält die überschreibbare Methode targetContentOffsetForProposedContentOffset:, mit der Sie bei einer Änderung des Layouts den richtigen Inhaltsversatz bereitstellen können. Dadurch wird die Animation korrekt animiert. Dies ist in iOS 7.0 und höher verfügbar

cdemiris99
quelle
Ich wünschte, ich könnte dies noch ein paar Mal verbessern. Für eine bestimmte Untergruppe von Fällen, die dieses Problem betreffen (z. B. für mich, wo ich den aktuellen Stand beibehalten möchte contentOffset), ist dies die perfekte Lösung. Danke @ cdemiris99.
Sam-W
1
Dies behebt mein sofortiges Problem, aber ich frage mich warum? Ich habe das Gefühl, ich sollte das nicht tun müssen. Ich werde ein besseres Verständnis dafür bekommen, was der Deal hier ist, und werde einen Fehler schreiben.
bpapa
Sie sind ein Genie, das das Problem perfekt behebt!
Blane Townsend
1
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint) -> CGPoint { if let collectionView = self.collectionView { let currentContentOffset = collectionView.contentOffset if currentContentOffset.y < proposedContentOffset.y { return currentContentOffset } } return proposedContentOffset } Das Problem wurde behoben. Danke für einen Hinweis.
Massenmacher
7

Dieses Problem hat mich auch gebissen und es scheint ein Fehler im Übergangscode zu sein. Soweit ich das beurteilen kann, wird versucht, sich auf die Zelle zu konzentrieren, die der Mitte des Layouts der Ansicht vor dem Übergang am nächsten liegt. Wenn sich jedoch vor dem Übergang keine Zelle in der Mitte der Ansicht befindet, wird immer noch versucht, die Stelle zu zentrieren, an der sich die Zelle nach dem Übergang befinden würde. Dies ist sehr deutlich, wenn Sie alwaysBounceVertical / Horizontal auf YES setzen, die Ansicht mit einer einzelnen Zelle laden und dann einen Layoutübergang durchführen.

Ich konnte dies umgehen, indem ich der Sammlung explizit anwies, sich nach dem Auslösen der Layoutaktualisierung auf eine bestimmte Zelle (in diesem Beispiel die erste sichtbare Zelle der Zelle) zu konzentrieren.

[self.collectionView setCollectionViewLayout:[self generateNextLayout] animated:YES];

// scroll to the first visible cell
if ( 0 < self.collectionView.indexPathsForVisibleItems.count ) {
    NSIndexPath *firstVisibleIdx = [[self.collectionView indexPathsForVisibleItems] objectAtIndex:0];
    [self.collectionView scrollToItemAtIndexPath:firstVisibleIdx atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
}
Tyler
quelle
Ich habe diese Problemumgehung in meinem Beispielcode ausprobiert und sie war sehr eng. Die Zelle prallte nur leicht ab. In anderen Szenarien funktionierte es jedoch nicht so gut. Ändern Sie beispielsweise, numberOfItemsInSection:um 100 zurückzugeben. Wenn Sie auf die erste Zelle tippen, wird die Ansicht um mehrere Zeilen nach unten verschoben. Wenn Sie dann auf die erste sichtbare Zelle tippen, wird die Ansicht nach oben verschoben. Wenn Sie dann erneut auf die erste Zelle tippen, bleibt diese stationär (korrektes Verhalten). Wenn Sie auf eine Zelle in der unteren Reihe tippen, wird sie abprallt. Scrollen Sie in der Ansicht um mehrere Zeilen nach unten, tippen Sie auf eine beliebige Zeile, und die Ansicht wird nach oben verschoben.
Timothy Moose
1
Ich fand mit animierten: NEIN in der scrollToItemAtIndexPath: Aufruf ließ Dinge besser aussehen.
Honus
2
Sieht so aus, als hätten sie APIs zur Steuerung dieses Verhaltens in iOS7 hinzugefügt.
Tyler
@tyler, auf welche neuen iOS7-APIs beziehen Sie sich?
Brainjam
12
Überprüfen Sie die UICollectionViewLayout-Methode - (CGPoint) targetContentOffsetForProposedContentOffset: (CGPoint) vorgeschlagenenContentOffset. Apples Vorschlag für mich war, den Zielindex für die Zelle, auf die ich mich konzentrieren wollte, in das Layout einzufügen (eine NSIndexPath-Requisite in meiner benutzerdefinierten Layouterweiterung verfügbar zu machen) und diese Informationen dann zu verwenden, um das entsprechende targetContentOffset zurückzugeben. Diese Lösung hat bei mir funktioniert.
Tyler
7

Ich springe mit einer späten Antwort auf meine eigene Frage ein.

Die TLLayoutTransitioning- Bibliothek bietet eine hervorragende Lösung für dieses Problem, indem die interaktiven Übergangs-APIs von iOS7 erneut beauftragt werden, nicht interaktive Übergänge von Layout zu Layout durchzuführen. Es bietet effektiv eine Alternative zum setCollectionViewLayoutLösen des Problems des Inhaltsversatzes und zum Hinzufügen mehrerer Funktionen:

  1. Animationsdauer
  2. Über 30 Lockerungskurven (mit freundlicher Genehmigung der AHEasing-Bibliothek von Warren Moore )
  3. Mehrere Inhaltsversatzmodi

Benutzerdefinierte Beschleunigungskurven können definiert werden als AHEasingFunction Funktionen werden. Der endgültige Inhaltsversatz kann in Form eines oder mehrerer Indexpfade mit den Platzierungsoptionen Minimal, Mitte, Oben, Links, Unten oder Rechts angegeben werden.

Versuchen Sie, die Resize-Demo auszuführen , um zu sehen, was ich meine im Arbeitsbereich "Beispiele" und mit den Optionen .

Die Verwendung ist wie folgt. Konfigurieren Sie zunächst Ihren View Controller so, dass eine Instanz von TLTransitionLayout:

- (UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout
{
    return [[TLTransitionLayout alloc] initWithCurrentLayout:fromLayout nextLayout:toLayout];
}

Anstatt aufzurufen setCollectionViewLayout, rufen Sie dann transitionToCollectionViewLayout:toLayoutden in der UICollectionView-TLLayoutTransitioningKategorie definierten Aufruf auf :

UICollectionViewLayout *toLayout = ...; // the layout to transition to
CGFloat duration = 2.0;
AHEasingFunction easing = QuarticEaseInOut;
TLTransitionLayout *layout = (TLTransitionLayout *)[collectionView transitionToCollectionViewLayout:toLayout duration:duration easing:easing completion:nil];

Dieser Aufruf initiiert einen interaktiven Übergang und intern einen CADisplayLinkRückruf, der den Übergangsfortschritt mit der angegebenen Dauer und Beschleunigungsfunktion steuert.

Der nächste Schritt besteht darin, einen endgültigen Inhaltsversatz anzugeben. Sie können einen beliebigen Wert angeben, aber die in toContentOffsetForLayoutdefinierte Methode UICollectionView-TLLayoutTransitioningbietet eine elegante Möglichkeit, Inhaltsversätze relativ zu einem oder mehreren Indexpfaden zu berechnen. Um beispielsweise eine bestimmte Zelle so nah wie möglich an der Mitte der Sammlungsansicht zu haben, rufen Sie unmittelbar danach Folgendes auf transitionToCollectionViewLayout:

NSIndexPath *indexPath = ...; // the index path of the cell to center
TLTransitionLayoutIndexPathPlacement placement = TLTransitionLayoutIndexPathPlacementCenter;
CGPoint toOffset = [collectionView toContentOffsetForLayout:layout indexPaths:@[indexPath] placement:placement];
layout.toContentOffset = toOffset;
Timothy Moose
quelle
Ich hatte einige wichtige Animationsprobleme mit zusätzlichen Ansichten und fand diese dann. Vielen Dank!
Morrowless
6

2019 tatsächliche Lösung

Angenommen, Sie haben eine Reihe von Layouts für Ihre Ansicht "Autos".

Nehmen wir an, Sie haben drei.

CarsLayout1: UICollectionViewLayout { ...
CarsLayout2: UICollectionViewLayout { ...
CarsLayout3: UICollectionViewLayout { ...

Es wird springen wenn Sie zwischen Layouts animieren.

Es ist nur ein unbestreitbarer Fehler von Apple. Es springt, wenn Sie animieren, ohne Frage.

Das Update ist das Folgende:

Sie müssen über einen globalen Float und die folgende Basisklasse verfügen:

var avoidAppleMessupCarsLayouts: CGPoint? = nil

class FixerForCarsLayouts: UICollectionViewLayout {
    
    override func prepareForTransition(from oldLayout: UICollectionViewLayout) {
        avoidAppleMessupCarsLayouts = collectionView?.contentOffset
    }
    
    override func targetContentOffset(
        forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
        if avoidAppleMessupCarsLayouts != nil {
            return avoidAppleMessupCarsLayouts!
        }
        return super.targetContentOffset(forProposedContentOffset: proposedContentOffset)
    }
}

Hier sind die drei Layouts für Ihren Bildschirm "Autos":

CarsLayout1: FixerForCarsLayouts { ...
CarsLayout2: FixerForCarsLayouts { ...
CarsLayout3: FixerForCarsLayouts { ...

Das ist es. Es funktioniert jetzt.


  1. Unglaublich dunkel, Sie könnten verschiedene "Sätze" von Layouts (für Autos, Hunde, Häuser usw.) haben, die (möglicherweise) kollidieren könnten. Aus diesem Grund haben Sie für jede "Menge" eine globale und eine Basisklasse wie oben.

  2. Dies wurde vor vielen Jahren von dem vorbeigehenden Benutzer @Isaacliu erfunden.

  3. Ein Detail, FWIW in Isaaclius Codefragment, finalizeLayoutTransitionwird hinzugefügt. Tatsächlich ist es logisch nicht notwendig.

Tatsache ist, dass Sie dies jedes Mal tun müssen, bis Apple die Funktionsweise ändert, wenn Sie zwischen den Layouts der Sammlungsansicht animieren. So ist das Leben!

Fattie
quelle
1
Ja, meine vorherige Antwort war nicht korrekt. Vielen Dank für den Hinweis! Es ist nie zu spät zu lernen (:
iWheelBuy
1
@iWheelBuy, es gab nur 2 Menschen in einem Jahrzehnt, die die richtige Technik herausgefunden haben :) Du warst einer von ihnen. Ihre Antwort enthielt einen kleinen Fehler. Ich habe ihn nur korrigiert und die Syntax aktualisiert. Vielen Dank für die Lösung !!!!!!!!!
Fattie
5

Einfach.

Animieren Sie Ihr neues Layout und das contentOffset von collectionView im selben Animationsblock.

[UIView animateWithDuration:0.3 animations:^{
                                 [self.collectionView setCollectionViewLayout:self.someLayout animated:YES completion:nil];
                                 [self.collectionView setContentOffset:CGPointMake(0, -64)];
                             } completion:nil];

Es wird self.collectionView.contentOffsetkonstant bleiben .

Nikolsky
quelle
Dies ist eine großartige Lösung für das Überschreiben von targetContentOffsetForProposedContentOffset: ist nicht ideal. Ich wechsle von einem benutzerdefinierten Layout zu einem Flow-Layout und möchte das Flow-Layout dafür nicht unterordnen. Vielen Dank!
Richard Venable
Was ist das self.someLayout?
Ramesh Bhuja
Ihre Instanz von UICollectionViewLayout mit Ihrem benutzerdefinierten Layout, zu dem Sie wechseln möchten. Werfen Sie einen Blick auf APIs von UICollectionView
Nikolsky
3

Wenn Sie lediglich nach einem Inhaltsversatz suchen, der sich beim Übergang von Layouts nicht ändert, können Sie ein benutzerdefiniertes Layout erstellen und einige Methoden überschreiben, um den Überblick über das alte contentOffset zu behalten und es wiederzuverwenden:

@interface CustomLayout ()

@property (nonatomic) NSValue *previousContentOffset;

@end

@implementation CustomLayout

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
    CGPoint previousContentOffset = [self.previousContentOffset CGPointValue];
    CGPoint superContentOffset = [super targetContentOffsetForProposedContentOffset:proposedContentOffset];
    return self.previousContentOffset != nil ? previousContentOffset : superContentOffset ;
}

- (void)prepareForTransitionFromLayout:(UICollectionViewLayout *)oldLayout
{
    self.previousContentOffset = [NSValue valueWithCGPoint:self.collectionView.contentOffset];
    return [super prepareForTransitionFromLayout:oldLayout];
}

- (void)finalizeLayoutTransition
{
    self.previousContentOffset = nil;
    return [super finalizeLayoutTransition];
}
@end

Dazu wird lediglich der vorherige Inhaltsversatz vor dem Layoutübergang gespeichert prepareForTransitionFromLayout, der neue Inhaltsversatz überschrieben targetContentOffsetForProposedContentOffsetund gelöscht finalizeLayoutTransition. Ziemlich einfach

Isaac Liu
quelle
Mein Gott, das gibt es auch targetContentOffset#forProposedContentOffset#withScrollingVelocity !
Fattie
2

Wenn es hilft, die Erfahrung zu erweitern: Ich bin auf dieses Problem immer wieder gestoßen, unabhängig von der Größe meines Inhalts, unabhängig davon, ob ich einen Inhaltseinsatz oder einen anderen offensichtlichen Faktor festgelegt habe. Meine Problemumgehung war also etwas drastisch. Zuerst habe ich UICollectionView unterklassifiziert und hinzugefügt, um unangemessene Einstellungen für den Inhaltsversatz zu bekämpfen:

- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
    if(_declineContentOffset) return;
    [super setContentOffset:contentOffset];
}

- (void)setContentOffset:(CGPoint)contentOffset
{
    if(_declineContentOffset) return;
    [super setContentOffset:contentOffset];
}

- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout animated:(BOOL)animated
{
    _declineContentOffset ++;
    [super setCollectionViewLayout:layout animated:animated];
    _declineContentOffset --;
}

- (void)setContentSize:(CGSize)contentSize
{
    _declineContentOffset ++;
    [super setContentSize:contentSize];
    _declineContentOffset --;
}

Ich bin nicht stolz darauf, aber die einzige praktikable Lösung scheint darin zu bestehen, jeden Versuch der Sammlungsansicht, einen eigenen Inhaltsoffset festzulegen, der sich aus einem Aufruf von ergibt, vollständig abzulehnen setCollectionViewLayout:animated: . Empirisch sieht es so aus, als ob diese Änderung direkt im unmittelbaren Aufruf erfolgt, was offensichtlich nicht durch die Benutzeroberfläche oder die Dokumentation garantiert wird, aber aus Sicht der Kernanimation sinnvoll ist, sodass ich mit der Annahme vielleicht nur zu 50% unzufrieden bin.

Es gab jedoch ein zweites Problem: UICollectionView fügte jetzt einen kleinen Sprung zu den Ansichten hinzu, die sich in einem neuen Layout der Sammlungsansicht an derselben Stelle befanden - indem sie um etwa 240 Punkte nach unten gedrückt und dann wieder an die ursprüngliche Position animiert wurden. Ich bin mir nicht CAAnimationsicher , warum, aber ich habe meinen Code geändert, um damit umzugehen, indem ich das s abgetrennt habe, das zu Zellen hinzugefügt wurde, die sich tatsächlich nicht bewegten:

- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout animated:(BOOL)animated
{
    // collect up the positions of all existing subviews
    NSMutableDictionary *positionsByViews = [NSMutableDictionary dictionary];
    for(UIView *view in [self subviews])
    {
        positionsByViews[[NSValue valueWithNonretainedObject:view]] = [NSValue valueWithCGPoint:[[view layer] position]];
    }

    // apply the new layout, declining to allow the content offset to change
    _declineContentOffset ++;
    [super setCollectionViewLayout:layout animated:animated];
    _declineContentOffset --;

    // run through the subviews again...
    for(UIView *view in [self subviews])
    {
        // if UIKit has inexplicably applied animations to these views to move them back to where
        // they were in the first place, remove those animations
        CABasicAnimation *positionAnimation = (CABasicAnimation *)[[view layer] animationForKey:@"position"];
        NSValue *sourceValue = positionsByViews[[NSValue valueWithNonretainedObject:view]];
        if([positionAnimation isKindOfClass:[CABasicAnimation class]] && sourceValue)
        {
            NSValue *targetValue = [NSValue valueWithCGPoint:[[view layer] position]];

            if([targetValue isEqualToValue:sourceValue])
                [[view layer] removeAnimationForKey:@"position"];
        }
    }
}

Dies scheint Ansichten, die sich tatsächlich bewegen, nicht zu behindern oder dazu zu führen, dass sie sich falsch bewegen (als ob sie erwarten würden, dass alles um sie herum um etwa 240 Punkte nach unten zeigt und mit ihnen an die richtige Position animiert wird).

Das ist also meine aktuelle Lösung.

Tommy
quelle
Tommy, danke fürs Teilen. Ich habe gerade eine Lösung zu meiner eigenen Frage hinzugefügt, die Sie vielleicht nützlich finden.
Timothy Moose
0

Ich habe jetzt wahrscheinlich zwei Wochen damit verbracht, verschiedene Layouts so zu gestalten, dass sie reibungslos miteinander übergehen. Ich habe festgestellt, dass das Überschreiben des vorgeschlagenen Offsets in iOS 10.2 funktioniert, aber in der vorherigen Version tritt immer noch das Problem auf. Was meine Situation ein bisschen verschlimmert, ist, dass ich aufgrund eines Bildlaufs in ein anderes Layout wechseln muss, sodass die Ansicht gleichzeitig gescrollt und gewechselt wird.

Tommys Antwort war das einzige, was in Versionen vor 10.2 für mich funktioniert hat. Ich mache jetzt folgendes.

class HackedCollectionView: UICollectionView {

    var ignoreContentOffsetChanges = false

    override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) {
        guard ignoreContentOffsetChanges == false else { return }
        super.setContentOffset(contentOffset, animated: animated)
    }

    override var contentOffset: CGPoint {
        get {
            return super.contentOffset
        }
        set {
            guard ignoreContentOffsetChanges == false else { return }
            super.contentOffset = newValue
        }
    }

    override func setCollectionViewLayout(_ layout: UICollectionViewLayout, animated: Bool) {
        guard ignoreContentOffsetChanges == false else { return }
        super.setCollectionViewLayout(layout, animated: animated)
    }

    override var contentSize: CGSize {
        get {
            return super.contentSize
        }
        set {
            guard ignoreContentOffsetChanges == false else { return }
            super.contentSize = newValue
        }
    }

}

Wenn ich dann das Layout einstelle, mache ich das ...

let theContentOffsetIActuallyWant = CGPoint(x: 0, y: 100)
UIView.animate(withDuration: animationDuration,
               delay: 0, options: animationOptions,
               animations: {
                collectionView.setCollectionViewLayout(layout, animated: true, completion: { completed in
                    // I'm also doing something in my layout, but this may be redundant now
                    layout.overriddenContentOffset = nil
                })
                collectionView.ignoreContentOffsetChanges = true
}, completion: { _ in
    collectionView.ignoreContentOffsetChanges = false
    collectionView.setContentOffset(theContentOffsetIActuallyWant, animated: false)
})
Mark Bridges
quelle
Alarm gibt es tatsächlichtargetContentOffset#forProposedContentOffset#withScrollingVelocity
Fattie
-2

Das hat endlich bei mir funktioniert (Swift 3)

self.collectionView.collectionViewLayout = UICollectionViewFlowLayout()
self.collectionView.setContentOffset(CGPoint(x: 0, y: -118), animated: true)
Mark Hennings
quelle
Welche Bedeutung hat die y: -118magische Zahl? Diese Lösung scheint für eine bestimmte Situation zu sein und ist daher nicht allzu hilfreich.
Jordan Bondo