web-dev-qa-db-fra.com

Quel est le moyen le plus robuste de forcer UIView à se redessiner?

J'ai un UITableView avec une liste d'éléments. La sélection d'un élément pousse un viewController qui procède ensuite comme suit. from method viewDidLoad Je déclenche une requête URLRequest pour les données requises par l'une de mes sous-vues - une sous-classe UIView avec drawRect remplacée. Lorsque les données arrivent du nuage, je commence à construire ma hiérarchie de vues. les données sont transmises à la sous-classe en question et sa méthode drawRect dispose désormais de tout ce dont elle a besoin pour être rendue.

Mais.

Parce que je n'appelle pas explicitement drawRect - Cocoa-Touch s'en charge - je n'ai aucun moyen d'informer Cocoa-Touch que je souhaite vraiment, vraiment, que cette sous-classe UIView affiche le rendu. Quand? Maintenant, ce serait bien!

J'ai essayé [myView setNeedsDisplay]. Cela fonctionne un peu parfois. Très inégale.

Je lutte avec ça depuis des heures et des heures. Est-ce que quelqu'un qui me plaît pourrait me fournir une approche solide et garantie pour forcer un nouveau rendu d'UIView.

Voici l'extrait de code qui alimente les données en vue:

// 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];

A bientôt, Doug

111
dugla

Le moyen garanti de forcer un UIView à restituer est [myView setNeedsDisplay]. Si vous rencontrez des problèmes avec cela, vous êtes probablement confronté à l'un de ces problèmes:

  • Vous l'appelez avant d'avoir les données ou votre -drawRect: met en cache quelque chose.

  • Vous vous attendez à ce que la vue dessine au moment où vous appelez cette méthode. Il n’existe intentionnellement aucun moyen d’exiger que le système de dessin Cocoa soit "dessiné pour le moment cette seconde". Cela perturberait tout le système de composition de vues, les performances de la corbeille et créerait probablement toutes sortes d'artefacts. Il n'y a que des façons de dire "cela doit être tracé dans le prochain cycle de tirage".

Si vous avez besoin de "quelque logique, dessinez, encore plus de logique", vous devez alors placer le "plus de logique" dans une méthode distincte et l'invoquer à l'aide de -performSelector:withObject:afterDelay: avec un retard de 0. Cela mettra "un peu plus de logique" après le prochain cycle de tirage. Voir cette question pour un exemple de ce type de code, et un cas où cela pourrait être nécessaire (bien qu'il soit généralement préférable de rechercher d'autres solutions si possible, car cela complique le code).

Si vous ne pensez pas que les choses se dessinent, mettez un point d'arrêt dans -drawRect: et voir quand on vous appelle. Si vous appelez -setNeedsDisplay, mais -drawRect: ne se fait pas appeler dans la prochaine boucle d’événements, puis indiquez dans votre hiérarchie de vues et assurez-vous que vous n’essayez pas de surpasser. L’extrême ingéniosité est la première cause de mauvais dessin dans mon expérience. Lorsque vous pensez savoir comment inciter le système à faire ce que vous voulez, vous le faites habituellement faire exactement ce que vous ne voulez pas.

186
Rob Napier

J'ai eu un problème avec un grand délai entre l'appel de setNeedsDisplay et drawRect: (5 secondes). Il s'est avéré que j'ai appelé setNeedsDisplay dans un thread différent du thread principal. Après avoir déplacé cet appel vers le fil principal, le délai a disparu.

J'espère que cela aidera.

51
Werner Altewischer

Le moyen garanti de remboursement, béton armé-solide pour forcer la vue à dessiner de manière synchrone(avant retourner au code d'appel) consiste à configurer les interactions de CALayer avec votre sous-classe UIView.

Dans votre sous-classe UIView, créez un - display méthode qui indique au calque que "oui, il a besoin d’être affiché", puis "pour le rendre ainsi":

/// 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];
}

Mettre également en œuvre un - drawLayer:inContext: méthode qui appellera votre méthode de dessin privée/interne (qui fonctionne puisque chaque UIView est un CALayerDelegate):

/// 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();
}

Et créez votre personnalisé - internalDrawWithRect: méthode, avec sécurité intégrée - 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];
}

Et maintenant, appelez [myView display] chaque fois que vous en avez vraiment besoin pour dessiner. - display dira le CALayer à displayIfNeeded, qui rappellera de manière synchrone dans notre - drawLayer:inContext: et dessine dans - internalDrawWithRect:, mettre à jour le visuel avec ce qui est dessiné dans le contexte avant de continuer.


Cette approche est similaire à celle de @ RobNapier ci-dessus, mais présente l'avantage d'appeler - displayIfNeeded en plus de - setNeedsDisplay, ce qui le rend synchrone.

Ceci est possible parce que CALayers expose plus de fonctionnalités de dessin que UIViews - les couches sont de niveau inférieur aux vues et conçues explicitement dans le but de dessiner hautement configurable dans la mise en page et dans Cocoa) sont conçus pour être utilisés de manière flexible (en tant que classe parente, ou en tant que délégant, ou en tant que passerelle vers d’autres systèmes de dessin, ou simplement par eux-mêmes).

Vous trouverez plus d'informations sur la configurabilité de CALayers dans la section Définition des objets de calque du Guide de programmation de l'animation de base .

13

J'ai eu le même problème, et toutes les solutions de SO ou de Google ne fonctionnaient pas pour moi. Habituellement, setNeedsDisplay fonctionne, mais quand cela ne fonctionne pas ...
J'ai essayé d'appeler setNeedsDisplay de la vue de toutes les manières possibles à partir de tous les fils et éléments possibles - toujours sans succès. Nous savons, comme l'a dit Rob, que

"Cela doit être tracé dans le prochain cycle de tirage."

Mais pour une raison quelconque, cela ne tirerait pas cette fois. Et la seule solution que j'ai trouvée consiste à l'appeler manuellement après un certain temps, pour laisser passer tout ce qui bloque le tirage au sort, comme ceci:

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];
});

C'est une bonne solution si vous n'avez pas besoin de redessiner la vue très souvent. Sinon, si vous vous déplacez, il n’ya généralement aucun problème à appeler setNeedsDisplay.

J'espère que cela aidera quelqu'un qui est perdu là-bas, comme moi.

5
dreamzor

Eh bien, je sais que cela pourrait être un gros changement ou même ne pas convenir à votre projet, mais avez-vous envisagé de ne pas effectuer le Push tant que vous n'avez pas déjà les données ? De cette façon, il vous suffit de dessiner la vue une seule fois et l'expérience utilisateur sera également meilleure: le Push s'installera déjà chargé.

Vous effectuez cette opération dans le UITableViewdidSelectRowAtIndexPath que vous demandez de manière asynchrone pour les données. Une fois que vous avez reçu la réponse, vous effectuez manuellement la transition et transmettez les données à votre viewController dans prepareForSegue. En attendant, vous voudrez peut-être afficher un indicateur d'activité, pour une vérification simple de l'indicateur de chargement https://github.com/jdg/MBProgressHUD

0
Miki