UIView qui puisse être bloquée

colas_colas_ Membre
juin 2014 modifié dans API UIKit #1

Bonjour !


 


Je bloque sur un problème d'architecture.


 


Je souhaiterais créer une classe mère CBDBlockableView, qui hériterait de UIView et qui disposerait de deux méthodes :



@interface CBDBlockableView : UIView

@property BOOL isBlocked ;

- (void)block ;
- (void)unblock ;

@end

 

Je souhaiterais ensuite sous-classer allègrement cette classe.


 


Je voudrais que la méthode - block empêche les sous-classes de dessiner la vue (et aussi bloque les actions delegate, mais ça c'est facile) et dessine à  la place, par exemple, un panneau sens interdit.


 


Je ne vois pas comment faire...


Impossible à  votre avis ?


 


 


Colas


 


 


 


PS : en écrivant, j'ai un idée, un peu crade : créer deux vues l'une sur l'autre. La première, la vraie. La deuxième, le sens interdit. Finalement, je me dis que c'est pas si crade que ça... (sauf pour la mémoire)

Réponses

  • AliGatorAliGator Membre, Modérateur
    La question à  se poser est : si tu block ta vue, est-ce que les subviews de cette vue doivent être juste hidden (ou bien supprimées de la hiérarchies pour ne plus être dessinées) mais gardée de côté pour que tu puisses à  tout moment facilement faire un unblock et les retrouver ? Ou est-ce qu'en général quand tu vas faire un block pour "bloquer" la vue tu feras rarement un unblock et c'est pas grave si tu vous alors toutes les subviews à  la poubelle (pour gagner de la mémoire) ?

    En fait le fait de permettre de faire "unblock" sur ta vue signifie que même si ta vue est "bloquée", il faut quand même que ses subviews existent ou soient stockées qqpart (prenant de la place mémoire) au cas où tu demandes un "unblock" un jour.


    Du coup si tu veux optimiser les choses, je pense qu'il faut prendre une autre approche :
    1) Prévoir une BlockableView certes avec block et unblock
    2) Mais que lorsque cette BlockableView n'est pas bloquée et doit afficher du contenu, charger ce contenu depuis un XIB (qui va te fournir toutes les subviews nécessaires pour mettre en subview de cette BlockableView

    - Comme ça quand ta vue est bloquée, elle va afficher juste le sens interdit (à  dessiner toi-même via le DrawRect, ou bien à  afficher via une bête UIImageView en subview, au choix), sans avoir besoin de charger et garder en mémoire ses subviews pour rien, donc en économisant beaucoup de mémoire dû au chargement de ces suviews.
    - Et quand tu la unblock, ça va charger les subviews via un XIB que tu auras précisé, et mettre ces vues du XIB en subview de ta BlockableView, pour ne les charger que si nécessaire
    - Et si tu la block à  nouveau, ça pourra supprimer toutes les subviews de ta BlockableView et ainsi re-libérer de la mémoire.



    Thomas a déjà  présenté un SmallTalk sur le sujet lors de la session CocoaHeads Rennes #14. Les SmallTalks ne sont pas filmés donc pas dispo en vidéo, mais on a mis à  disposition le projet d'exemple démontrant comment faire pour placer une UIView dans un XIB qui va automatiquement charger son contenu depuis un autre XIB. Ce n'est pas exactement le cas d'usage que tu veux (le nom du XIB est codé en dur dans la subclass, etc) mais c'est un bon point de départ pour trouver l'inspiration je pense.
  • colas_colas_ Membre
    juin 2014 modifié #3

    Merci de ta réponse. Oui, il faut garder les subviews.


     


    Ton approche se limite aux vues qui sont dessinées via un xib. En tous cas ça a l'air intéressant.


     


    Pour l'instant, je reste plus convaincu par mon idée de superposer deux vues l'une sur l'autre.


     


    Colas


  • AliGatorAliGator Membre, Modérateur
    Ok, donc tes subviews resteront en mémoire (juste cachée par une vue qui serait par dessus toutes les autres) même si ta vue est bloquée donc ?

    Attention dans ce cas à  vérifier que tu ne risques pas par erreur de permettre d'ajouter des subviews à  ta BlockingView depuis l'extéieur, qui viendraient potentiellement se rajouter par dessus ta vue sens-interdit
  • Oui je vais faire une interface .h qui ne donnera accès que à  la @property view.


    Je publie ça quand j'ai fini ;-) au cas où ça intéresse des gens.


  • Bon du coup, j'en ai profité pour regarder un peu CGContext, CGPath, etc.


     


    J'ai codé le signe interdit en Core Graphics.


    J'utilise une astuce dont j'ai eu l'idée pour me déplacer sur le cercle (une sorte de MoveToPointOnCircle()) (lignes *** dans le code).


     


    Si des experts lisent ceci et trouvent qu'il y a des maladresses/erreurs, je suis preneur ;-)



    const CGFloat noEntryRadiusCircle = 40 ;
    const CGFloat noEntryWidthStroke = 10 ;
    const CGFloat noEntryInset = 10 ;
    const CGFloat noEntryCornerRadius = 5 ;
    const CGFloat noEntryParameterForWidth = 1 ; // should be <= 1
    const CGFloat noEntryParameterForAngle = 0.2 ;


    + (void)drawNoEntrySignIn:(CGRect)mainRect
    withBackgroundColor:(CGColorRef)color
    andTransparency:(CGFloat)alpha
    withFactor:(CGFloat)factor
    {
    CGContextRef ctx= UIGraphicsGetCurrentContext();

    CGContextSetFillColorWithColor(ctx, color);

    CGRect newRect = [CBDCoreGraphicsHelper augmentedRectFrom:mainRect
    byLength:-noEntryInset
    withFactor:factor] ;

    UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRoundedRect:newRect
    cornerRadius:noEntryCornerRadius*factor] ;

    [bezierPath fill] ;


    CGPoint center = [CBDCoreGraphicsHelper centerOf:mainRect];

    //CGContextSaveGState(ctx); ????

    CGContextSetLineWidth(ctx,noEntryWidthStroke*factor);
    CGContextSetRGBStrokeColor(ctx,1,1,1,alpha);



    /*
    First part
    */
    CGMutablePathRef path = CGPathCreateMutable();

    CGFloat initialAngle = 1-noEntryParameterForAngle ;


    /*
    *****
    The following is equivalent to MoveToPointOnCircle()
    *****
    */
    CGPathAddArc(path,
    NULL,
    center.x,center.y,
    noEntryRadiusCircle,
    M_PI*initialAngle,
    M_PI*initialAngle,
    0) ;

    CGPoint initialPoint = CGPathGetCurrentPoint(path) ;

    CGPathAddArc(path,
    NULL,
    center.x,center.y,
    noEntryRadiusCircle,
    M_PI*initialAngle,
    M_PI*(initialAngle+noEntryParameterForWidth),
    0) ;

    CGPathAddLineToPoint(path,
    NULL,
    initialPoint.x,
    initialPoint.y) ;

    CGPathCloseSubpath(path) ;




    /*
    Second part
    */
    CGMutablePathRef path2 = CGPathCreateMutable();

    initialAngle = -noEntryParameterForAngle ;


    CGPathAddArc(path2,
    NULL,
    center.x,center.y,
    noEntryRadiusCircle,
    M_PI*initialAngle,
    M_PI*initialAngle,
    0) ;

    initialPoint = CGPathGetCurrentPoint(path2) ;

    CGPathAddArc(path2,
    NULL,
    center.x,center.y,
    noEntryRadiusCircle,
    M_PI*initialAngle,
    M_PI*(initialAngle+noEntryParameterForWidth),
    0) ;

    CGPathAddLineToPoint(path2,
    NULL,
    initialPoint.x,
    initialPoint.y) ;

    CGPathCloseSubpath(path2) ;



    CGContextAddPath(ctx, path);
    CGContextAddPath(ctx, path2);
    CGContextSetRGBStrokeColor(ctx,1,1,1,alpha);
    CGContextStrokePath(ctx);


    CGPathRelease(path);
    CGPathRelease(path2);
    }
  • AliGatorAliGator Membre, Modérateur
    Hummm je suis pas sûr d'avoir compris ton code, faut un peu décoder les maths derrière et j'ai pas trop le temps ^^

    Si j'ai bien compris, au lieu de dessiner le cercle en entier, tu dessines le cercle en 2 temps, d'abord un demi-cercle, puis stocke le point où tu es rendu, puis dessines l'autre-demi cercle. Ce qui te permet ensuite de tracer une ligne entre le point d'arrivée et le point mémorisé à  mi-chemin. C'est ça ?

    Mais dans ce cas pourquoi tu as besoin de First Part et Second Part ? Du coup j'ai dû louper un truc ^^
  • Oui c'est ce que je fais.


     


    En fait c'est pas un demi cercle que je trace, c'est un arc dont la longueur est gérée par le paramètre noEntryParameterForWidth (s'il vaut 1 ---> c'est un demi-cercle, s'il est plus petit ---->c'est un arc de cercle, s'il vaut 0 ----> c'est vide).


     


    C'est en jouant sur les paramètres que je me suis rendu compte que c'était mieux quand le paramètre vaut 1 !!


     


    Cela permet en tout cas que les "coins" de ligne transversale ne débordent pas du cercle. Je ne sais pas si c'est le cas, mais avec les stroke "épais", on a des fois ce risque, non ?


     


    cf. pictures


  • En fait, je commence par dessiner un arc de cercle "vide", de l'angle alpha à  l'angle alpha. Je mémorise le point. Je trace le vrai arc de cercle et enfin je rejoins les deux points.


  • AliGatorAliGator Membre, Modérateur
    juin 2014 modifié #10
    Pourquoi ne pas plutôt :
    - tracer un demi de cercle de l'angle alpha à  l'angle alpha+Ï€
    - mémoriser bottomLeft = le point d'arrivée
    - tracer l'autre demi-cercle de l'angle alpha+Ï€ à  alpha(+2*Ï€)
    - tracer une ligne du point d'arrivée (qui correspond donc au point sur le cercle à  l'angle alpha) et le point bottomLeft (qui correspond donc au point sur le cercle à  l'angle alpha+Ï€)

    Ca serait plus simple, non ? (Et plus propre que de "tracer un arc de cercle vide" ce qui me dérange un peu)
  • Oui c'est clairement plus simple !


  • Ce qui donne



    - (void)drawNoEntrySignIn:(CGRect)mainRect
    withBackgroundColor:(CGColorRef)color
    andTransparency:(CGFloat)alpha
    withFactor:(CGFloat)factor
    {
    CGContextRef ctx= UIGraphicsGetCurrentContext();


    /*
    The background
    */
    CGRect newRect = [[CBDCoreGraphicsHelper sharedInstance] augmentedRectFrom:mainRect
    byLength:-self.inset
    withFactor:factor] ;



    UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRoundedRect:newRect
    cornerRadius:self.cornerRadius*factor] ;
    CGContextSetFillColorWithColor(ctx, color);
    [bezierPath fill] ;




    /*
    The no-entry sign
    */
    CGMutablePathRef path = CGPathCreateMutable();

    CGPoint center = [[CBDCoreGraphicsHelper sharedInstance] centerOf:mainRect];
    CGFloat initialAngle = 1-self.parameterForAngle ;

    CGPathAddArc(path,
    NULL,
    center.x,center.y,
    self.radiusCircle,
    M_PI*initialAngle,
    M_PI*(initialAngle+1),
    0) ;

    CGPoint middlePoint = CGPathGetCurrentPoint(path) ;

    CGPathAddArc(path,
    NULL,
    center.x,center.y,
    self.radiusCircle,
    M_PI*(initialAngle+1),
    M_PI*(initialAngle+2),
    0) ;

    CGPathAddLineToPoint(path,
    NULL,
    middlePoint.x,
    middlePoint.y) ;

    CGPathCloseSubpath(path) ;



    /*
    Drawing
    */
    CGContextAddPath(ctx, path);

    CGContextSetLineWidth(ctx,self.widthStroke*factor);
    CGContextSetRGBStrokeColor(ctx,1,1,1,alpha);
    CGContextStrokePath(ctx);



    /*
    Memory
    */
    CGPathRelease(path);
    }
  • AliGatorAliGator Membre, Modérateur
    juin 2014 modifié #13
    Utiliser une sharedInstance et un appel de méthode tout ça pour calculer le center d'un CGRect, c'est un peu violent quand même !

    Pourquoi ne pas simplement utiliser CGPointMake(CGRectGetMidX(mainRect), CGRectGetMidY(mainRect)) ?
  • J'ai déjà  avoué sur ce forum beaucoup aimé la syntaxe [object methodWithLongName]...  :-*


  • AliGatorAliGator Membre, Modérateur
    juin 2014 modifié #15
    Pour une méthode, oui. Mais là  pour une fonction, créer un singleton pour rien c'est un peu too much ;) Cf l'autre sujet pour continuer la discussion sur cette question.
Connectez-vous ou Inscrivez-vous pour répondre.