Comment dessiner des lignes et écrire du texte sur une image

PatyomPatyom Membre
juillet 2017 modifié dans API AppKit #1

Bonjour,


 


J'ai importé des graphiques d'internet et je voudrais les "agrémenter" d'annotations et de tracés verticaux représentant les heures de la journée pour ensuite enregistrer le tout.


 


J'ai déjà  trouvé comment créé des ligne verticales mais je n'arrive pas à  changer la couleur.


Voici le code de mon projet :




import Cocoa

final class Line: NSView {
override func draw(_ dirtyRect: NSRect) {
let myPath = NSBezierPath()
myPath.move(to: CGPoint(x: 20, y: 20))
myPath.line(to: CGPoint(x: 20, y: 300))
myPath.lineWidth = 0.3
myPath.stroke()
}
}

final class ViewController: NSViewController {

override func viewDidLoad() {
super.viewDidLoad()

// Représention de l'image ou image extraite d'un fichier
let frame = CGRect(x: 0, y: 0, width: 100, height: 600)

// Incrustation dans l'image sélectionnée
let line = Line(frame: frame)
view.addSubview(line)
}

override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}

}

Se sont des lignes de codes que j'ai récupérées et dont je comprends le cheminement.


Là , maintenant j'aimerais


. Changer la couleur de la ligne,


. écrire un texte dedans, 


. et sauvegarder le tout.


 


Merci

«13

