web-dev-qa-db-fra.com

Coin de couche UILabelRadius impactant négativement les performances

J'ai créé une vue de document qui affiche le numéro de page dans le coin. Le numéro de page est un uilabel avec une couleur d'arrière-plan semi-transparente et a un rayon de coin (en utilisant la propriété cornerRadius de view's layer). Je l'ai positionné au-dessus d'un UIScrollView. Cependant, cela rend le défilement saccadé. Si je supprime le cornerRadius, les performances sont bonnes. Y a-t-il quelque chose que je puisse faire à ce sujet? Quelle serait une meilleure solution? Il semble avoir été réalisé dans le UIWebView sans aucun problème de performances.

45
Nick

Pour les étiquettes, ou les vues avec des coins arrondis et/ou les couleurs d'arrière-plan et les ombres sur les vues défilantes, la solution est assez simple:

Le plus gros problème vient de l'option de couche masksToBounds. Cela semble taxer considérablement les performances, mais l'étiquette semble avoir besoin de cette option pour masquer la couleur d'arrière-plan aux coins arrondis. Donc, pour contourner cela, vous devez définir la couleur d'arrière-plan du calque des étiquettes à la place et désactiver masksToBounds.

Le deuxième problème est que le comportement par défaut consiste à redessiner la vue chaque fois que possible, ce qui est totalement inutile avec des éléments statiques ou à changement lent sur les vues défilantes. Ici, nous définissons simplement layer.shouldRasterize = YES. Cela permettra à CA de "mettre en cache" une version tramée de la vue pour un dessin rapide lors du défilement (vraisemblablement avec une accélération matérielle).

Vous devez vous assurer que votre calque possède un canal alpha, sinon la pixellisation affectera le dessin des coins arrondis. Je n'ai jamais eu de problème car j'ai défini l'alpha pour mes couleurs d'arrière-plan, mais vous devrez peut-être vérifier votre situation.

Voici un exemple UILabel configuré pour fonctionner correctement sur un scollview:

UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(4, 4, 40.0, 24.0)];
lbl.font = [UIFont fontWithName:@"Helvetica" size:14.0];
lbl.textAlignment = UITextAlignmentRight;
lbl.text = @"Hello World";
// Must set the label background to clear so the layer background shows
lbl.backgroundColor = [UIColor clearColor];        
// Set UILabel.layer.backgroundColor not UILabel.backgroundColor otherwise the background is not masked to the rounded border.
lbl.layer.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:0.5].CGColor;

lbl.layer.cornerRadius = 8;
lbl.layer.borderColor = [UIColor blackColor].CGColor;
lbl.layer.borderWidth = 1;
// Huge change in performance by explicitly setting the below (even though default is supposedly NO)
lbl.layer.masksToBounds = NO;
// Performance improvement here depends on the size of your view
lbl.layer.shouldRasterize = YES;
lbl.layer.rasterizationScale = [UIScreen mainScreen].scale;
// self here is the child view in the scroll view
[self addSubview:lbl];
[lbl release];

Je peux remplir l'écran de l'iPad 1 avec des vues comme celle-ci et faire défiler en douceur :)

145
psychoacoustic

Je suis tombé sur celui-ci aussi. cornerRadius (pour UIButton et UIImageView) tuait les performances de mon application. Je n'ai trouvé aucune solution de travail qui arrondit UIButton avec une image d'arrière-plan, j'ai donc dû écrire mon propre morceau de code - la première méthode. Le second ajoute un coin arrondi et une bordure à un UIImageView. Je pense que ce code parle de lui-même.

+(void)setBackgroundImage:(UIImage*)image forButton:(UIButton*)button withCornerRadius:(float)cornerRadius borderColor:(CGColorRef)borderColor andBorderWith:(float)borderWidth
{
    UIImageView *tempImageView = [[UIImageView alloc] initWithFrame:button.frame];
    UIGraphicsBeginImageContextWithOptions(tempImageView.bounds.size, NO, 1.0);
    [[UIBezierPath bezierPathWithRoundedRect:tempImageView.bounds
                                cornerRadius:cornerRadius] addClip];
    [image drawInRect:tempImageView.bounds];
    tempImageView.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    [button setBackgroundImage:tempImageView.image forState:UIControlStateNormal];
    button.layer.shouldRasterize = YES;
    button.layer.borderColor = borderColor;
    button.layer.borderWidth = borderWidth;
    button.layer.cornerRadius = cornerRadius;
}

+(void)makeRoundedCornersForImageView:(UIImageView*)imgV withCornerRadius:(float)cornerRadius borderColor:(CGColorRef)borderColor andBorderWith:(float)borderWidth
{
    UIImageView *tempImageView = [[UIImageView alloc] initWithFrame:imgV.frame];
    UIGraphicsBeginImageContextWithOptions(tempImageView.bounds.size, NO, 1.0);
    [[UIBezierPath bezierPathWithRoundedRect:tempImageView.bounds
                                cornerRadius:cornerRadius] addClip];
    [imgV.image drawInRect:tempImageView.bounds];
    tempImageView.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    imgV.image = tempImageView.image;
    imgV.layer.shouldRasterize = YES;
    imgV.layer.borderColor = borderColor;
    imgV.layer.borderWidth = borderWidth;
    imgV.layer.cornerRadius = cornerRadius;
}
8
Szuwar_Jr

Comme Petert l'a suggéré, après avoir vu les messages "liés" dans la barre latérale, j'ai décidé de créer ma propre image. J'ai sous-classé UIView et ajouté une image d'arrière-plan (pour l'arrière-plan aux bords arrondis) et une étiquette de texte standard à l'init. Pour créer l'image d'arrière-plan, je crée une image extensible à l'aide des fonctions de dessin CG.

    // create background image
    CGFloat radius = frame.size.height / 2;
    CGFloat imgSize = (radius*2)+1; // 1 pixel for stretching
    UIGraphicsBeginImageContext(CGSizeMake(imgSize, imgSize));
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSetAlpha(context, 0.5f);
    CGContextSetLineWidth(context, 0);
    CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);

    CGFloat minx = 0;
    CGFloat midx = imgSize/2;
    CGFloat maxx = imgSize;
    CGFloat miny = 0;
    CGFloat midy = imgSize/2;
    CGFloat maxy = imgSize;

    CGContextMoveToPoint(context, minx, midy);
    CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
    CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
    CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
    CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
    CGContextClosePath(context);
    CGContextDrawPath(context, kCGPathFillStroke);

    UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    UIImage *stretchImage = [viewImage stretchableImageWithLeftCapWidth:radius topCapHeight:radius];

    UIImageView *stretch = [[UIImageView alloc] initWithImage:stretchImage];
    stretch.frame = self.bounds;
    stretch.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
    [self addSubview:stretch];
    [self sendSubviewToBack:stretch];
    [stretch release];
6
Nick

J'ai eu un problème similaire avec UILabel dans un UITableViewCell personnalisé avec des coins arrondis. Pour obtenir des performances fluides, j'ai "créé" des images avec des coins arrondis pour contourner cela ( voir ).

Beaucoup d'autres publications, y compris this , ou this pourraient aider.

3
petert