2016-05-30 9 views
8

Ich unterteile UICollectionViewFlowLayout, um in zwei Richtungen in einem UICollectionView zu scrollen. Das Scrollen funktioniert gut für eine kleinere Anzahl von Zeilen- und Abschnittszählungen (100-200 Zeilen und Abschnitte), aber es gibt eine sichtbare Verzögerung beim Scrollen, wenn ich die Anzahl der Zeilen und Sektionen über 500 d. H. 250.000 oder mehr Zellen in UICollectionView erhöhe. Ich habe die Quelle der Verzögerung für in Schleife in der layoutAttributesForElementsInRect verfolgt. Ich bin mit einem DictionaryUICollectionViewLayoutAttributes jeder Zelle zu halten, es zu vermeiden, neu zu berechnen und Looping durch Attribute von Zellen zurückzukehren, von layoutAttributesForElementsInRectSichtbare Verzögerung beim Scrollen durch zweiseitiges Scrollen UICollectionView mit großer Anzahl von Zellen (250.000 oder mehr)

import UIKit 

class LuckGameCollectionViewLayout: UICollectionViewFlowLayout { 

    // Used for calculating each cells CGRect on screen. 
    // CGRect will define the Origin and Size of the cell. 
    let CELL_HEIGHT = 70.0 
    let CELL_WIDTH = 70.0 

    // Dictionary to hold the UICollectionViewLayoutAttributes for 
    // each cell. The layout attribtues will define the cell's size 
    // and position (x, y, and z index). I have found this process 
    // to be one of the heavier parts of the layout. I recommend 
    // holding onto this data after it has been calculated in either 
    // a dictionary or data store of some kind for a smooth performance. 
    var cellAttrsDictionary = Dictionary<NSIndexPath, UICollectionViewLayoutAttributes>() 
    // Defines the size of the area the user can move around in 
    // within the collection view. 
    var contentSize = CGSize.zero 

    override func collectionViewContentSize() -> CGSize { 
     return self.contentSize 
    } 

    override func prepareLayout() { 

     // Cycle through each section of the data source. 
     if collectionView?.numberOfSections() > 0 { 
      for section in 0...collectionView!.numberOfSections()-1 { 

       // Cycle through each item in the section. 
       if collectionView?.numberOfItemsInSection(section) > 0 { 
        for item in 0...collectionView!.numberOfItemsInSection(section)-1 { 

         // Build the UICollectionVieLayoutAttributes for the cell. 
         let cellIndex = NSIndexPath(forItem: item, inSection: section) 
         let xPos = Double(item) * CELL_WIDTH 
         let yPos = Double(section) * CELL_HEIGHT 

         let cellAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: cellIndex) 
         cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT) 

         // Save the attributes. 
         cellAttrsDictionary[cellIndex] = cellAttributes 
        } 
       } 

      } 
     } 

     // Update content size. 
     let contentWidth = Double(collectionView!.numberOfItemsInSection(0)) * CELL_WIDTH 
     let contentHeight = Double(collectionView!.numberOfSections()) * CELL_HEIGHT 
     self.contentSize = CGSize(width: contentWidth, height: contentHeight) 

    } 

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 
     // Create an array to hold all elements found in our current view. 
     var attributesInRect = [UICollectionViewLayoutAttributes]() 

     // Check each element to see if it should be returned. 
     for (_,cellAttributes) in cellAttrsDictionary { 
      if CGRectIntersectsRect(rect, cellAttributes.frame) { 
       attributesInRect.append(cellAttributes) 
      } 
     } 

     // Return list of elements. 
     return attributesInRect 
    } 

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? { 
     return cellAttrsDictionary[indexPath]! 
    } 

    override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { 
     return false 
    } 
} 

Edit: Im Anschluss an die Veränderungen, die ich gekommen bin, oben mit in dem layoutAttributesForElementsInRect Methode.

