2016-05-30 10 views
8

Sono sottoclasse UICollectionViewFlowLayout per ottenere lo scorrimento a due direzioni in un UICollectionView. Lo scorrimento funziona bene per un numero minore di righe e sezioni (100-200 righe e sezioni), ma è visibile un ritardo durante lo scorrimento quando aumento il numero di righe e sezioni oltre 500 i.e 250.000 o più celle nello UICollectionView. Ho rintracciato l'origine del lag da inserire nel ciclo nello layoutAttributesForElementsInRect. Sto usando un Dictionary per tenere UICollectionViewLayoutAttributes di ogni cella per evitare di ricalcolare e loop attraverso di essa per restituire gli attributi di cellule da layoutAttributesForElementsInRectRitardo visibile durante lo scorrimento in due direzioni di scorrimento di UICollectionView con un numero elevato di celle (250.000 o più)

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: Di seguito sono riportati i cambiamenti che sono venuto su con la Metodo layoutAttributesForElementsInRect.

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 
} 

Ho calcolato l'offset del CollectionView e utilizzati per calcolare le cellule che saranno visibili sullo schermo (usando altezza/larghezza di ciascuna cella). Ho dovuto aggiungere celle aggiuntive su ciascun lato in modo che quando l'utente scorre non ci siano celle mancanti. Ho provato questo e le prestazioni vanno bene.

+1

Ovviamente il ciclo attraverso il dizionario è la parte costosa. Mi chiedo se è possibile digitare il dizionario in modo da poter ottenere tutto ciò che si interseca con un rect senza dover eseguire il ciclo di valori di 250k. –

+0

@KevinDiTraglia Ho pensato anche a questo, ma il problema è che 'layoutAttributesForElementsInRect' non restituisce necessariamente solo gli elementi visibili. Dal doc (https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/CreatingCustomLayouts/CreatingCustomLayouts.html) 'In base alla posizione di scorrimento corrente, la vista raccolta chiama quindi il layoutAttributesForElementsInRect: metodo per chiedere gli attributi delle celle e delle viste in un rettangolo specifico, che può o non può essere lo stesso del rettangolo visibile. –

risposta

3

Sfruttando lo layoutAttributesForElementsInRect(rect: CGRect) con la dimensione della cella nota, non è necessario memorizzare nella cache gli attributi e è sufficiente calcolarli per un dato rect come richiesto da collectionView. Dovresti comunque verificare i casi limite di 0 e i conteggi di sezione/riga massimi per evitare di calcolare attributi non necessari o non validi, ma ciò può essere fatto facilmente nelle clausole where attorno ai loop. Ecco un esempio funzionante che ho testato con 1000 sezioni x 1000 righe e funziona perfettamente senza ritardi sul dispositivo:

Modifica: ho aggiunto il valore LargeRect in modo che gli attributi possano essere precalcolati prima dello scorrimento arriva lì. Dalla tua modifica sembra che tu stia ancora memorizzando nella cache gli attributi che non credo siano necessari per le prestazioni. Inoltre porterà a un ingombro di memoria molto più grande con lo scorrimento maggiore. C'è anche un motivo per cui non vuoi usare lo CGRect fornito dal callback piuttosto che calcolarne uno dal contentOffset?

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

Puoi controllare il mio aggiornamento con la soluzione che ho trovato e qualsiasi feedback su questo. –

+0

Qualche suggerimento su come far funzionare questa funzione per celle di larghezza diversa? Grazie! – Unikorn

+0

@Unnikorn Se si intende avere una larghezza di cella diversa per ogni cella, è sicuramente più difficile. È possibile salvare gli attributi nel dizionario come nell'esempio originale della domanda precedente, ma ciò aumenterebbe l'impronta di memoria o memorizzerebbe l'offset x per ogni sezione (riga in questo caso) che aumenterebbe man mano che l'utente trascina verso destra o diminuisce quando si trascina a sinistra e viene calcolato in base alla larghezza totale delle celle visibili più la spaziatura delle celle. Sono sicuro che ci sarebbero problemi con ciò che avrebbe bisogno di ulteriori esplorazioni, ma quello potrebbe essere un inizio. –

1

È necessario creare una struttura dati per le celle ordinata per dimensione, in modo da ottenere un algoritmo che utilizza tali dimensioni per restringere l'intervallo di ricerca.

Prendiamo il caso di una tabella con celle alte 100 pixel per l'intera larghezza della vista e 250_000 elementi, per le celle che si intersecano {0, superiore, 320, inferiore}. Quindi la struttura di dati sarebbe un array ordinato dal top coordinate, e l'algoritmo di accompagnamento sarebbe come

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

aggiungere fino complessità layout attuale richiede.

1

cellAttrsDictionary non è appropriato memorizzare UICollectionViewLayoutAttributes. È indicizzato da NSIndexPath ma in layoutAttributesForElementsInRect stai cercando un'area. Se hai bisogno di cellAttrsDictionary per qualcos'altro, puoi lasciarlo ma memorizzare ogni cellaAttribute in aggiunta a in un'altra struttura dati che è più veloce da cercare.

Ad esempio, un array nidificato:

var allCellAttributes = [[UICollectionViewLayoutAttributes]]() 

Il primo array livello descrive un'area, diciamo che è in blocchi da 1000 pixel larghezza elevata e pieno schermo. Così tutti cellAttributes che si intersecano con il rettangolo {0, 0, screenwidth, 1000} andare in:

allCellAttributes[0].append(cellAttributes) 

Tutti cellAttributes che si intersecano con il rettangolo {0, 1000, screenwidth 2000} andare in:

allCellAttributes[1].append(cellAttributes) 

E così via ...

Poi, in layoutAttributesForElementsInRect è possibile cercare in questa scheda struttura saltando direttamente nella matrice a seconda della data CGRect. Se il rect è ad es.{0, 5500, 100, 6700} poi basta bisogno di cercare in questa gamma:

allCellAttributes[5] 
allCellAttributes[6] 

Questo dovrebbe dare l'idea di base, Spero che tu capisca cosa voglio dire.

Problemi correlati