TableView Cell Inhalt springt beim "scrollen"

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • TableView Cell Inhalt springt beim "scrollen"

    Hallo,
    In meiner App für Ipad benutze ich eine uitableview. Jede Zelle enthält 2 Labels und mehrere Buttons. Die Labels sind bereits ind der Klasse der Zelle definiert, die Buttons erzeuge ich dynamisch in der Funktion:
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

    Da die Tabelle mehr Zellen enthält, als dargestellt werden können, muss der User "scrollen". Dabei kommt es zu einem (für mich) unerklärlichen Phenomän:
    Die dynamisch erzeugten Buttons bleiben nicht in der Zelle, in der Sie erzeugt wurden, sondern "springen" hin und her; die Labels hingegen sind immer richtig.
    Der Code für die Zellenerzeugung sieht wie folgt aus:

    Quellcode

    1. static NSString *cellIdentifier = @"atributValuesCell";
    2. atributValueCell_Pad *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    3. Attribut *a= [steckerAttribute objectAtIndex:indexPath.row];
    4. AttributValue *av = a.atributvalueRel;
    5. Wert *settedValue = av.value;
    6. CGSize maximumSize = CGSizeMake(250, 999);
    7. UIFont *myFont = [UIFont systemFontOfSize:17];
    8. CGSize myStringSize = [[NSString stringWithFormat:@" %@ ",a.name] sizeWithFont:myFont constrainedToSize:maximumSize];
    9. [cell.atributNameLabel setFrame:CGRectMake(212, 2, myStringSize.width, cell.atributNameLabel.frame.size.height)];
    10. [cell.settedValueNameLabel setFrame:CGRectMake(cell.atributNameLabel.frame.origin.x+cell.atributNameLabel.frame.size.width+2, cell.atributNameLabel.frame.origin.y, 250, cell.atributNameLabel.frame.size.height)];
    11. cell.settedValueNameLabel.text = settedValue.name;
    12. cell.atributNameLabel.text = [NSString stringWithFormat:@" %@ ",a.name];
    13. //werteArray nach name sortieren
    14. NSMutableArray *werteArr = [NSMutableArray arrayWithCapacity:0];
    15. [werteArr addObject:defaultValue];
    16. NSMutableArray *werteArr2 = [NSMutableArray arrayWithArray:[a.werteRel allObjects]];
    17. NSSortDescriptor *sort=[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:NO];
    18. [werteArr2 sortUsingDescriptors:[NSArray arrayWithObject:sort]];
    19. [werteArr addObjectsFromArray:werteArr2];
    20. int j=0;
    21. for (Wert *w in werteArr)
    22. {
    23. //NSLog(@"wert:%@",w.name);
    24. //für jeden Wert des Attributes ein button, falls nicht bereits vorhanden
    25. int tag = (indexPath.row+1)*1000+j;
    26. UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(moveRowManually:)];
    27. //erster Ausfruf -> Buttons erzeugen
    28. UIButton *ib;
    29. if([[steckerButtonsCreated objectAtIndex:indexPath.row] isEqualToString:@"false"])
    30. {
    31. ib = [UIButton buttonWithType:UIButtonTypeCustom];
    32. [ib setFrame:CGRectMake((j+1)*80+50, 27, 72,72)];
    33. [ib setBackgroundImage:[UIImage imageNamed:[NSString stringWithFormat:@"%@.png",w.wertId]] forState:UIControlStateNormal];
    34. //[ib setTitleColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1] forState:UIControlStateNormal];
    35. //[ib setTitle:w.name forState:UIControlStateNormal];
    36. //[ib addGestureRecognizer:panGesture];
    37. if ([w.possible isEqualToString:@"1"])
    38. {
    39. [ib addTarget:self action:@selector(selectValue:) forControlEvents:UIControlEventTouchUpInside];
    40. [ib addGestureRecognizer:panGesture];
    41. [ib setImage:nil forState:UIControlStateNormal];
    42. }
    43. else
    44. {
    45. [ib setImage:[UIImage imageNamed:@"not_selectable_layer.png"] forState:UIControlStateNormal];
    46. }
    47. ib.tag = tag;
    48. [ib setContentMode:UIViewContentModeCenter];
    49. [cell.contentView addSubview:ib];
    50. }
    51. else
    52. {
    53. ib = (UIButton*) [self.view viewWithTag:tag];
    54. if ([w.possible isEqualToString:@"1"])
    55. {
    56. //NSLog(@"add Target for wert:%@ in attribut:%@", w.name, a.name);
    57. [ib addTarget:self action:@selector(selectValue:) forControlEvents:UIControlEventTouchUpInside];
    58. [ib addGestureRecognizer:panGesture];
    59. [ib setImage:nil forState:UIControlStateNormal];
    60. }
    61. else
    62. {
    63. //NSLog(@"remove Target:%@ for wert:%@ in attribut:%@",ib, w.name, a.name);
    64. [ib removeTarget:self action:@selector(selectValue:) forControlEvents:UIControlEventTouchUpInside];
    65. [ib removeGestureRecognizer:panGesture];
    66. [ib setImage:[UIImage imageNamed:@"not_selectable_layer.png"] forState:UIControlStateNormal];
    67. }
    68. }
    69. j++;
    70. }
    71. //[self moveRowToSelectedValues];
    72. [steckerButtonsCreated replaceObjectAtIndex:indexPath.row withObject:@"true"];
    73. return cell;
    Alles anzeigen


    Hat jemand ne Idee, wieso die Buttons nicht in den Zellen bleiben, in denen ich sie erzeuge?
  • Das bleibt ja auch so:


    Quellcode

    1. static NSString *cellIdentifier = @"atributValuesCell";
    2. atributValueCell_Pad *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]
    3. if (cell == nil)
    4. {
    5. cell = [[UITableViewCell alloc] init ... ];
    6. // deine Label und Buttons hinzufügen ...
    7. }
    8. return cell;
    9. ;
    Alles anzeigen
  • Tobse001 schrieb:

    Unter iOS 6 kann cell niemals nil sein nach dequeueReusableCell, davor brauchte man das mit der Abfrage und alloc/init


    Dürfte aber auch bedeuten, dass das was er macht unter iOS6 so nicht machbar ist ? Sprich das SB des IB gibt immer eine Zelle ohne Buttons zurück und dann muss für jede Zelle die Buttons erzeugt werden ? Damit wird das Ganze dann so langsam das es nicht mehr nutzbar wird ? Versteh ich gerade nicht das System….

    Das war doch gerade das gute der reuse Zelle, dass man nur 5 oder 6 mal eine Zelle mit alle Ihren Buttons erzeugen musste und sie dann immer wieder verwenden konnte. Das würde nach dem neuen Prinzip so nicht mehr funktionieren wenn, es sei denn man erzeugt die Zelle wirklich komplett im IB

    Gruß

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)
  • Wo das steht ist eine gute Frage, ich hab's aus den WWDC 2012 Videos und es auch schon getestet.

    DequeueReusableCell gibt immer eine für den Identifier zurück.
    Wenn es im Moment keine reusable Cell gibt, erstellt die TableView automatisch eine für einen.

    Ohne IB dürfte das natürlich nicht klappen.
    Ich würde aber eher sagen, dass das dann auch nicht zum tragen kommt, da die TableView sicher nur nach einem Prototype mit dem Identifier im IB sucht.
    Sonst kann sie ja nicht wissen was sie da erstellen soll.
  • Tobse001 schrieb:

    Wo das steht ist eine gute Frage, ich hab's aus den WWDC 2012 Videos und es auch schon getestet.

    Eben, das steht nirgends.

    Tobse001 schrieb:

    DequeueReusableCell gibt immer eine für den Identifier zurück.
    Wenn es im Moment keine reusable Cell gibt, erstellt die TableView automatisch eine für einen.

    Nein, nur wenn Du im TableView ein nib oder eine Cell Klasse für den Identifier registriert hast. Hast Du das nicht, gibt's auch unter iOS 6 schon mal ein nil zurück. Das Ganze gibt es übrigens nicht erst seit iOS 6, sondern schon seit iOS 5.

    Tobse001 schrieb:

    Ohne IB dürfte das natürlich nicht klappen.

    Doch, das geht auch ohne IB.

    Michael
  • Michael schrieb:

    Das Ganze gibt es übrigens nicht erst seit iOS 6, sondern schon seit iOS 5.


    Das man Prototypes anlegen kann, oder das die Tableview automatisch eine neue Cell erstellt wenn keine reusable vorhanden ist?
    Das ist ja zumindest bei iOS6 als große Neuheit vorgestellt worden (ausgedacht für UICollectionView und "backported" für UITableView).


    Michael schrieb:


    Tobse001 schrieb:

    Ohne IB dürfte das natürlich nicht klappen.

    Doch, das geht auch ohne IB.

    Michael


    Ok, gerade registerClass:forCellReuseIdentifier: gesehen.
    Aber das zumindest ist neu (iOS 6).

    Oder meintest du reuseable Cells allgemein?
    Dass es die schon ewig gibt ist mir klar.
  • Hallo @all,
    ich freue mich über die rege Diskussion hier. Doch leider weiß ich immer noch nicht, wie ich mein Problem löse.

    Da die Anzahl der Buttons, die ich erzeuge variiert, kann ich diese nicht schon im Vorfeld in der Cell-Klasse definieren. Dafür habe ich mir ein Array erzeugt, in dem ich mir merke, für welche Zelle ich die Buttons schon erzeugt habe, so dass sie nciht jedes mal neu erzeugt werden. Die Elemente (2 Labels und 2 Buttons), die in der Zellenklasse definiert sind passen auch immer, aber bei den neu erzeugten Buttons scheint irgendwie die Zuweisung zu der Zelle verloren zu gehen, wenn ich "scrolle".

    Hoffe das erklärt mein Problem etwas genauer und jemand hat nen Lösungsvorschlag.
  • Aber im Endeffekt ist es so wie ich gesagt habe ? Entweder er nimmt die Cell aus dem identifizier, dann bekommt er immer eine ohne buttons und muss die für jede Zelle bei jedem Aufruf erstellen, was einfach viel zu langsam sein dürfte bei der Menge an Zeugs was der TO da reinhaut

    oder er macht die Zelle komplett von Hand bzw. lädt sie aus einem NIB wie früher und added die Buttons dann programmatisch.

    Oder sehe ich das falsch ?

    Gruß

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)
  • pool338 schrieb:

    Hallo @all,
    ich freue mich über die rege Diskussion hier. Doch leider weiß ich immer noch nicht, wie ich mein Problem löse.

    Da die Anzahl der Buttons, die ich erzeuge variiert, kann ich diese nicht schon im Vorfeld in der Cell-Klasse definieren. Dafür habe ich mir ein Array erzeugt, in dem ich mir merke, für welche Zelle ich die Buttons schon erzeugt habe, so dass sie nciht jedes mal neu erzeugt werden. Die Elemente (2 Labels und 2 Buttons), die in der Zellenklasse definiert sind passen auch immer, aber bei den neu erzeugten Buttons scheint irgendwie die Zuweisung zu der Zelle verloren zu gehen, wenn ich "scrolle".

    Hoffe das erklärt mein Problem etwas genauer und jemand hat nen Lösungsvorschlag.


    Da ist doch schon Dein Problem. Du kannst nicht die Buttons merken und in verschiedene Zellen stecken, Wie soll das gehen. Eine Instanz von Button darf auch nur in einer Zelle sein. Sonst hast Du genau Dein Verhalten. Du must schon für jede Zelle eigene Buttons erzeugen oder eben das reuse der TableView benutzen.

    Gruß

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)
  • Ja.

    Wenn er im IB die Zelle mit diesem Identifier ohne Buttons erstellt,
    bekommt er auch eine ohne die Buttons von der TableView erstellt.

    Will man nun wissen ob man Buttons hinzufügen muss oder nicht,
    sollte man also evtl. eher abfragen ob die Button property nil ist.
    (Sofern man eine Cell Subclass mit so einer Property erstellt hat, sonst eben schauen ob es eine View mit Tag XX gibt o.Ä.)

    Wegen unterschiedlicher Buttonanzahl je nach IndexPath:
    Entweder 2 verschiedene Prototypes/Identifer, oder wenn nur ein Button gebraucht wird den zweiten auf hidden setzen.

    Das ist mit Sicherheit weit schneller als bei jedem reusen sonst wieder neue Views (Buttons) zu erstellen.
    Um die "teuren" Objekterzeugungen gering zu halten macht man ja das Spiel mit den reusable Cells.


    @Threadstarter:
    Das anfangs von Thalius verlinkte Dokument erklärt,
    dass die Tableview nur eine handvoll Zellen erzeugt.
    (Wenn man nur einen Identifier nutzt praktisch so viele wie es braucht um den Screen zu füllen.)

    Du bekommst also:
    - Für die selbe Row nicht immer wieder die selbe Zelle wie beim letzten mal
    - Für eine Row evtl. eine Zelle welche schon für eine andere Row verwendet wurde.
    inkl. der dort genutzten Buttons und evlt. gesetzten Labeltexten usw.

    Du musst dich also darum kümmern die alten Inhalte zurückzusetzen und/oder durch neue zu ersetzen.
    Und du kannst das auch über den IB machen.
    Erstelle dort einfach schon die maximale Anzahl der Buttons und positioniere sie richtig.

    Bei cellForRowAtIndexPath prüfst du einfach welche der Zellen du auf hidden = NO und hidden = YES setzten musst.
  • @Tobse:
    Ich erstelle jeden Button nur einmal, denn wenn das erste mal "cellForRowAtIndexPath" aufgerufen wird, erstelle ich die Buttons und speichere in einem Array, dass diese Buttons angelegt wurden. Somit werden diese kein weiteres mal erzeugt.

    Da sowohl die Positionierung, das Button Image als auch die Anzahl der Buttons im weiteren Verlauf der APP geändert wird, kann ich diese nicht schon im Vorfeld (IB) erzeugen !?
  • Wenn das mit den hidden Buttons nicht geht, dann must du für jeden Zelltyp einen eigenen Identifier machen und diese entsprechend befüllen. Wenn das auch nicht geht weil die Anzahl an verschiedenen Zellen variable ist, dann solltest Du Dein ganzes AppKonzept nicht einmal überdenken

    Gruß

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)