Réponses

  • DrakenDraken Membre
    juillet 2017 modifié #2

    Je n'ai pas le temps de me pencher sur la question, maintenant. Mais le sujet a été abordé souvent dans les pages du forum. Il faut utiliser un contexte graphique et/ou la fonction drawRect() draw() des views. Fait une recherche sur ces thèmes dans le forum. Les réponses y sont nombreuses.


     


    EDIT : fichu macOS ! iOS c'est mieux quand même .. * évite la chaussure de Tablier *

  • Ok, je vais faire comme çà 


     


    merci


  • Un premier jet pour afficher une vue " composite " formée d'une image et d'un ensemble de lignes.


     


    C'est une NSView personnalisée avec deux paramètres : 


    - une NSImage?


    - un tableau de lignes


     


    Les lignes sont définies dans une structure TypeLigne :


    - premier point


    - second point


    - épaisseur


    - couleur



    import Foundation
    import Cocoa

    struct TypeLigne {
    var point1 : NSPoint
    var point2 : NSPoint
    var width : CGFloat
    var couleur : NSColor
    }

    class ViewAvecCalque : NSView {
    var image : NSImage?
    var listeLignes : [TypeLigne]?

    override func draw(_ dirtyRect: NSRect) {
    let rect = dirtyRect
    // let context = NSGraphicsContext.current()
    // Si l'image existe on la dessine sur la vue
    if let image = image {
    image.draw(in: rect)
    }
    // Si les lignes existent on les dessines sur la vue
    if let lesLignes = listeLignes {
    for ligne in lesLignes {
    let path = NSBezierPath()
    ligne.couleur.set()
    path.move(to: ligne.point1)
    path.line(to: ligne.point2)
    path.lineWidth = ligne.width
    path.stroke()
    }
    }

    }

    }


    Exemple d'utilisation :



    //
    // ViewController.swift
    // OSX_VueComposite
    //

    import Foundation
    import Cocoa

    class ViewController: NSViewController {

    var maVue = ViewAvecCalque()

    override func viewDidLoad() {
    super.viewDidLoad()

    // Création de la vue
    maVue.frame = CGRect(x: 100, y: 40, width: 200, height: 200)
    self.view.addSubview(maVue)

    // Définition image
    maVue.image = NSImage.init(named: "BlueSky")

    // Définition lignes
    maVue.listeLignes = creerLignes()

    }

    func creerLignes() -> [TypeLigne] {

    // Ligne Rouge
    let ligne1 = TypeLigne(point1: NSMakePoint(0,0),
    point2: NSMakePoint(100,100),
    width: 3.0,
    couleur: NSColor.red)

    // Ligne Bleu
    let ligne2 = TypeLigne(point1: NSMakePoint(0, 0),
    point2: NSMakePoint(100, 150),
    width: 3.0,
    couleur: NSColor.blue)

    // Stockage des lignes dans un tableau
    var tableauLignes = [TypeLigne]()
    tableauLignes.append(ligne1)
    tableauLignes.append(ligne2)

    return tableauLignes

    }

    override var representedObject: Any? {
    didSet {
    // Update the view, if already loaded.
    }
    }


    }


  • DrakenDraken Membre
    juillet 2017 modifié #5

    Nouvelle version avec les textes. Chaque texte est défini par une position et un NSAttributedString (une version évoluée de String pouvant encapsuler du texte, des informations de couleurs, des polices de caractères, des informations de formatage, etc.. Un outil puissant, mais pas spécialement simple d'emploi). On en parle dans plusieurs sujets sur le forum.



    import Foundation
    import Cocoa

    struct TypeLigne {
    var point1 : NSPoint
    var point2 : NSPoint
    var width : CGFloat
    var couleur : NSColor
    }

    struct TypeTexte {
    var point : NSPoint
    var text : NSAttributedString
    }

    class ViewAvecCalque : NSView {
    var image : NSImage?
    var listeLignes : [TypeLigne]?
    var listeTextes : [TypeTexte]?

    override func draw(_ dirtyRect: NSRect) {
    let rect = dirtyRect

    // Si l'image existe on la dessine sur la vue
    if let image = image {
    image.draw(in: rect)
    }

    // Si les lignes existent on les dessines sur la vue
    if let lesLignes = listeLignes {
    for ligne in lesLignes {
    let path = NSBezierPath()
    ligne.couleur.set()
    path.move(to: ligne.point1)
    path.line(to: ligne.point2)
    path.lineWidth = ligne.width
    path.stroke()
    }
    }

    // Si les textes existent, on les dessines
    if let lesTextes = listeTextes {
    for texte in lesTextes {
    texte.text.draw(at: texte.point)
    }
    }

    }

    }


    Mise en oeuvre :



    //
    // ViewController.swift
    // OSX_VueComposite
    //

    import Foundation
    import Cocoa

    class ViewController: NSViewController {

    var maVue = ViewAvecCalque()

    override func viewDidLoad() {
    super.viewDidLoad()

    // Création de la vue
    maVue.frame = CGRect(x: 100, y: 40, width: 200, height: 200)
    self.view.addSubview(maVue)

    // Définition image
    maVue.image = NSImage.init(named: "BlueSky")

    // Définition lignes
    maVue.listeLignes = creerLignes()

    // Textes
    maVue.listeTextes = creerTextes()

    }

    func creerLignes() -> [TypeLigne] {

    // Ligne Rouge
    let ligne1 = TypeLigne(point1: NSMakePoint(0,0),
    point2: NSMakePoint(100,100),
    width: 3.0,
    couleur: NSColor.red)

    // Ligne Bleu
    let ligne2 = TypeLigne(point1: NSMakePoint(0, 0),
    point2: NSMakePoint(100, 150),
    width: 3.0,
    couleur: NSColor.blue)

    // Stockage des lignes dans un tableau
    var tableauLignes = [TypeLigne]()
    tableauLignes.append(ligne1)
    tableauLignes.append(ligne2)

    return tableauLignes

    }

    func creerTextes() -> [TypeTexte] {
    let t1 = TypeTexte(point: NSMakePoint(100, 100),
    text: NSAttributedString(string:"San Fransisco"))
    let t2 = TypeTexte(point: NSMakePoint(50, 150),
    text: NSAttributedString(string:"Boston"))
    var tableau = [TypeTexte]()
    tableau.append(t1)
    tableau.append(t2)
    return tableau
    }

    override var representedObject: Any? {
    didSet {
    // Update the view, if already loaded.
    }
    }


    }


    Pour paramétrer les textes (font, taille, couleur) il faut entrer dans les détails des NSAttributedString. Pas le temps ce soir. J'ai rendez-vous avec un Arrow Saison 5 sur Netflix (épisode S5x08 avec des envahisseurs aliens. C'est un super crossover de la chaine The CW faisant intervenir les personnages de ses 4 séries de supers héros - Arrow, Flash, SuperGirl et Legends of Tomorrow)


  • Il n'y a pas de solution simple pour faire une copie d'écran d'une NSView. Voici un lien avec quelques lignes de code pour y arriver :


     


    https://pastebin.com/d4puXBi8

  • Variante avec définition des textes incluant :


    - position


    - String


    - Font


    - couleur d'affichage


     


    Le système génère un NSAttributedString à  la volée lors de l'affichage.



    import Foundation
    import Cocoa

    struct TypeLigne {
    var point1 : NSPoint
    var point2 : NSPoint
    var width : CGFloat
    var couleur : NSColor
    }

    struct TypeTexte {
    var point : NSPoint
    var text : String
    var police : NSFont
    var couleur : NSColor
    }

    class ViewAvecCalque : NSView {
    var image : NSImage?
    var listeLignes : [TypeLigne]?
    var listeTextes : [TypeTexte]?

    override func draw(_ dirtyRect: NSRect) {
    let rect = dirtyRect

    // Si l'image existe on la dessine sur la vue
    if let image = image {
    image.draw(in: rect)
    }

    // Si les lignes existent on les dessines sur la vue
    if let lesLignes = listeLignes {
    for ligne in lesLignes {
    let path = NSBezierPath()
    ligne.couleur.set()
    path.move(to: ligne.point1)
    path.line(to: ligne.point2)
    path.lineWidth = ligne.width
    path.stroke()
    }
    }

    // Si les textes existent, on les dessines
    if let lesTextes = listeTextes {
    for texte in lesTextes {
    let attrTexte = NSAttributedString(
    string: texte.text,
    attributes: [
    NSFontAttributeName : texte.police,
    NSForegroundColorAttributeName : texte.couleur
    ])
    attrTexte.draw(at: texte.point)
    }
    }

    }

    }


    Mise en application :



    import Foundation
    import Cocoa

    class ViewController: NSViewController {

    var maVue = ViewAvecCalque()

    override func viewDidLoad() {
    super.viewDidLoad()

    // Création de la vue
    maVue.frame = CGRect(x: 100, y: 40, width: 200, height: 200)
    self.view.addSubview(maVue)

    // Définition image
    maVue.image = NSImage.init(named: "BlueSky")

    // Définition lignes
    maVue.listeLignes = creerLignes()

    // Textes
    maVue.listeTextes = creerTextes()

    }

    func creerLignes() -> [TypeLigne] {

    // Ligne Rouge
    let ligne1 = TypeLigne(point1: NSMakePoint(0,0),
    point2: NSMakePoint(100,100),
    width: 3.0,
    couleur: NSColor.red)

    // Ligne Bleu
    let ligne2 = TypeLigne(point1: NSMakePoint(0, 0),
    point2: NSMakePoint(100, 150),
    width: 3.0,
    couleur: NSColor.blue)

    // Stockage des lignes dans un tableau
    var tableauLignes = [TypeLigne]()
    tableauLignes.append(ligne1)
    tableauLignes.append(ligne2)

    return tableauLignes

    }

    func creerTextes() -> [TypeTexte] {

    let t1 = TypeTexte(point: NSMakePoint(50, 150),
    text: "San Fransisco",
    police: NSFont.boldSystemFont(ofSize: 17.0),
    couleur: NSColor.blue)

    let t2 = TypeTexte(point: NSMakePoint(100, 100),
    text: "Boston",
    police: NSFont.systemFont(ofSize: 15.0),
    couleur: NSColor.red)

    var tableau = [TypeTexte]()
    tableau.append(t1)
    tableau.append(t2)
    return tableau
    }

    override var representedObject: Any? {
    didSet {
    // Update the view, if already loaded.
    }
    }

    }


  • Petit détail : Je n'ai pas vérifié, mais je présume qu'en cas de modifications des données, il faut utiliser needsDisplay pour dire à  la NSView que son contenu n'est plus valable, et qu'elle doit se redessiner.

  • Merci, je vais pouvoir étudier tout cela et le mettre en application.


     


    A+


  • Au lieu de recalculer à  chaque mise à  jour les NSBezierPath à  dessiner il peut être plus simple de les calculer une fois pour toutes et de faire un simple [NSBezierPath store] à  chaque mise à  jour. D'autant plus que les paramètres de dessin épaisseur (lineWidth), type de raccord (lineCapStyle,lineJoinStylemiterLimit etc) et pointillé (LineDash) peuvent rester associé au NSBezierPath mais pas la couleur !


    En plus NSBezierPath conforms to NSCoding


    donc sauvegarde et lecture automatiques.


  • Il fallait lire [NSBezierPath stoke] et non pas [NSBezierPath store] bien sûr ! (maudit correcteur)


  • CéroceCéroce Membre, Modérateur
    juillet 2017 modifié #12

    Tu voulais dire -[NSBezierPath stroke], non ?  ;-)


  • Magnifique le tuto, j'ai déjà  inséré la plus part de mes lignes dans mes graphs mais comme je ne connais pas le nombre peut différé pour chaque graph, je suis obliger de modifier un peu le code pour automatiser la chose. J'avance de plus en plus dans ce logiciel et ce n'est que le début.


     


    Merci à  tous, allez j'y retourne.




  • Au lieu de recalculer à  chaque mise à  jour les NSBezierPath à  dessiner il peut être plus simple de les calculer une fois pour toutes et de faire un simple [NSBezierPath store] à  chaque mise à  jour. D'autant plus que les paramètres de dessin épaisseur (lineWidth), type de raccord (lineCapStyle,lineJoinStylemiterLimit etc) et pointillé (LineDash) peuvent rester associé au NSBezierPath mais pas la couleur !


    En plus NSBezierPath conforms to NSCoding


    donc sauvegarde et lecture automatiques.




    Oui, c'est largement perfectible avec différentes optimisations. On peut aussi modifier le système de positionnement dans la vue, utilisant des coordonnées exprimés en pourcentage, plutôt qu'en valeurs absolue, de manière à  permettre le redimensionnement de l'image. Quoi que c'est peut-être moins utile sous macOS que sous iOS.



    // Point à  20% de la largeur de l'image
    // et 75% de sa hauteur
    let point = NSMakePoint(0.2,0.75) 
  • j'ai une question toujours sur le positionnement de mes lignes verticales, voilà  :


     


    j'ai 3 imageViews l'une au-dessus de l'autre, de même dimension contenant les graphiques et bien entendu mes lignes ne vont pas toutes dans la même imageView. Comment faire ?


     


    Une idée


     


    Je pose la question non sans avoir tenté quelque chose, évidemment 


  • Une superposition de 3 imagesview  ??? ? Tu n'as pas peur que le contenu de la première soit totalement caché par les deux autres ?


     


    C'est difficile de te conseiller sans en savoir davantage sur ton application. Les lignes et les textes d'une imageview peuvent-ils être partiellement effacés par les vues situées plus haut, ou sont-elles prioritaires sur les graphismes ?


     


    Tu as trois architectures possibles :


     


    - a/ le sandwich


     


       - Fond d'écran


        - imageView1


        - VuePersonnalisée1 pour les lignes et les textes de l'image 1


        - imageView2


        - VuePersonnalisée2 pour les lignes et les textes de l'image 2


        - imageView3


       - VuePersonnalisée3 pour les lignes et les textes de l'image 3


     


    Inconvénient : les lignes et les textes d'une couche peuvent être recouverts par une autre


     


    - b/ la pile d'assiettes


     


       - Fond d'écran


       - imageView1


       - imageView2


       - imageView3


       - VuePersonnalisée1 pour les lignes et les textes de l'image 1


       - VuePersonnalisée2 pour les lignes et les textes de l'image 2


       - VuePersonnalisée3 pour les lignes et les textes de l'image 3


     


    - c/ Le couvercle


     


       - Fond d'écran


       - imageView1


       - imageView2


       - imageView3


       - VuePersonnalisée regroupant TOUTES  les illustrations ( lignes et les textes) 

  • J'ai réfléchi et je crois que je vais faire une seule imageView regroupant les trois graphiques et positionner mes lignes verticales en changeant l'origine des y à  volonté.


    Ce sera surement plus simple mais encore faut-il que j'arrive à  le faire  :*   :*


    mais bon.


     


    Je vous tiens au courant.


     


    Merci


  • DrakenDraken Membre
    juillet 2017 modifié #18


    J'ai réfléchi et je crois que je vais faire une seule imageView regroupant les trois graphiques et positionner mes lignes verticales en changeant l'origine des y à  volonté.




     


    J'ai cru comprendre que toutes tes images avaient la taille. Et maintenant tu parles de changer la position des lignes en modifiant l'origine des y. Est-ce que tu fabrique tes images en assemblant verticalement des graphismes, dans ce style ?


     


    ++++++++++++++++++++++++++


    +   Image 1 : Haut du graphisme  +


    ++++++++++++++++++++++++++


    +  Image 2 : Milieu du graphisme +


    ++++++++++++++++++++++++++


    +  Image 3 : Bas du graphisme     +


    ++++++++++++++++++++++++++


     


    Avec des éléments graphique de base :


     


    ++++++++++++++++++++++++++


    +   Image 1 : Haut du graphisme  +


    ++++++++++++++++++++++++++


    +                                                    +


    +             TRANSPARENCE           +


    +                                                    +


    ++++++++++++++++++++++++++


     

    ++++++++++++++++++++++++++

    +           TRANSPARENCE             +


    ++++++++++++++++++++++++++


    +  Image 2 : Milieu du graphisme +


    ++++++++++++++++++++++++++


    +            TRANSPARENCE           +


    ++++++++++++++++++++++++++


     

    ++++++++++++++++++++++++++

    +                                                    +


    +            TRANSPARENCE            +


    +                                                    +


    ++++++++++++++++++++++++++


    +  Image 3 : Bas du graphisme     +


    ++++++++++++++++++++++++++


     


    EDIT : Ah zut, ce n'est pas facile d'aligner les lettres pour dessiner des cadres de même taille, le forum n'utilisant pas la même police pour l'édition et l'affichage des posts terminés. Enfin, tant que cela reste compréhensible ..


  • En fait, j'ai 1 graph avec une tranche horaire de 8:00 à  11:00


    en dessous 1 graph avec la tranche horaire de 11:00 à  14:00


    et en dessous 1 graph avec la tranche horaire de 14:00 à  17:45


    donc les lignes dont l'horaire est compris entre 8:00 et 11:00 vont s'afficher dans le 1er graph,


    les lignes dont l'horaire est compris entre 11:00 et 14:00 vont s'afficher dans le 2ème graph,


    les lignes dont l'horaire est compris entre 14:00 et 17:45 vont s'afficher dans le 3ème graph.

     

    Je sais c'est un peu compliqué à  faire, j'ai déclaré dans Main StoryBoard les 3 imageViews et créé les outlets correspondants.

     

    Je n'arrive pas à  adapter ton code, il est pourtant clair mais que pour 1 seule image.

  • Bin non, c'est facile à  faire. Il suffit d'utiliser Storyboard pour placer une vue personnalisée au-dessus de chaque imageView, et récupérer 3 nouveaux outlets. Ce n'est pas bien loin à  faire. Je t'explique ça demain.

  • PatyomPatyom Membre
    juillet 2017 modifié #21

    Bonjour,


    Petite remarque, j'arrive bien à  créer mon graphique avec les bonnes ligne verticales dans le Rect mais ce que je ne sais pas faire c'est recopier la totalité de ce qu'il y a dans le Rect (graph + lignes + textes) dans mon imageView prédéfinie dans le Main Storyboard.


     


    Je fais :



    // Graph01 c'est mon imageView
    Graph01.image = maVue.image

    mais bien entendu il ne copie que l'image.


    il faut que j'ajoute la couche avec les lignes et celle avec les textes.


    Là , je ne sais pas faire


  • Une version légèrement modifiée de la classe ne gérant plus d'image. Tu n'en a pas besoin, si tu utilises une imageView en fond d'écran.



    import Foundation
    import Cocoa

    struct TypeLigne {
    var point1 : NSPoint
    var point2 : NSPoint
    var width : CGFloat
    var couleur : NSColor
    }

    struct TypeTexte {
    var point : NSPoint
    var text : String
    var police : NSFont
    var couleur : NSColor
    }

    class ViewAvecCalque : NSView {
    var listeLignes : [TypeLigne]?
    var listeTextes : [TypeTexte]?

    override func draw(_ dirtyRect: NSRect) {

    // Si les lignes existent on les dessines sur la vue
    if let lesLignes = listeLignes {
    for ligne in lesLignes {
    let path = NSBezierPath()
    ligne.couleur.set()
    path.move(to: ligne.point1)
    path.line(to: ligne.point2)
    path.lineWidth = ligne.width
    path.stroke()
    }
    }

    // Si les textes existent, on les dessines
    if let lesTextes = listeTextes {
    for texte in lesTextes {
    let attrTexte = NSAttributedString(
    string: texte.text,
    attributes: [
    NSFontAttributeName : texte.police,
    NSForegroundColorAttributeName : texte.couleur
    ])
    attrTexte.draw(at: texte.point)
    }
    }

    }

    }
     

    Mise en oeuvre :


     


    J'ai utilisé Storyboard pour créer 3 imagesView et 3 ViewAvecCalque. Chaque imageView est recouverte d'une ViewAvecCalque. J'ai défini un jeu de contraintes de sorte que chaque ViewAvecCalque possède les mêmes dimensions et la même position que la vue qu'elle recouvre. 


     


    Si tu ne sais pas te servir de Storyboard pour créer graphiquement des vues personnalisées, ou créer des contraintes pour cloner une caractéristique graphique d'un objet (taille et dimension) dis-le je t'expliquerait.


     


    Pour en revenir au sujet, j'ai 3 liens (IBOutlet) vers les imagesView et 3 vers les Calques. Il suffit ensuite d'envoyer les bonnes données sur les différents calques, pour dessiner les lignes et les textes.



    import Foundation
    import Cocoa

    class ViewController: NSViewController {

    @IBOutlet weak var imageView1: NSImageView!
    @IBOutlet weak var imageView2: NSImageView!
    @IBOutlet weak var imageView3: NSImageView!

    @IBOutlet weak var vueCalque1: ViewAvecCalque!
    @IBOutlet weak var vueCalque2: ViewAvecCalque!
    @IBOutlet weak var vueCalque3: ViewAvecCalque!

    override func viewDidLoad() {
    super.viewDidLoad()

    imageView1.image = NSImage.init(named: "BlueSky")
    imageView2.image = NSImage.init(named: "textureParchemin1")
    imageView3.image = NSImage.init(named: "tileVert")

    // Lignes
    vueCalque1.listeLignes = creerLignes()
    vueCalque2.listeLignes = creerLignes()
    vueCalque3.listeLignes = creerLignes()

    // Textes
    vueCalque1.listeTextes = creerTextes1()
    vueCalque2.listeTextes = creerTextes2()
    vueCalque3.listeTextes = creerTextes3()


    }

    func creerLignes() -> [TypeLigne] {

    // Ligne Rouge
    let ligne1 = TypeLigne(point1: NSMakePoint(0,0),
    point2: NSMakePoint(100,30),
    width: 3.0,
    couleur: NSColor.red)

    // Ligne Bleu
    let ligne2 = TypeLigne(point1: NSMakePoint(0, 0),
    point2: NSMakePoint(180, 40),
    width: 3.0,
    couleur: NSColor.blue)

    // Stockage des lignes dans un tableau
    var tableauLignes = [TypeLigne]()
    tableauLignes.append(ligne1)
    tableauLignes.append(ligne2)

    return tableauLignes

    }

    func creerTextes1() -> [TypeTexte] {

    let t1 = TypeTexte(point: NSMakePoint(15, 40),
    text: "VuePersonnalisée 1",
    police: NSFont.boldSystemFont(ofSize: 17.0),
    couleur: NSColor.blue)

    var tableau = [TypeTexte]()
    tableau.append(t1)
    return tableau
    }

    func creerTextes2() -> [TypeTexte] {

    let t1 = TypeTexte(point: NSMakePoint(15, 40),
    text: "VuePersonnalisée 2",
    police: NSFont.boldSystemFont(ofSize: 17.0),
    couleur: NSColor.red)

    var tableau = [TypeTexte]()
    tableau.append(t1)
    return tableau
    }

    func creerTextes3() -> [TypeTexte] {

    let t1 = TypeTexte(point: NSMakePoint(15, 40),
    text: "VuePersonnalisée 3",
    police: NSFont.boldSystemFont(ofSize: 17.0),
    couleur: NSColor.black)

    var tableau = [TypeTexte]()
    tableau.append(t1)
    return tableau
    }

    override var representedObject: Any? {
    didSet {
    // Update the view, if already loaded.
    }
    }

    }


  • Ton approche est assez compliqué, avec les 3 imagesView.


    Si j'avais eu à  faire la même chose, j'aurais viré toutes les imagesView pour n'utiliser qu'une vue personnalisée UNIQUE s'occupant d'assembler les images de fond et de l'affichage des lignes et textes.

  • Joanna CarterJoanna Carter Membre, Modérateur


    Si j'avais eu à  faire la même chose, j'aurais viré toutes les imagesView pour n'utiliser qu'une vue personnalisée UNIQUE s'occupant d'assembler les images de fond et de l'affichage des lignes et textes.




     


    C'est beaucoup mieux comme ça

  • PatyomPatyom Membre
    juillet 2017 modifié #25

    Bien, alors pouvez-vous m'expliquer comment regrouper 3 images dans une seule imageView ou Rect ?


  • La solution est déjà  présente dans l'une des premières versions de ViewAvecCalque :



    import Foundation
    import Cocoa

    struct TypeLigne {
    var point1 : NSPoint
    var point2 : NSPoint
    var width : CGFloat
    var couleur : NSColor
    }

    struct TypeTexte {
    var point : NSPoint
    var text : NSAttributedString
    }

    class ViewAvecCalque : NSView {
    var image : NSImage?
    var listeLignes : [TypeLigne]?
    var listeTextes : [TypeTexte]?

    override func draw(_ dirtyRect: NSRect) {
    let rect = dirtyRect

    // Si l'image existe on la dessine sur la vue
    if let image = image {
    image.draw(in: rect)
    }

    // Si les lignes existent on les dessines sur la vue
    if let lesLignes = listeLignes {
    for ligne in lesLignes {
    let path = NSBezierPath()
    ligne.couleur.set()
    path.move(to: ligne.point1)
    path.line(to: ligne.point2)
    path.lineWidth = ligne.width
    path.stroke()
    }
    }

    // Si les textes existent, on les dessines
    if let lesTextes = listeTextes {
    for texte in lesTextes {
    texte.text.draw(at: texte.point)
    }
    }

    }

    }

    Plus précisément :



    override func draw(_ dirtyRect: NSRect) {
    let rect = dirtyRect

    // Si l'image existe on la dessine sur la vue
    if let image = image {
    image.draw(in: rect)
    }


    L'image est affichée sur la totalité du rectangle de la vue personnalisée. En modifiant le rectangle d'affichage, on peut donner n'importe taille ou position à  l'image.


     


    Nouvelle version de la fonction utilisant cette technique pour afficher les 3 images dans la Vue Personnalisée.



    class ViewAvecCalque : NSView {
    var listeLignes : [TypeLigne]?
    var listeTextes : [TypeTexte]?
    var image1 : NSImage?
    var image2 : NSImage?
    var image3 : NSImage?

    override func draw(_ dirtyRect: NSRect) {
    let rect = dirtyRect

    // Si l'image 1 existe on la dessine sur la vue
    // Bande supérieure de la vue
    if let image1 = image1 {
    let rectAffichage = CGRect(x: 0,
    y: 0,
    width: rect.width,
    height: rect.height/3.0)
    image1.draw(in: rectAffichage)
    }

    // bande du milieude la vue
    if let image2 = image2 {
    let rectAffichage = CGRect(x: 0,
    y: rect.height/3.0,
    width: rect.width,
    height: rect.height/3.0)
    image2.draw(in: rectAffichage)
    }

    // bande du bas de la vue
    if let image3 = image3 {
    let rectAffichage = CGRect(x: 0,
    y: rect.height*2/3.0,
    width: rect.width,
    height: rect.height/3.0)
    image3.draw(in: rectAffichage)
    }


    // Si les lignes existent on les dessines sur la vue
    if let lesLignes = listeLignes {
    for ligne in lesLignes {
    let path = NSBezierPath()
    ligne.couleur.set()
    path.move(to: ligne.point1)
    path.line(to: ligne.point2)
    path.lineWidth = ligne.width
    path.stroke()
    }
    }

    // Si les textes existent, on les dessines
    if let lesTextes = listeTextes {
    for texte in lesTextes {
    let attrTexte = NSAttributedString(
    string: texte.text,
    attributes: [
    NSFontAttributeName : texte.police,
    NSForegroundColorAttributeName : texte.couleur
    ])
    attrTexte.draw(at: texte.point)
    }
    }

    }

    }


    EDIT : Bon, ça marche PRESQUE ! Il y a une inversion de l'ordre d'affichage des bandes. J'ai codé avec une logique iOS alors que l'orientation de l'axe y est inversé sur ce fichu macOS.. Je dois filer, je n'ai pas le temps de régler le problème. Ce n'est pas difficile à  faire. Tu devrais y arriver par toi-même.

  • PatyomPatyom Membre
    juillet 2017 modifié #27

    c'est bon, je suis arrivé à  édifier mon prog d'affichage images + lignes (textes pas encore, mais çà  va venir) et je vous remercie de vos conseils, j'ai eu un peu de mal avec les horaires pour placer les lignes (pb avec les Floats et les minutes à  transformer) oui parce que les lignes verticales correspondaient à  des heures précises.


     


    Maintenant il faut que j'enregistre tout ce que j'ai empilé dans mon Rect global (j'ai opté pour le Rect global, c'est plus simple).


     


    là , non plus c'est pas gagné.


  • Tout frais, tout chaud :



    import Foundation
    import Cocoa

    struct TypeLigne {
    var point1 : NSPoint
    var point2 : NSPoint
    var width : CGFloat
    var couleur : NSColor
    }

    struct TypeTexte {
    var point : NSPoint
    var text : String
    var police : NSFont
    var couleur : NSColor
    }

    class ViewAvecCalque : NSView {
    var listeLignes1 : [TypeLigne]?
    var listeLignes2 : [TypeLigne]?
    var listeLignes3 : [TypeLigne]?
    var listeTextes1 : [TypeTexte]?
    var listeTextes2 : [TypeTexte]?
    var listeTextes3 : [TypeTexte]?
    var image1 : NSImage?
    var image2 : NSImage?
    var image3 : NSImage?

    override func draw(_ dirtyRect: NSRect) {

    func dessinerLignes(liste:[TypeLigne]?, offsetY:CGFloat) {
    guard let lesLignes = liste else { return }
    for ligne in lesLignes {
    // Ajustement des coordonnées à  partir de l'offset y
    let pointA = NSMakePoint(ligne.point1.x, ligne.point1.y + offsetY)
    let pointB = NSMakePoint(ligne.point2.x, ligne.point2.y + offsetY)
    let path = NSBezierPath()
    ligne.couleur.set()
    path.move(to: pointA)
    path.line(to: pointB)
    path.lineWidth = ligne.width
    path.stroke()
    }
    }

    func dessinerTextes(liste:[TypeTexte]?, offsetY:CGFloat) {
    guard let lesTextes = liste else { return }
    for texte in lesTextes {
    let attrTexte = NSAttributedString(
    string: texte.text,
    attributes: [
    NSFontAttributeName : texte.police,
    NSForegroundColorAttributeName : texte.couleur
    ])
    let position = NSMakePoint(texte.point.x, texte.point.y + offsetY)
    attrTexte.draw(at: position)
    }
    }

    let rect = dirtyRect

    // Calcul des zones graphiques
    let rect1 = CGRect(x: 0,
    y: rect.height*2/3.0,
    width: rect.width,
    height: rect.height/3.0)
    let rect2 = CGRect(x: 0,
    y: rect.height/3.0,
    width: rect.width,
    height: rect.height/3.0)
    let rect3 = CGRect(x: 0,
    y: 0,
    width: rect.width,
    height: rect.height/3.0)

    // Si l'image 1 existe on la dessine sur la vue
    // Bande supérieure de la vue
    if let image1 = image1 {
    image1.draw(in: rect1)
    }

    // bande du milieu de la vue
    if let image2 = image2 {
    image2.draw(in: rect2)
    }

    // bande du bas de la vue
    if let image3 = image3 {
    image3.draw(in: rect3)
    }

    // Si les lignes existent on les dessines sur la vue
    dessinerLignes(liste: listeLignes1, offsetY: rect1.origin.y)
    dessinerLignes(liste: listeLignes2, offsetY: rect2.origin.y)
    dessinerLignes(liste: listeLignes3, offsetY: rect3.origin.y)

    // Si les textes existent, on les dessines
    dessinerTextes(liste: listeTextes1, offsetY: rect1.origin.y)
    dessinerTextes(liste: listeTextes2, offsetY: rect2.origin.y)
    dessinerTextes(liste: listeTextes3, offsetY: rect3.origin.y)
    }
    }


    Utilisation :



    import Foundation
    import Cocoa

    class ViewController: NSViewController {

    @IBOutlet weak var vueCalque: ViewAvecCalque!

    override func viewDidLoad() {
    super.viewDidLoad()

    vueCalque.image1 = NSImage.init(named: "BlueSky")
    vueCalque.image2 = NSImage.init(named: "textureParchemin1")
    vueCalque.image3 = NSImage.init(named: "tileVert")

    // Lignes
    vueCalque.listeLignes1 = creerLignes()
    vueCalque.listeLignes2 = creerLignes()
    vueCalque.listeLignes3 = creerLignes()

    // Textes
    vueCalque.listeTextes1 = creerTextes1()
    vueCalque.listeTextes2 = creerTextes2()
    vueCalque.listeTextes3 = creerTextes3()

    }

    func creerLignes() -> [TypeLigne] {

    // Ligne Rouge
    let ligne1 = TypeLigne(point1: NSMakePoint(0,0),
    point2: NSMakePoint(100,30),
    width: 3.0,
    couleur: NSColor.red)

    // Ligne Bleu
    let ligne2 = TypeLigne(point1: NSMakePoint(0, 0),
    point2: NSMakePoint(180, 40),
    width: 3.0,
    couleur: NSColor.blue)

    // Stockage des lignes dans un tableau
    var tableauLignes = [TypeLigne]()
    tableauLignes.append(ligne1)
    tableauLignes.append(ligne2)

    return tableauLignes

    }

    func creerTextes1() -> [TypeTexte] {

    let t1 = TypeTexte(point: NSMakePoint(15, 40),
    text: "VuePersonnalisée 1",
    police: NSFont.boldSystemFont(ofSize: 17.0),
    couleur: NSColor.blue)

    var tableau = [TypeTexte]()
    tableau.append(t1)
    return tableau
    }

    func creerTextes2() -> [TypeTexte] {

    let t1 = TypeTexte(point: NSMakePoint(15, 40),
    text: "VuePersonnalisée 2",
    police: NSFont.boldSystemFont(ofSize: 17.0),
    couleur: NSColor.red)

    var tableau = [TypeTexte]()
    tableau.append(t1)
    return tableau
    }

    func creerTextes3() -> [TypeTexte] {

    let t1 = TypeTexte(point: NSMakePoint(15, 40),
    text: "VuePersonnalisée 3",
    police: NSFont.boldSystemFont(ofSize: 17.0),
    couleur: NSColor.black)

    var tableau = [TypeTexte]()
    tableau.append(t1)
    return tableau
    }

    override var representedObject: Any? {
    didSet {
    // Update the view, if already loaded.
    }
    }
    }


  • Joanna CarterJoanna Carter Membre, Modérateur

    Et juste pour te montrer qu'il n'est pas facile à  dessiner, voici du code d'un ancien projet (oui, c'est ObjC  8--) ) qui montre comment séparer le dessin des éléments pour le faire plus facile à  lire



    @interface AFMCentreOfGravityEnvelopeDrawingVisitor ()

    @property (weak, nonatomic) AFMCentreOfGravityEnvelope *centreOfGravityEnvelope;

    @property (weak, nonatomic, readonly) AFMWeightUnit *weightUnit;


    @property (nonatomic, readonly) NSUInteger minimumAircraftWeight;

    @property (nonatomic, readonly) NSUInteger maximumAircraftWeight;

    @property (nonatomic, readonly) CGFloat minimumCoG;

    @property (nonatomic, readonly) CGFloat maximumCoG;


    @property (nonatomic, readonly) CGFloat leftLegendMargin;

    @property (nonatomic, readonly) CGFloat bottomLegendMargin;

    @property (nonatomic, readonly) CGFloat outerMargin;


    @property (nonatomic, readonly) CGFloat xAxisZero;

    @property (nonatomic, readonly) CGFloat xAxisMaximum;

    @property (nonatomic, readonly) CGFloat yAxisZero;

    @property (nonatomic, readonly) CGFloat yAxisMaximum;


    @property (copy, nonatomic, readonly) NSString *xAxisLabelText;

    @property (copy, nonatomic, readonly) NSString *yAxisLabelText;

    @property (nonatomic, readonly) CGFloat xAxisMajorTickY;

    @property (nonatomic, readonly) CGFloat xAxisMinorTickY;

    @property (nonatomic, readonly) CGFloat yAxisMajorTickX;

    @property (nonatomic, readonly) CGFloat yAxisMinorTickX;


    @property (nonatomic, readonly) CGPoint graphOrigin;

    @property (nonatomic, readonly) CGSize graphSize;

    @property (nonatomic, readonly) CGRect graphBounds;


    @property (nonatomic, readonly) CGFloat xAxisScaleFactor;

    @property (nonatomic, readonly) CGFloat yAxisScaleFactor;


    - (void)drawHorizontalGridLineForWeight:(CGFloat)weight;

    - (void)drawVerticalGridLineForCoG:(CGFloat)coG;

    - (void)drawGridLines;


    - (void)drawXMajorTickMarkForCoG:(CGFloat)coG;

    - (void)drawXMajorTickMarkLabelForCoG:(CGFloat)coG;

    - (void)drawXMinorTickMarkForCoG:(CGFloat)coG;

    - (void)drawXTickMarks;

    - (void)drawXAxisLine;


    - (void)drawYMajorTickMarkForWeight:(CGFloat)weight;

    - (void)drawYMajorTickMarkLabelForWeight:(CGFloat)weight;

    - (void)drawYMinorTickMarkForWeight:(CGFloat)weight;

    - (void)drawYTickMarks;

    - (void)drawYAxisLine;


    - (void)drawXAxisLabel;

    - (void)drawYAxisLabel;


    - (void)drawAxes;


    - (void)drawTitle;

    @property (strong, nonatomic, readonly) NSDictionary *text12Attributes;

    @property (strong, nonatomic, readonly) NSDictionary *text14Attributes;

    @property (strong, nonatomic, readonly) NSDictionary *text14BoldAttributes;

    @end
  • * hurle d'horreur avant de s'évanouir ! *

  • Joanna CarterJoanna Carter Membre, Modérateur

    Je peux te montrer plus  8--)


Connectez-vous ou Inscrivez-vous pour répondre.