Les NSValueTransformers et Swift, quelqu'un a testé ?

Je voudrais utiliser un NSValueTransformer dans un petit projet en Swift.


 


Le tranformer en question doit prendre un numéro en entrée (entre 0 et 11) et me retourner un NSString avec le nom du mois (indexé à  partir de zéro).


 


Comme je n'ai pour ainsi dire trouvé aucun exemple sur le net en swift, j'ai transposé les exemples en objective-C (je précise que je n'ai jamais manipulé de NSValueTransformer dans aucun des deux langages jusqu'à  aujourd'hui).


Bref, je l'ai déclaré comme ceci : 


 


class MoisTransformer: NSValueTransformer {


    override class func transformedValueClass() -> AnyClass!


    {


        return NSString.self


    }


    


    override func transformedValue(value: AnyObject!) -> AnyObject!


    {


        var nomDuMois:NSString = "";


(--- je vous épargne le switch sur les douze mois, l'essentiel ici, c'est que ça compile bien ---)


        return nomDuMois


    }


}


 


 


Puis je l'ai enregistré dans le App Delegate comme ceci :


 


    let leNomDuMois:MoisTransformer = MoisTransformer()


 


    func applicationDidFinishLaunching(aNotification: NSNotification?) {


        // Insert code here to initialize your application


        NSValueTransformer.setValueTransformer(leNomDuMois, forName:"MoisTransformer")


    }


 


Or quand j'essaye de l'appliquer sur un Text Field (via l'Interface Builder), j'ai cette erreur à  l'exécution :


 


*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot find value transformer with name MoisTransformer'


 


Est-ce que ça ne fonctionne pas encore, ou bien c'est moi qui ai raté une marche ?


 


Om Nom


Réponses

  • zoczoc Membre

    Au hasard : le XIB ne serait-il pas chargé avant l'appel de applicationDidFinishLaunching, et donc avant l'appel à  setValueTransformer ?


     


    Parce que moi de ce que je lis, il est conseillé d'appeler setValueTransformer dans la méthode (de classe) "+initialize" de la classe du dérivé de NSValueTransformer, ou, d'après le "Value Transformer Programming Guide" :


     


    Value transformers are typically registered by an application's delegate class, in response to receiving a initialize: class message. This allows registration to occur early in the application startup process, providing access to the value transformers as nib files load.

     


  • Merci de ces infos, c'est sans doute là  que réside le problème.


    En fait, si j'ai opté pour applicationDidFinishLaunching c'est parce que je n'ai pas trouvé de méthode appelée plus tôt dans mes classes Swift, donc j'ai fait confiance, sans doute un peu trop vite, au commentaire : // Insert code here to initialize your application


     


    Reste que je ne trouve pas d'équivalent au initialize dans les classes Swift...


     


    Peux-tu m'en dire plus sur une façon d'appeler setValueTransformer depuis la méthode de classe dérivée MoisTransformer ? Je ne sais pas quoi passer en premier paramètre : c'est supposé être une instance de NSValueTransformer, mais impossible d'utiliser self puisque je suis dans une méthode de classe.




  •  


    Reste que je ne trouve pas d'équivalent au initialize dans les classes Swift...


     




     


    Il n'y a pas d'équivalent effectivement, mais comme ton délégué d'application est dérivé d'une classe "Objective-C" tu peux mettre ta propre implémentation dans une fonction 



    class func initialize()
  • Om NomOm Nom Membre
    août 2014 modifié #5

    Argh... j'ai trouvé ! C'est moi qui ai fait mon gros boulet !  :*


     


    En fait, il manquait juste 


       @objc(MoisTransformer)


    avant la déclaration de ma classe...  :'(  


     


    Du coup, petit bilan : 


     


    A l'heure actuelle, si on le déclare dans applicationDidFinishLaunching, ça fonctionne contre toute attente. Mais étant donné le paragraphe du programming guide que soc a cité, c'est plus raisonnable de suivre les préconisations et de l'intégrer dans la méthode initialize de l'app delegate (merci à  jpimbert).


     


    Deux petites précisions complémentaire (pour ceux qui cherchent à  faire la même chose que moi). Comme il s'agit d'un override, il faut ajouter le mot-clé en Swift :


        class override func initialize() {


     

    et comme il s'agit d'une méthode de classe, l'instance du transformer ne peut pas être un attribut de la classe App Delegate car (à  l'heure actuelle, en tous cas) un attribut static en swift ne peut pas être une classe !


    Il faut donc déclarer l'instance du transformer hors de la classe (c'est moche mais ça marche).


     


    En tous cas, merci à  tous de votre aide !


     


    Om Nom


  • zoczoc Membre


    Argh... j'ai trouvé ! C'est moi qui ai fait mon gros boulet !  :*


     


    En fait, il manquait juste 


       @objc(MoisTransformer)


    avant la déclaration de ma classe...  :'(  


     




     


    Ca ne devrait même pas être nécessaire car ta classe dérive d'une classe Objective-C, et donc c'est implicite (ou alors ça a changé à  un moment donné au fil des bêtas et j'ai raté l'info...).

  • AliGatorAliGator Membre, Modérateur
    août 2014 modifié #7
    Il est à  noter que depuis je-ne-sais-plus-quelle-version (peut-être même depuis le début ?) on n'est plus obligé de faire un "setValueTransformer:forName:" si je ne m'abuse. En effet, quand dans IB ou ailleurs tu lui demandes d'utiliser un NSValueTransformer nommé "MoisTransformer", il va d'abord regarder en effet si tu en as enregistré un avec ce nom "MoisTransformer" via la méthode "setValueTransformer:forName:"... mais si tu ne l'as pas fait (s'il n'en trouve pas d'enregistré à  ce nom), il va alors, en solution de repli, regarder s'il n'y a pas une classe qui porte ce nom "MoisTransformer", et dans ce cas en créer une instance et l'enregistrer lui-même automatiquement, avant de l'utiliser.

    C'est expliqué dans la doc de la méthode "valueTransformerForName:" en fait : " If valueTransformerForName: does not find a registered transformer instance for name, it will attempt to find a class with the specified name. If a corresponding class is found an instance will be created and initialized using its init: method and then automatically registered with name. "
  • CéroceCéroce Membre, Modérateur
    Je confirme ce que tu écris Ali, il suffit de créer la sous-classe de NSValueTransformer, avec seulement les deux méthodes de conversion, et elle apparaà®t automatiquement dans l'éditeur de xib (même si ça contredit le Value Transformer Programming Guide).
  • iLandesiLandes Membre
    juillet 2015 modifié #9

    class MoisTransformer: NSValueTransformer {
        override class func transformedValueClass() -> AnyClass!
        {
            return NSString.self
        }
        
        override func transformedValue(value: AnyObject!) -> AnyObject!
        {
            var nomDuMois:NSString = "";
    (--- je vous épargne le switch sur les douze mois, l'essentiel ici, c'est que ça compile bien ---)
            return nomDuMois
        }
    }

    Je remonte un peu ce sujet car j'ai un peu galéré sur les NSValueTransformer.
     
    Pour fonctionner à  ce jour sur xCode 6.4 et sur Swift il faut transformer le code en
    @objc(MoisTransformer) class MoisTransformer: NSValueTransformer {
    override class func transformedValueClass() -> AnyClass
    {
    return NSString.self
    }

    override func transformedValue(value: AnyObject!) -> AnyObject?
    {
    var nomDuMois:NSString = "";
    // (--- je vous épargne le switch sur les douze mois, l'essentiel ici, c'est que ça compile bien ---)
    return nomDuMois
    }
    }
     
    Effectivement dans Interface Builder ça marche tout de suite !! Les changements ? & ! sont subtiles
     
     
    Par contre l'exemple donné ne me semble pas très intéressant. En effet je me suis cassé les dents sur les problèmes de date. Pour trouver le nom d'un mois dans une date, mieux faut se pencher sur NSDate et NSDateFormatter. Un bon casse tête mais qui permet d'avoir une version internationale tout de suite. Il n'est pas toujours facile de trouver la meilleure solution à  un problème au vu du nombre incalculable de classes disponible dans Cococa & co.
     
    Personnellement je me suis servi de NSValueTransformer pour transformer la valeur d'une note midi sous forme d'entier en une valeur sous forme de String. C'est tout simple les notes vont de 0 à  127, il y a 12 notes par octaves et une convention qui établis la valeur de l'octave de départ à  -2. Ainsi 1 donne C-2 ; 60 donne C3 ; 127 donne G8 etc.
     
    Comme ce site m'aide pour chacun de mes développements, voici le code que je vous offre. 
     
    Tous les commentaires sont les bienvenus...

    import Foundation
    @objc(IntegerNoteValueTransformer) class IntegerNoteValueTransformer: NSValueTransformer
    {
    // MARK: Constants
    struct Constants {
    static let NotesArray = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
    static let OctaviaOffSet = 2
    static let noteMin = 0
    static let noteMax = 127
    }

    // MARK: Class Functions
    override class func transformedValueClass() -> AnyClass {
    return NSString.self
    }

    override class func allowsReverseTransformation() -> Bool {
    return false
    }

    // MARK: Transformation
    override func transformedValue(value: AnyObject!) -> AnyObject? {
    if let integerValue = value as? Int {
    if integerValue >= Constants.noteMin && integerValue <= Constants.noteMax {
    let octavia = (integerValue / Constants.NotesArray.count) - Constants.OctaviaOffSet
    let stringNote = Constants.NotesArray [integerValue % Constants.NotesArray.count]
    return "\(stringNote)\(octavia)"
    }
    }
    return nil
    }
    }
  • Note aux admins : Je galère à  publier du code en swift correctement dans un message sur le forum


  • AliGatorAliGator Membre, Modérateur

    Note aux admins : Je galère à  publier du code en swift correctement dans un message sur le forum

    Il faut utiliser la balise CODE et non pas coller le texte en couleur directement.

    Si j'édite ton message d'origine je vois qu'il ressemble à  ceci :
  • AliGatorAliGator Membre, Modérateur
    Au passage la remarque dans le code d'origine de Om Nom me fait un peu peur en relisant :

    (--- je vous épargne le switch sur les douze mois, l'essentiel ici, c'est que ça compile bien ---)


    En effet comme tu le soulèves iLandes,

    Pour trouver le nom d'un mois dans une date, mieux faut se pencher sur NSDate et NSDateFormatter. Un bon casse tête mais qui permet d'avoir une version internationale tout de suite.

    Et je ne peux qu'appuyer cette remarque.

    C'est une mauvaise idée de lister soi-même les noms des mois, alors que NSDateFormatter et NSLocale non seulement savent déjà  faire ça pour toi, mais en plus le font bien mieux, en gérant déjà  toutes les langues, les cas particuliers auxquels tu n'as sans doute pas pensé (quid des cas dans différents fuseaux horaires quand es sur une date qui en France correspond à  1h30 le 1er Août mais correspond encore à  la date du 31 Juillet aux US, quid des gens (rares, certes, mais ils existent) qui n'utilisent pas le calendrier Grégorien " genre calendrier chinois ? etc...), le contexte, et les différentes formes (courtes, longues, etc)...
Connectez-vous ou Inscrivez-vous pour répondre.