2016-01-28 7 views
9

Vollbildansicht der Tabellenansicht Nur-iPad-App. Ich habe Swipe zum Löschen auf meinen Zeilen aktiviert. Die Zeilenanimation endet immer nach dem Löschen (commitEditingStyle wird abgeschlossen), aber gelegentlich wird die gesamte Tabellenansicht eingefroren. Nicht die ganze Benutzeroberfläche, wohlgemerkt, es ist kein blockierter Haupt-Thread. Ich kann auf eine Spaltenüberschrift tippen oder auf die Zurück-Schaltfläche auf dem Navigationscontroller tippen, aber die Tabelle selbst wird gesperrt und kann nicht kopiert werden. Ich kann es einfach auftauen, indem ich auf eine meiner Spaltenkopf-Schaltflächen tippe.UITableView friert nach dem Wischen ein, um die gesamte Benutzeroberfläche zu löschen.

enter image description here

Ich bin nur völlig ratlos, was könnte das Einfrieren verursachen. Ich verwende einen NSFetchedResultsController und hier ist mein Delegate-Code dafür. Es ist ziemlich Kesselblech (aktualisiert: nicht als Kesselplatte jetzt einen batching Ansatz verwenden.):

// MARK: NSFetchedResultsController delegate methods 

lazy var deletedSectionIndexes : NSMutableIndexSet = { 
    return NSMutableIndexSet() 
}() 

lazy var insertedSectionIndexes : NSMutableIndexSet = { 
    return NSMutableIndexSet() 
}() 

lazy var deletedRowIndexPaths : [NSIndexPath] = { 
    return [NSIndexPath]() 
}() 

lazy var insertedRowIndexPaths : [NSIndexPath] = { 
    return [NSIndexPath]() 
}() 

lazy var updatedRowIndexPaths : [NSIndexPath] = { 
    return [NSIndexPath]() 
}() 


func controllerWillChangeContent(controller: NSFetchedResultsController) { 

} 

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { 
    switch(type) { 

    case .Delete: 
     if let indexPath = indexPath { 
      self.deletedRowIndexPaths.appendDistinct(indexPath) 
     } 
    case .Update: 
     if let indexPath = indexPath { 
      self.updatedRowIndexPaths.appendDistinct(indexPath) 
     } 
    case .Insert: 
     if let newIndexPath = newIndexPath { 
      self.insertedRowIndexPaths.appendDistinct(newIndexPath) 
     } 
    case .Move: 
     if let indexPath = indexPath, newIndexPath = newIndexPath { 
      self.insertedRowIndexPaths.appendDistinct(newIndexPath) 
      self.deletedRowIndexPaths.appendDistinct(indexPath) 
     } 
    } 
} 

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { 
    switch(type) { 

    case .Delete: 
     self.deletedSectionIndexes.addIndex(sectionIndex) 
    case .Insert: 
     self.insertedSectionIndexes.addIndex(sectionIndex) 
    default: 
     break 
    } 
} 

func controllerDidChangeContent(controller: NSFetchedResultsController) { 
    self.tableView.beginUpdates() 
    self.tableView.insertSections(self.insertedSectionIndexes, withRowAnimation: .None) 
    self.tableView.deleteSections(self.deletedSectionIndexes, withRowAnimation: .None) 

    self.tableView.insertRowsAtIndexPaths(self.insertedRowIndexPaths, withRowAnimation: .None) 
    self.tableView.deleteRowsAtIndexPaths(self.deletedRowIndexPaths, withRowAnimation: .None) 
    self.tableView.reloadRowsAtIndexPaths(self.updatedRowIndexPaths, withRowAnimation: .None) 
    self.tableView.endUpdates() 

    self.insertedSectionIndexes.removeAllIndexes() 
    self.deletedSectionIndexes.removeAllIndexes() 
    self.deletedRowIndexPaths.removeAll() 
    self.insertedRowIndexPaths.removeAll() 
    self.updatedRowIndexPaths.removeAll()   
} 