override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {   
    // Create an array to hold all elements found in our current view. 
    var attributesInRect = [UICollectionViewLayoutAttributes]() 

    let xOffSet = self.collectionView?.contentOffset.x 
    let yOffSet = self.collectionView?.contentOffset.y 
    let totalColumnCount = self.collectionView?.numberOfSections() 
    let totalRowCount = self.collectionView?.numberOfItemsInSection(0) 

    let startRow = Int(Double(xOffSet!)/CELL_WIDTH) - 10 //include 10 rows towards left 
    let endRow = Int(Double(xOffSet!)/CELL_WIDTH + Double(Utils.getScreenWidth())/CELL_WIDTH) + 10 //include 10 rows towards right 
    let startCol = Int(Double(yOffSet!)/CELL_HEIGHT) - 10 //include 10 rows towards top 
    let endCol = Int(Double(yOffSet!)/CELL_HEIGHT + Double(Utils.getScreenHeight())/CELL_HEIGHT) + 10 //include 10 rows towards bottom 

    for(var i = startRow ; i <= endRow; i = i + 1){ 
     for (var j = startCol ; j <= endCol; j = j + 1){ 
      if (i < 0 || i > (totalRowCount! - 1) || j < 0 || j > (totalColumnCount! - 1)){ 
       continue 
      } 

      let indexPath: NSIndexPath = NSIndexPath(forRow: i, inSection: j) 
      attributesInRect.append(cellAttrsDictionary[indexPath]!) 
     } 
    } 

    // Return list of elements. 
    return attributesInRect 
} 

Ich habe den Offset des Collection und die verwendete berechnet, um die Zellen zu berechnen, die auf dem Bildschirm sichtbar sein werden (Höhe/Breite jeder Zelle verwendet wird). Ich musste auf jeder Seite zusätzliche Zellen hinzufügen, damit beim Scrollen keine Zellen fehlen. Ich habe das getestet und die Leistung ist in Ordnung.

+1

Offensichtlich ist das Durchlaufen des Wörterbuchs der teure Teil. Ich frage mich, ob es eine Möglichkeit gibt, Ihr Wörterbuch zu verschlüsseln, damit Sie alles bekommen können, was sich mit einem Rect kreuzt, ohne 250k-Werte durchlaufen zu müssen. –

+0

@KevinDiTraglia Ich dachte auch darüber nach, aber das Problem ist, dass 'layoutAttributesForElementsInRect' nicht unbedingt nur die sichtbaren Elemente zurückgibt. Aus dem Dokument (https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/CreatingCustomLayouts/CreatingCustomLayouts.html) "Basierend auf der aktuellen Bildlaufposition ruft die Auflistungsansicht dann Ihr LayoutAttributesForElementsInRect auf: Methode, um nach den Attributen der Zellen und Ansichten in einem bestimmten Rechteck zu fragen, das mit dem sichtbaren Rechteck identisch sein kann oder nicht. " –

Antwort

3

von Vorteil der layoutAttributesForElementsInRect(rect: CGRect) mit bekannten Zellgröße nehmen Sie benötigen, um Ihre Attribute nicht zwischenspeichern und konnte sie nur für einen bestimmten rect berechnen, wie die Collection sie anfordert. Sie müssen immer noch die Grenzfälle von 0 und die maximale Anzahl von Abschnitten/Zeilen prüfen, um zu vermeiden, dass unnötige oder ungültige Attribute berechnet werden. Dies kann jedoch problemlos in where Klauseln um die Schleifen herum geschehen. Hier ist ein funktionierendes Beispiel, das ich mit 1000 Sektionen x 1000 Zeilen getestet habe und es funktioniert ohne Verzögerung auf dem Gerät:

Edit: Ich habe die biggerRect hinzugefügt, so dass Attribute für vor dem Scrollen berechnet werden können kommt dort hin. Von Ihrer Bearbeitung an sieht es so aus, als würden Sie immer noch die Attribute zwischenspeichern, die meiner Meinung nach nicht für die Performance benötigt werden. Außerdem wird es zu einem viel größeren Speicherbedarf mit mehr Scrollen führen. Gibt es auch einen Grund, warum Sie nicht den mitgelieferten CGRect aus dem Callback verwenden möchten, anstatt manuell einen aus dem ContentOffset zu berechnen?

class LuckGameCollectionViewLayout: UICollectionViewFlowLayout { 

let CELL_HEIGHT = 50.0 
let CELL_WIDTH = 50.0 

override func collectionViewContentSize() -> CGSize { 
    let contentWidth = Double(collectionView!.numberOfItemsInSection(0)) * CELL_WIDTH 
    let contentHeight = Double(collectionView!.numberOfSections()) * CELL_HEIGHT 
    return CGSize(width: contentWidth, height: contentHeight) 
} 

override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 
    let biggerRect = rect.insetBy(dx: -2048, dy: -2048) 
    let startIndexY = Int(Double(biggerRect.origin.y)/CELL_HEIGHT) 
    let startIndexX = Int(Double(biggerRect.origin.x)/CELL_WIDTH) 
    let numberOfVisibleCellsInRectY = Int(Double(biggerRect.height)/CELL_HEIGHT) + startIndexY 
    let numberOfVisibleCellsInRectX = Int(Double(biggerRect.width)/CELL_WIDTH) + startIndexX 
    var attributes: [UICollectionViewLayoutAttributes] = [] 

