webentwicklung-frage-antwort-db.com.de

Was ist die robusteste Methode, um ein UIView zum Neuzeichnen zu zwingen?

Ich habe eine UITableView mit einer Liste von Elementen. Durch Auswahl eines Elements wird ein viewController gedrückt, der dann die folgenden Schritte ausführt. from method viewDidLoad Ich löse eine URLRequest für Daten aus, die für eine meiner Unteransichten erforderlich sind - eine UIView-Unterklasse mit überschriebenem drawRect. Wenn die Daten aus der Cloud eingehen, beginne ich mit dem Aufbau meiner Ansichtshierarchie. Die betreffende Unterklasse erhält die Daten übergeben und die drawRect-Methode verfügt nun über alles, was sie zum Rendern benötigt.

Aber.

Da ich drawRect nicht explizit aufrufe - Cocoa-Touch erledigt das - kann ich Cocoa-Touch nicht mitteilen, dass ich wirklich wirklich möchte, dass diese UIView-Unterklasse gerendert wird. Wann? Jetzt wäre gut!

Ich habe [myView setNeedsDisplay] ausprobiert. Das funktioniert manchmal. Sehr fleckig.

Ich ringe stundenlang damit. Könnte jemand, der mir bitte einen grundsoliden, garantierten Ansatz zum Erzwingen eines erneuten Renderns von UIView bieten?.

Hier ist der Codeausschnitt, der der Ansicht Daten zuführt:

// Create the subview
self.chromosomeBlockView = [[[ChromosomeBlockView alloc] initWithFrame:frame] autorelease];

// Set some properties
self.chromosomeBlockView.sequenceString     = self.sequenceString;
self.chromosomeBlockView.nucleotideBases    = self.nucleotideLettersDictionary;

// Insert the view in the view hierarchy
[self.containerView          addSubview:self.chromosomeBlockView];
[self.containerView bringSubviewToFront:self.chromosomeBlockView];

// A vain attempt to convince Cocoa-Touch that this view is worthy of being displayed ;-)
[self.chromosomeBlockView setNeedsDisplay];

Prost, Doug

111
dugla

Die garantierte, felsenfeste Möglichkeit, ein UIView zum erneuten Rendern zu zwingen, ist [myView setNeedsDisplay]. Wenn Sie Probleme damit haben, werden Sie wahrscheinlich auf eines der folgenden Probleme stoßen:

  • Sie rufen es auf, bevor Sie die Daten tatsächlich haben, oder Ihr -drawRect: Hat etwas übercacht.

  • Sie erwarten, dass die Ansicht in dem Moment gezeichnet wird, in dem Sie diese Methode aufrufen. Es gibt absichtlich keine Möglichkeit, mit dem Kakao-Zeichen-System zu verlangen, dass Sie "gerade in dieser Sekunde zeichnen". Dies würde das gesamte View-Compositing-System stören, die Leistung des Papierkorbs beeinträchtigen und wahrscheinlich alle Arten von Artefakten erzeugen. Es gibt nur Möglichkeiten zu sagen, dass dies im nächsten Ziehzyklus gezeichnet werden muss.

Wenn Sie "etwas Logik, Zeichnen, etwas mehr Logik" benötigen, müssen Sie die "etwas mehr Logik" in eine separate Methode einfügen und sie mit -performSelector:withObject:afterDelay: Mit einer Verzögerung von 0 aufrufen "Noch mehr Logik" nach dem nächsten Ziehzyklus. In diese Frage finden Sie ein Beispiel für diese Art von Code und einen Fall, in dem er möglicherweise benötigt wird (obwohl es normalerweise am besten ist, nach anderen Lösungen zu suchen, wenn dies möglich ist, da dies den Code kompliziert).

Wenn Sie nicht glauben, dass die Dinge gezeichnet werden, setzen Sie einen Haltepunkt in -drawRect: Und sehen Sie, wann Sie angerufen werden. Wenn Sie -setNeedsDisplay Aufrufen, aber -drawRect: In der nächsten Ereignisschleife nicht aufgerufen wird, durchsuchen Sie Ihre Ansichtshierarchie und vergewissern Sie sich, dass Sie nicht versuchen, etwas auszutricksen. Klugheit ist meiner Erfahrung nach die häufigste Ursache für schlechtes Zeichnen. Wenn Sie glauben, am besten zu wissen, wie Sie das System dazu bringen können, das zu tun, was Sie wollen, dann bringen Sie es normalerweise dazu, genau das zu tun, was Sie nicht wollen.

186
Rob Napier

Ich hatte ein Problem mit einer großen Verzögerung zwischen dem Aufrufen von setNeedsDisplay und drawRect: (5 Sekunden). Es stellte sich heraus, dass ich setNeedsDisplay in einem anderen Thread als dem Haupt-Thread aufgerufen habe. Nachdem dieser Aufruf in den Haupt-Thread verschoben wurde, wurde die Verzögerung aufgehoben.

Ich hoffe, das ist hilfreich.

51