Der Lösch im didChangeObject Delegatmethode aufgerufen wird, aber technisch ist es nicht ein echte löschen. Ich setze einfach eine Eigenschaft auf -1 und speichere dann dieses Element durch den NSMangagedObjectContext - an diesem Punkt scheint die NSFRC das Richtige zu tun, was sie aus der Liste der abgerufenen Objekte entfernt, die mit diesem Prädikat abgerufen wurden:

NSPredicate(format: "account = %@ and quantity != -1", account) 

Dabei ist account ein gültiges kontoverwaltetes Objekt. Die Zeile verschwindet ohne ein Problem 90% oder mehr der Zeit. Es ist nur gelegentlich, dass nach dem Abschluss der Animation der Tisch in dem von mir beschriebenen Herrenhaus zufriert. Es friert niemals ein, wenn die Lösch-Schaltfläche noch angezeigt wird, also weiß ich, dass es nach dem Aufruf von commitEditingStyle aufgerufen wird. Die Schaltfläche Löschen verfügt nicht über eine benutzerdefinierte Implementierung. Dies ist die standardmäßige UITableView-Implementierung von Swipe zum Löschen. Hier ist meine commitEditingStyle Methode:

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { 
    if editingStyle == .Delete { 
     if let frameboardItem = self.fetchedResultsController.objectAtIndexPath(indexPath) as? IRMFrameBoardItemMO { 
      if frameboardItem.isNew { 
       // If it's never been pushed to the server, just delete locally. This will trigger a table reload 
       // via NSFetchedResultsController 
       DataManager.mainContext.deleteObject(frameboardItem) 
      } else { 
       // Otherwise mark it with a negative quantity which tells the server to delete it and tells the 
       // app to hide it. 
       frameboardItem.quantity = -1 
      } 

      do { 
       try DataManager.mainContext.save() 
      } catch let error as NSError { 
       dLog("Something went wrong: \(error.localizedDescription)") 
      } 

     } 

    } 

} 

Sie ein Video sehen kann hier von dem, was ich rede. Es ist über zwei Minuten, also wollen Sie vielleicht nicht die ganze Sache sehen, aber ich werde es hier als Referenz setzen.

https://vimeo.com/153406113

Würde gerne Vorschläge hören.

aktualisieren

ich die NSFRC Delegatmethoden aktualisiert, um eine Dosierung Ansatz zu verwenden, um die Updates, um sicherzustellen, erhalten alle auf einmal angewendet. Dies hat das Problem nicht behoben. Der Tisch friert immer noch periodisch ein.

+0

Sind Sie sicher, dass die Datenquelle ordnungsgemäß aktualisiert wird? Gibt es eine Situation, in der 'frameboardItem' in der folgenden Anweisung' nil' ist: 'wenn frameboardItem = self.fetchedResultsController.objectAtIndexPath (indexPath) als? IRMFrameBoardItemMO'? Und wenn es "Null" ist, wird die Tabelle aktualisiert, ohne dass die Datenquelle aktualisiert wird? – Alexander

+0

Ich habe mir das Video mehrmals angesehen, wo du gelöscht hast und es war eiskalt. Es scheint zu tun, wenn Sie oben und unten von einem Abschnitt löschen und dann Abschnitte ändern. und lösche oben und unten. Oder vielleicht sind es nur ein oder zwei bestimmte Abschnitte. Ich frage mich, ob es ein Problem mit der Anzahl Ihrer Listen gibt oder wie diese Objekte neu positioniert werden. das hält die UI der Tabelle hoch. Es wäre gut, wenn Sie es eingrenzen könnten, wie genau es reproduzierbar ist. Ich denke nur laut nach. –

+0

1) Ist das TableView in einem untergeordneten Ansichtscontroller? 2) Wenn Sie zu Testzwecken eine der Tasten (die auf Berührungen reagieren) so ändern, dass der Fernseher nach oben (oder unten) blättert, scrollt er tatsächlich? Das könnte einen Hinweis darauf geben, ob der Fernseher Berührungen ignoriert oder ob er friert, wenn er versucht zu scrollen. – pbasdf

Antwort

2