    for section in startIndexY..<numberOfVisibleCellsInRectY 
     where section >= 0 && section < self.collectionView!.numberOfSections() { 
      for item in startIndexX..<numberOfVisibleCellsInRectX 
       where item >= 0 && item < self.collectionView!.numberOfItemsInSection(section) { 
        let cellIndex = NSIndexPath(forItem: item, inSection: section) 
        if let attrs = self.layoutAttributesForItemAtIndexPath(cellIndex) { 
         attributes.append(attrs) 
        } 
      } 
    } 
    return attributes 
} 

override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? { 
    let xPos = Double(indexPath.row) * CELL_WIDTH 
    let yPos = Double(indexPath.section) * CELL_HEIGHT 
    let cellAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath) 
    cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT) 
    return cellAttributes 
} 
} 
+0

Können Sie mein Update mit der von mir entwickelten Lösung und einer Rückmeldung darüber überprüfen? –

+0

Irgendwelche Vorschläge, um dies für verschiedene Zellbreiten zu arbeiten? Vielen Dank! – Unikorn

+0

@Unikorn Wenn Sie verschiedene Zellbreiten für jede Zelle haben wollen, ist es sicherlich schwieriger. Sie könnten die Attribute in Dictionary wie im Originalbeispiel aus der obigen Frage speichern, aber das würde den Speicherbedarf erhöhen oder den x-Offset für jeden Abschnitt (in diesem Fall Zeile) speichern, der erhöht wird, wenn der Benutzer nach rechts zieht oder verringert sich beim Ziehen nach links und wird basierend auf der Gesamtbreite der sichtbaren Zellen plus dem Zellenabstand berechnet. Ich bin mir sicher, dass es Probleme damit geben würde, die weiter erforscht werden müssten, aber das könnte ein Anfang sein. –

1

Sie müssen eine Datenstruktur für Ihre Zellen erstellen, die nach Dimension (en) geordnet ist, um einen Algorithmus zu finden, der diese Dimension (en) verwendet, um den Suchbereich einzugrenzen.

Nehmen wir den Fall einer Tabelle mit Zellen 100 Pixel hoch über die gesamte Breite der Ansicht und 250_000 Elemente, gefragt, ob die Zellen schneiden {0, oben, 320, unten}. Dann würde Ihre Datenstruktur ein Array von Top-Koordinate geordnet sein, und der dazu gehörige Algorithmus wäre wie

let start: Int = top/100 
let end: Int = bottom/100 + 1 
return (start...end).map { cellAttributes[$0] } 

so viel Komplexität als Ihre tatsächliche Layout erfordert.

1

cellAttrsDictionary ist ungeeignet, die UICollectionViewLayoutAttributes zu speichern. Es wird von NSIndexPath indiziert, aber in layoutAttributesForElementsInRect suchen Sie nach einem Bereich. Wenn Sie cellAttrsDictionary für etwas anderes benötigen, dann können Sie es lassen, aber speichern Sie jedes cellAttribute zusätzlich in einer anderen Datenstruktur, die schneller zu suchen ist.

Zum Beispiel eine verschachtelte Array:

var allCellAttributes = [[UICollectionViewLayoutAttributes]]() 

Die erste Ebene Array eine Fläche beschreibt, lassen Sie uns sagen, es ist in Blöcken von 1000 Pixel hoch und voller Bildschirmbreite. Also alle cellAttributes, die mit dem Rechteck schneiden {0, 0, Screen, 1000} gehen in:

allCellAttributes[0].append(cellAttributes) 

Alle cellAttributes, die mit dem Rechteck schneiden {0, 1000, Screen 2000} gehen in:

allCellAttributes[1].append(cellAttributes) 

Und so weiter ...

Dann in layoutAttributesForElementsInRect können Sie in dieser Datenstruktur suchen, indem Sie direkt in das Array springen in Abhängigkeit von der gegebenen CGRect. Wenn das Rect z.B.{0, 5500, 100, 6700} dann brauchen Sie nur in diesem Bereich suchen:

allCellAttributes[5] 
allCellAttributes[6] 

Dies sollten Sie die grundlegende Idee geben, ich hoffe, Sie verstehen, was ich meine.

Verwandte Themen