[Résolu] Dessiner avec ou sans context

busterTheobusterTheo Membre
mars 2015 modifié dans API UIKit #1

Bonjour,


 


je fais des bezier avec ce genre de truc



override func drawRect(myRect: CGRect) {

var monPath: UIBezierPath = UIBezierPath(rect:myRect)

let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 2.0)
CGContextSetStrokeColorWithColor(context, UIColor(red: 0.0/255.0, green: 0.0/255.0, blue: 0.0/255.0, alpha: 1.0).CGColor)
CGContextMoveToPoint(context, 100, 50)
CGContextAddCurveToPoint(context, 100, 250, 300, 250, 300, 50)
CGContextAddCurveToPoint(context, 300, 0, 100, 0, 100, 50)
CGContextStrokePath(context)
}

et je découvre sur le tuto de Ray Wenderlich (http://www.raywenderlich.com/90488/calayer-in-ios-with-swift-10-examples) dans l'exemple 8 (entre autres) que l'on peut faire sans.


C'est de janvier 2015, donc, c'est pas vieux.


 


Qu'est-ce-qui est mieux ?


 


Parce que ce qui m'intéresse, c'est qu'il n'utilise pas un drawRect dans une classe (fichier séparé) UIView qui doit être instanciée dans le ViewController, etc


 


Merci de m'éclairer


Réponses

  • Cool ce lien ! Et cool cette façon de dessiner dans une vue ! C'est compatible iOS 6 ou 7 ?


  • busterTheobusterTheo Membre
    mars 2015 modifié #3

    mort de rire...


     


    ben ouais, je trouve ça cool moi, mais je n'arrive pas à  faire un stoke, j'ai une putain d'erreur de 10 kms (le projet tourne bien pourtant) qui me parle de CGContext, alors que y'en n'a pas - oups


     


     


    Dès que je rajoute ça



    masquePath.lineWidth = 8.0
    UIColor.blackColor().setStroke()
    masquePath.stroke()

    derrière le 



    masquePath.closePath()

    ça me met ce message



     


    CGContextSetStrokeColorWithColor: invalid context 0x0. This is a serious error. This application, or a library it uses, is using an invalid context  and is thereby contributing to an overall degradation of system stability and reliability. This notice is a courtesy: please fix this problem. It will become a fatal error in an upcoming update



  • Joanna CarterJoanna Carter Membre, Modérateur
    Pourquoi le UIBezierPath non-utilisé ?
  • DrakenDraken Membre
    mars 2015 modifié #5

    Tu peux créer un contexte graphique sur mesure, sans passer par le drawRect d'une UIView


    Exemple d'un code dessinant un dégradé de couleur dans un contexte personnalisé :



    let size = CGSizeMake(200, 200)

    // CREATION CONTEXTE
    UIGraphicsBeginImageContext(size)
    let context = UIGraphicsGetCurrentContext()

    // PREPARATION DU DEGRADE
    let gradient = CAGradientLayer()
    gradient.frame = CGRectMake(0, 0, size.width, size.height)
    let couleur1 = UIColor(red: 214/255, green: 237/255, blue: 253/255, alpha: 1.0)
    let couleur2 = UIColor(red:241/255, green:248/255, blue:254/255, alpha:1.0)
    gradient.colors = [couleur1.CGColor, couleur2.CGColor]

    // DESSIN DANS LE CONTEXTE
    gradient.renderInContext(context)

    // RECUPERATION IMAGE
    let image = UIGraphicsGetImageFromCurrentImageContext()
    // DESTRUCTION CONTEXTE APRES UTILISATION
    UIGraphicsEndImageContext()



  •  


     


    Pourquoi le UIBezierPath non-utilisé ?

    Ben oui t'as raison, c'est un truc qui trainait - Désolé


  • Draken, mais où on place ce genre de code, y'a pas de path, donc je ne peux pas le mettre sur un layer ou dans une View ?


     


    Et moi, je dois faire des



    masquePath.addCurveToPoint(CGPointMake(350, 200), controlPoint1: CGPointMake(150, 400), controlPoint2: CGPointMake(350, 400)) 

    c'est un peu compliqué tout ça.


     


    Et pourquoi on doit faire dans un context ou pas ?


     


    Dans un context, ça donne ça



    CGContextAddCurveToPoint(context, 100, 250, 300, 250, 300, 50)
  • Joanna CarterJoanna Carter Membre, Modérateur

    C'est en Objective-C mais les principes sont là 



    - (void)drawRect:(CGRect)rect
    {
    UIBezierPath *path = [[UIBezierPath alloc] init];

    [path moveToPoint:CGPointMake(100, 50)];

    [path addCurveToPoint:CGPointMake(300, 50) controlPoint1:CGPointMake(100, 250) controlPoint2:CGPointMake(300, 250)];

    [path addCurveToPoint:CGPointMake(100, 50) controlPoint1:CGPointMake(300, 0) controlPoint2:CGPointMake(100, 0)];

    [[UIColor colorWithRed:0.0 / 255.0 green:0.0 / 255.0 blue:0.0 / 255.0 alpha:1.0] set];

    path.lineWidth = 2.0;

    [path stroke];
    }
  • Joanna CarterJoanna Carter Membre, Modérateur
    mars 2015 modifié #9

    Et, en Swift :



    override func drawRect(rect: CGRect)
    {
    let path = UIBezierPath()

    path.moveToPoint(CGPoint(x:100, y:50))

    path.addCurveToPoint(CGPoint(x:300, y:50), controlPoint1:CGPoint(x:100, y:250), controlPoint2:CGPoint(x:300, y:250))

    path.addCurveToPoint(CGPoint(x:100, y:50), controlPoint1:CGPoint(x:300, y:0), controlPoint2:CGPoint(x:100, y:0))

    path.closePath()

    UIColor(red:0.0/255.0, green:0.0/255.0, blue:0.0/255.0, alpha:1.0).set()

    path.lineWidth = 2.0

    path.stroke()
    }
  • DrakenDraken Membre
    mars 2015 modifié #10

    Voici un exemple utilisant un contexte graphique personnalisé pour fabriquer un dégradé de couleur, et l'afficher dans une UIImageView  :


     


    Tout tient dans le code, pas besoin de créer une UIView dans un autre fichier. 



    override func viewDidLoad() {
    super.viewDidLoad()

    // CREATION UIIMAGEVIEW PAR CODE
    var imageView = UIImageView(frame: CGRectMake(0, 0, 200, 400))
    self.view.addSubview(imageView)
    imageView.center = self.view.center

    // CREATION CONTEXTE
    let size = imageView.frame.size
    UIGraphicsBeginImageContext(size)
    let context = UIGraphicsGetCurrentContext()

    // DESSIN DEGRADE
    let gradient = CAGradientLayer()
    gradient.frame = CGRectMake(0, 0, size.width, size.height)
    let couleur1 = UIColor(red: 214/255, green: 237/255, blue: 253/255, alpha: 1.0)
    let couleur2 = UIColor(red:241/255, green:248/255, blue:254/255, alpha:1.0)
    gradient.colors = [couleur1.CGColor, couleur2.CGColor]
    gradient.renderInContext(context)

    // STOCKAGE DESSIN DANS UNE UIIMAGE
    let monImage = UIGraphicsGetImageFromCurrentImageContext()

    // DESTRUCTION CONTEXTE
    UIGraphicsEndImageContext()

    // AFFICHAGE DESSIN SUR ECRAN
    imageView.image = monImage

    }


  • Joanna CarterJoanna Carter Membre, Modérateur
    mars 2015 modifié #11

    Ou, suite à  mon dernier message, on peut travailler avec un layer :



    class MaView : UIView
    {
    private let path = UIBezierPath()

    required init(coder aDecoder: NSCoder)
    {
    super.init(coder: aDecoder)

    path.moveToPoint(CGPoint(x:100, y:50))

    path.addCurveToPoint(CGPoint(x:300, y:50), controlPoint1:CGPoint(x:100, y:250), controlPoint2:CGPoint(x:300, y:250))

    path.addCurveToPoint(CGPoint(x:100, y:50), controlPoint1:CGPoint(x:300, y:0), controlPoint2:CGPoint(x:100, y:0))

    path.closePath()

    let subLayer = CAShapeLayer()

    subLayer.path = path.CGPath

    subLayer.strokeColor = UIColor.blackColor().CGColor

    subLayer.fillColor = UIColor.whiteColor().CGColor

    subLayer.lineWidth = 2.0

    layer.addSublayer(subLayer)
    }
  • Bonjour, et merci encore pour vos réponses qui m'ont été bien utiles.


     


    J'ai donc fait deux projets :


     


    Le premier


    Avec des View imbriquées - Ces views sont instanciées d'après les UIView contenant les drawRect et leurs méthodes CGContextAddCurveToPoint(context, 100, 250, 300, 250, 300, 50) etc.


     


    Le second


    Sans ces instances, et leur drawRect - Et donc, les dessins sont fait dans des layers associés à  des View Apple, avec les méthodes masquePath.addCurveToPoint(CGPointMake(350, 200), controlPoint1: CGPointMake(150, 400), controlPoint2: CGPointMake(350, 400)) etc.


     


     


     


    EN gros, qu'elle est la meilleur méthode ?


     


    A savoir que je peux actuellement, avec un scrollView, déplacer et zoomer une image dans un masque avec son contour, sans problème dans chacun des deux projets.


     


    Doit-on préférer les CGContextAddCurveToPoint dans un contexte dans un drawRect dans une classe externe, ou les addCurveToPoint sur un path dans un layer dans le ViewController?


     


    C''est la grande question. :p   Merci d'avance

  • AliGatorAliGator Membre, Modérateur
    Je crois qu'il n'y a pas vraiment de réponse à  cette question en fait : les deux méthodes sont valables, c'est plutôt kif-kif :D

    L'avantage que j'aime avec les CALayer cependant :
    - Ces classes font partie de CoreAnimation, et donc par natures sont tout à  fait adaptées pour l'animation. Par exemple j'ai déjà  implémentée des composants pour dessiner des graphes avec des CAShapeLayer, et j'ai pu animer le changement de taille des barres quand les valeurs changeaient, avec un changement de hauteur animé très facilement, etc. Alors qu'avec drawRect, l'animation, c'est pas le pied
    - les classes CALayer comme leur nom l'indique sont des layers (des couches), et peuvent être utilisées comme tel un peu à  la manière de Photoshop. C'est pratique pour superposer des calques et des dessins de formes et facilement les faire bouger les uns par rapport aux autres ou changer leurs propriétés (couleurs, épaisseur, etc...)

    Mais après c'est aussi une affaire de goût...
  • Joanna CarterJoanna Carter Membre, Modérateur
    mars 2015 modifié #14
    Ali a bien raison en disant que les couches te serviraient mieux si tu prévoyais les animations. Côté stockage, surtout, je te conseillerais à  utiliser les UIBezierPath, même avec le drawRect, car c'est beaucoup plus facile à  gérer avec le CoreData.
  • Tu as un exemple à  donner Ali avec les animations sur un graphique à  bar par exemple ? J'aimerais bien faire pareil pour un projet d'étude :)

  • Joanna CarterJoanna Carter Membre, Modérateur
  • Super Joanna !! Je regarde ça.


  • busterTheobusterTheo Membre
    mars 2015 modifié #18

    Merci pour les réponses. Ok, j'adopte les layers.


     


    J'essaie de mettre  en pratique tout çà .


    Je démarre donc un nouveau projet.


     


    Et pourtant après avoir tester plusieurs codes (réponses du bar, et de google), je ne parviens pas à  placer les couleurs de fill et de stoke.


     


     


    J'ai bien ma forme, mais elle reste noire sans contour, et en plus j'ai une quinzaine d'erreurs (sur les méthodes CGContext... que je n'utilise pas) dans la debugBox. Je ne trouve pas où est mon erreur.


     


    Je laisse le code et les erreurs plus loin.


     


    Je précise que j'ai bien vu ce post et sa résolution, mais ça marche pas.


     


    Merci d'avance.


     


    Le code



    import UIKit

    class ViewController: UIViewController {

    // Mark: - Les objets

    var myView = UIView()
    var masqueLayer = CAShapeLayer()
    var path = UIBezierPath()

    // Mark: - Les variables

    // START de la courbe
    var startCurveX = CGFloat(100)
    var startCurveY = CGFloat(100)

    // controlPoint1 de 1e la courbe
    var aCurveP1X = CGFloat(100)
    var aCurveP1Y = CGFloat(250)

    // controlPoint2 de 1e la courbe
    var aCurveP2X = CGFloat(300)
    var aCurveP2Y = CGFloat(250)

    // La fin de la courbe
    var aEndCurveX = CGFloat(300)
    var aEndCurveY = CGFloat(100)

    // controlPoint1 de la 2e courbe
    var bCurveP1X = CGFloat(300)
    var bCurveP1Y = CGFloat(0)

    // controlPoint2 de la 2e courbe
    var bCurveP2X = CGFloat(100)
    var bCurveP2Y = CGFloat(0)

    // La fin de la 2e courbe
    var bEndCurveX = CGFloat(100)
    var bEndCurveY = CGFloat(100)

    // Mark: - View Life Cycle

    override func viewDidLoad() {
    super.viewDidLoad()

    myView = UIView(frame: CGRectMake(200,200,400,400))
    self.view.addSubview(myView)

    path.lineWidth = 8.0

    UIColor.whiteColor().setFill()
    path.fill()

    UIColor.redColor().setStroke()
    path.stroke()

    path.moveToPoint(CGPoint(x:startCurveX, y:startCurveY))
    path.addCurveToPoint(CGPoint(x:aEndCurveX, y:aEndCurveY), controlPoint1:CGPoint(x:aCurveP1X, y:aCurveP1Y), controlPoint2:CGPoint(x:aCurveP2X, y:aCurveP2Y))
    path.addCurveToPoint(CGPoint(x:startCurveX, y:startCurveY), controlPoint1:CGPoint(x:bCurveP1X, y:bCurveP1Y), controlPoint2:CGPoint(x:bCurveP2X, y:bCurveP2Y))
    path.closePath()

    masqueLayer.path = path.CGPath

    myView.layer.addSublayer(masqueLayer)
    }
    }

    Les erreurs (Toutes les méthodes CGContext y passent, je n'en met qu'une)



     


    <Error>: CGContextSetFillColorWithColor: invalid context 0x0. This is a serious error. This application, or a library it uses, is using an invalid context  and is thereby contributing to an overall degradation of system stability and reliability. This notice is a courtesy: please fix this problem. It will become a fatal error in an upcoming update.



  • Ouais désolé, j'avais mal regardé. C'est que je viens de comprendre en regardant à  nouveau ce post (exemple huit) aussi.


     


    Et donc, je revenais pour dire que j'avais compris.


     


    En fait je plaçais mes méthodes sur le path, alors qu'elles vont sur le layer.


    C'est dingue ça, le layer, en fait, c'est le path. Je croyais que le path était dans le layer (masqueLayer.path = path.CGPath).


     


    Je considère donc le sujet comme résolu, je me met dans le titre du post.


     


    Merci à  tous, je peux maintenant me consacrer à  enregistrer ce petit path dans le coreData.


     


    C'est génial ce bar.  :p   :D   :p   :D   :p


     


    Je laisse le code pour celui qui en a besoin



    import UIKit

    class ViewController: UIViewController {

    // Mark: - Les objets

    var myView = UIView()
    var masqueLayer = CAShapeLayer()
    var path = UIBezierPath()

    // Mark: - Les variables

    // START de la courbe
    var startCurveX = CGFloat(100)
    var startCurveY = CGFloat(100)

    // controlPoint1 de 1e la courbe
    var aCurveP1X = CGFloat(100)
    var aCurveP1Y = CGFloat(250)

    // controlPoint2 de 1e la courbe
    var aCurveP2X = CGFloat(300)
    var aCurveP2Y = CGFloat(250)

    // La fin de la courbe
    var aEndCurveX = CGFloat(300)
    var aEndCurveY = CGFloat(100)

    // controlPoint1 de la 2e courbe
    var bCurveP1X = CGFloat(300)
    var bCurveP1Y = CGFloat(0)

    // controlPoint2 de la 2e courbe
    var bCurveP2X = CGFloat(100)
    var bCurveP2Y = CGFloat(0)

    // La fin de la 2e courbe
    var bEndCurveX = CGFloat(100)
    var bEndCurveY = CGFloat(100)

    // Mark: - View Life Cycle

    override func viewDidLoad() {
    super.viewDidLoad()

    myView = UIView(frame: CGRectMake(200,200,400,400))
    self.view.addSubview(myView)

    path.moveToPoint(CGPoint(x:startCurveX, y:startCurveY))
    path.addCurveToPoint(CGPoint(x:aEndCurveX, y:aEndCurveY), controlPoint1:CGPoint(x:aCurveP1X, y:aCurveP1Y), controlPoint2:CGPoint(x:aCurveP2X, y:aCurveP2Y))
    path.addCurveToPoint(CGPoint(x:startCurveX, y:startCurveY), controlPoint1:CGPoint(x:bCurveP1X, y:bCurveP1Y), controlPoint2:CGPoint(x:bCurveP2X, y:bCurveP2Y))
    path.closePath()

    masqueLayer.path = path.CGPath

    masqueLayer.fillColor = UIColor.clearColor().CGColor
    masqueLayer.fillRule = kCAFillRuleNonZero
    masqueLayer.lineCap = kCALineCapButt
    masqueLayer.lineDashPattern = nil
    masqueLayer.lineDashPhase = 0.0
    masqueLayer.lineJoin = kCALineJoinMiter
    masqueLayer.lineWidth = 8.0
    masqueLayer.miterLimit = 10.0
    masqueLayer.strokeColor = UIColor.redColor().CGColor

    myView.layer.addSublayer(masqueLayer)
    }
    }
  • AliGatorAliGator Membre, Modérateur

    Tu as un exemple à  donner Ali avec les animations sur un graphique à  bar par exemple ? J'aimerais bien faire pareil pour un projet d'étude :)

    Pas sous la main, je l'ai fait pour un projet Client donc ce n'est pas Open-Source et je ne peux pas le publier ici.

    À part d'un exemple d'Ali, j'ai trouvé ceci : http://blog.pixelingene.com/2012/02/animating-pie-slices-using-a-custom-calayer/

    J'ai lu en diagonale, ça a l'air bien comme article mais j'ai du mal à  comprendre pourquoi il s'embête à  créer son propre PieSliceLayer alors que CAShapeLayer suffit.

    Je veux dire :
    - créer un PieSliceLayer pour avoir un niveau d'abstraction et avoir un truc plus simple à  manipuler qui a directement les propriétés startAngle et endAngle, pourquoi pas, c'est plus simple que d'avoir à  calculer le CGPath à  partir de ces angles à  chaque fois. Mais bon, c'est pas la mort non plus et faire une sous-classe juste pour ça n'est pas non plus une absolue nécessité
    - par contre dans son article il justifie la création de ce truc parce que CAShapeLayer ne s'anime pas comme il veut (et que la slice "se retourne" lors de l'animation au lieu de changer d'angle et de joliment tourner autour du centre du graphe) comme l'illustre son petit jeu d'images. Or si ça fait cela, c'est parce que les angles qu'il utilise font que les 2 slices (celle de -60° à  60° et celle de -120° à  120°) ne sont pas dans le même sens trigonométrique. Il aurait inversé les 2 angles dans le 2ème cas (de 120° à  -120°) ou plutôt les aurait normalisés (de 120° à  240°) pour que le sens de l'arc de cercle de startAngle vers endAngle se fasse toujours dans le même sens dans les 2 cas, et son animation aurait tout de suite mieux marché. Et au lieu de juste corriger ça et continuer à  profiter de CAShapeLayer, il a recréé son propre CALayer et réimplémenté la méthode de dessin, juste à  cause de ça... alors qu'il aurait pu s'en passer !
Connectez-vous ou Inscrivez-vous pour répondre.