Ich habe auch über dieses Problem raten. Meine Idee ist, dass controllerDidChangeContent kann zweimal oder öfter und schneller als Tabellenaktualisierungen aufgerufen werden und diese Ursache für mehrere Aufrufe von tableView.beginUpdates(), die Tabelle auflegen kann.

So dies zu beheben schlage ich Wrap-Update in dispatch_async Block oder nur einfach boolean flag

func controllerDidChangeContent(controller: NSFetchedResultsController) { 
    dispatch_async(dispatch_get_main_queue(), {() -> Void in 
     self.tableView.beginUpdates() 
     // ..... rest of update code 
     self.updatedRowIndexPaths.removeAll() 
    }) 
} 
+0

Also was macht das genau dann? Die Updates werden dann in die Warteschlange gestellt? –

+0

@MattLong ja, Updates werden konsequent in der Warteschlange ausgeführt – sage444

+0

Leider ist es nicht ganz eine Silberkugel, aber es ist ziemlich nah. Ich habe das Einfrieren sogar mit dieser Technik reproduziert, aber die Frequenz ist sehr minimal - fast nie. Ich akzeptiere deine Antwort als richtig. Danke, dass Sie einen Stich in die Dunkelheit gemacht haben. Ich schätze es. –

0

Verwenden Sie Blöcke.

Es ist mir unklar, auf welchen Thread der MOC zugegriffen wird, obwohl, da Sie fetchedResultsController verwenden, es wahrscheinlich der wichtigste ist.

Als solche können Sie

  • deleteObject
  • save

in einem performBlockAndWait Warte ausgeführt werden soll. Dies kann dazu beitragen, die Datenintegrität zu gewährleisten.Etwas entlang der Linien:

DataManager.mainContext.performBlockAndWait {() -> Void in 
    DataManager.mainContext.deleteObject(frameboardItem) 
    if DataManager.mainContext.hasChanges { 
     do { 
      try DataManager.mainContext.save() 
     } catch let error as NSError { 
      dLog("Something went wrong: \(error.localizedDescription)") 
     } 
    } 
} 
0

Ich glaube nicht, das Tableview friert wegen Speicherproblemen oder unausgewogen beginnen */endEditing Anrufe (eine Ausnahme ausgelöst werden würde oder ein Signal gesendet werden).

Ich denke, es könnte Sachen auf einem anderen Thread als dem Hauptthread tun. In einem solchen Fall werden selbst Blöcke nicht helfen. (Setzen Sie einen Haltepunkt und überprüfen Sie, was Thread stoppt ..., auch: Test auf einem echten Gerät)

Meine Idee zu beheben, ist, versuchen Sie etwas anderes wie das Hinzufügen der Daten zu einem temporären Array hinzuzufügen oder zu entfernen und zu aktualisieren TableView innerhalb einer Methode ausführen (rufen Sie diese Methode auf, um explizit auf dem Hauptthread auszuführen, nachdem der Fetch-Ergebniscontroller seine Delegataufrufe beendet hat).

0

Haben Sie versucht, NSFetchedResultsControllerDelegate Delegierter in üblichen Weise zu implementieren, ich meine Tabelle Aktualisierung beginnen, wenn fetchedResultController zu fragt , Aktualisierungen vornehmen und dann die Aktualisierung beenden?

func controllerWillChangeContent(controller: NSFetchedResultsController) { 
    self.tableView.beginUpdates() 
} 

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { 
    /* update table here */ 
} 

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { 
    /* update table here */ 
} 

func controllerDidChangeContent(controller: NSFetchedResultsController) {  
    self.tableView.endUpdates() 
} 

UPADATE:
Ist es möglich, dass, wenn Sie ‚Objekt markieren als gelöscht‘ löst es einige kompliziertere Kette Wechsel des Objekts, die wiederum didChangeObject Funktion dazu führen, für mehrmals aufgerufen werden ? Haben Sie verfolgt, wie oft die Funktion DidChangeObject während der einzelnen Löschmarkierung aufgerufen wurde?

+0

Ja. So war es vor dem Wechsel zur Batch-Aktualisierung. Siehe die Frage für Details. –

+0

@MattLong Yeap, Entschuldigung, ich habe das vermisst. –