Die Geld-zurück-Garantie, Stahlbeton-solide Art, einen Blick auf synchron zeichnen(vorher zurück zum aufrufenden Code) dient zum Konfigurieren der Interaktionen von CALayer mit Ihrer Unterklasse UIView.

Erstellen Sie in Ihrer UIView-Unterklasse eine - display - Methode, die der Ebene mitteilt, dass "ja, es muss angezeigt werden" und dann "damit es so ist":

/// Redraws the view's contents immediately.
/// Serves the same purpose as the display method in GLKView.
/// Not to be confused with CALayer's `- display`; naming's really up to you.
- (void)display
{
    CALayer *layer = self.layer;
    [layer setNeedsDisplay];
    [layer displayIfNeeded];
}

Implementieren Sie auch eine - drawLayer:inContext: - Methode, die Ihre private/interne Zeichenmethode aufruft (was funktioniert, da jedes UIView ein CALayerDelegate ist):

/// Called by our CALayer when it wants us to draw
///     (in compliance with the CALayerDelegate protocol).
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
    UIGraphicsPushContext(context);
    [self internalDrawWithRect:self.bounds];
    UIGraphicsPopContext();
}

Und erstellen Sie Ihre benutzerdefinierte - internalDrawWithRect: - Methode zusammen mit der fehlersicheren - drawRect::

/// Internal drawing method; naming's up to you.
- (void)internalDrawWithRect:(CGRect)rect
{
    // @FILLIN: Custom drawing code goes here.
    //  (Use `UIGraphicsGetCurrentContext()` where necessary.)
}

/// For compatibility, if something besides our display method asks for draw.
- (void)drawRect:(CGRect)rect {
    [self internalDrawWithRect:rect];
}

Und jetzt rufen Sie einfach [myView display] Auf, wann immer Sie es wirklich brauchen, um zu zeichnen. - display Teilt dem CALayer den displayIfNeeded mit, der synchron in unseren - drawLayer:inContext: Zurückruft und die Zeichnung in - internalDrawWithRect: Ausführt, wobei der aktualisiert wird visuell mit dem, was in den Kontext gezogen wird, bevor Sie fortfahren.


Dieser Ansatz ähnelt dem oben beschriebenen von @ RobNapier, hat jedoch den Vorteil, dass - displayIfNeeded Zusätzlich zu - setNeedsDisplay Aufgerufen wird, wodurch er synchron ist.

Dies ist möglich, weil CALayers mehr Zeichenfunktionen bieten als UIViews - Ebenen sind niedriger als Ansichten und wurden explizit für das hochkonfigurierbare Zeichnen innerhalb des Layouts und (wie viele andere Dinge) entwickelt in Cocoa) sind so konzipiert, dass sie flexibel eingesetzt werden können (als Elternklasse oder als Delegator oder als Brücke zu anderen Zeichensystemen oder einfach für sich allein).

Weitere Informationen zur Konfigurierbarkeit von CALayers finden Sie im Abschnitt Einrichten von Ebenenobjekten im Core Animation Programming Guide .

13

Ich hatte das gleiche Problem und alle Lösungen von SO oder Google haben bei mir nicht funktioniert. Normalerweise funktioniert setNeedsDisplay, aber wenn es nicht funktioniert ...
Ich habe versucht, setNeedsDisplay der Ansicht von allen möglichen Threads und Dingen abzurufen - immer noch kein Erfolg. Wir wissen, wie Rob sagte, dass

"Dies muss im nächsten Ziehzyklus gezeichnet werden."

Aber aus irgendeinem Grund würde es diesmal nicht zeichnen. Und die einzige Lösung, die ich gefunden habe, besteht darin, sie nach einiger Zeit manuell aufzurufen, damit alles, was die Auslosung blockiert, vergeht:

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 
                                        (int64_t)(0.005 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
    [viewToRefresh setNeedsDisplay];
});

Dies ist eine gute Lösung, wenn Sie die Ansicht nicht häufig neu zeichnen müssen. Andernfalls gibt es normalerweise keine Probleme mit dem Aufruf von setNeedsDisplay, wenn Sie einige (Aktions-) Aktionen ausführen.

Ich hoffe, es hilft jemandem, der dort verloren ist, wie ich es war.

5
dreamzor

Nun, ich weiß, dass dies eine große Änderung sein könnte oder sogar nicht für Ihr Projekt geeignet ist, aber haben Sie in Betracht gezogen , den Push nicht durchzuführen, bis Sie bereits über die Daten verfügen ? Auf diese Weise müssen Sie die Ansicht nur einmal zeichnen und das Benutzererlebnis wird verbessert - der Push wird bereits geladen eingezogen.

Die Art und Weise, wie Sie dies tun, ist in der UITableViewdidSelectRowAtIndexPath, die Sie asynchron nach den Daten fragen. Sobald Sie die Antwort erhalten haben, führen Sie die Übergabe manuell durch und übergeben die Daten in prepareForSegue an Ihren viewController. In der Zwischenzeit möchten Sie möglicherweise einen Aktivitätsindikator anzeigen, um den einfachen Ladekontrollindikator zu überprüfen https://github.com/jdg/MBProgressHUD

0
Miki