Mixer Obj-C et Swift

LeChatNoirLeChatNoir Membre, Modérateur

Bon, j'ai toujours pas le temps d'apprendre le swift mais vu que je vais commencer un nouveau module, je me demande si je m'y mettrai pas...


 


Donc ma question est simple. Je charge mes VC (ViewController) à  l'ancienne depuis des xib...


 


Depuis un VC objective-C, est ce que je peux instancier et "pusher" un VC écrit en swift... ?


 Si oui, comment ?


 


Si non, je suis prêt à  utiliser Storyboard comme il se doit mais même dans ce cas, je vais devoir passer des variables à  mon nouveau VC dans le prepareForSegue et donc, comment le faire ?


 


Merci !


 


 


 


 


 


«1

Réponses

  • zoczoc Membre
    août 2016 modifié #2

    ObjC et Swift sont interopérables à  100%. Swift supporte 100% des API existantes en ObjC. Donc Oui, tu peux charger les view controllers écrits en Swift à  partir de VC écrits en ObjC, et inversement. Et ils peuvent toujours manipuler des xib.


     


    Xcode génèrera automatiquement un header ObjC correspondant à  la classe du VC écrit en Swift.


  • LeChatNoirLeChatNoir Membre, Modérateur

    Ok. J'ai effectivement trouver des choses en ce sens... Par contre, depuis Swift 2, ça a l'air différent.


     


    J'ai ma classe swift qui doit déclarer qu'elle est compatible obj-C via @objc.


     


    Or avec swift 2, visiblement, c'est forcément une classe qui hérite de NSObject.


     


    Donc quand je veux déclarer ma classe Swift en faisant :



    @objc class PhotoVerticalVC: UIViewController {


    Je me prend une erreur compilo qui dit :


     


    "Only classes that inherit from NSObject can be declared @objc"


     


    Du coup, je fais comment ?

  • LeChatNoirLeChatNoir Membre, Modérateur

    Oups... Ok, j'avais pas tout lu... Il me dit dans un même temps qu'il ne connait pas UIViewController...


     


    Donc un simple import résout le pb...



    import UIKit

    :)


     


    Je continue la découverte... Et merci Zoc pour tes encouragements  ;)


  • LeChatNoirLeChatNoir Membre, Modérateur

    Ok, donc voilà  les étapes à  dérouler :


     


    1 - créer sa classe Swift (un seul fichier, plus de .h, perturbant pour un vieux comme moi :))


    2 - cette classe doit contenir la directive @objc pour pouvoir être utilisée depuis une classe objective-C



    @objc class MaClasse : type

    3 - le type doit hériter de NSObject. Si c'est un objet provenant d'UIKit, bien importer UIKit



    import UIKit

    @objc class CAPhotoVerticalViewer: UIViewController {

    }

    4 - dans sa classe objective-C, on peut alors instancier son objet :



    CAPhotoVerticalViewer * myViewController=...

    5 - on va avoir alors une erreur indiquant qu'il connait pas cette classe. Il faut vérifier les paramètres suivants dans les build settings de sa target :


    • Embedded Content Contains Swift : YES
    • Install Objective-C Compatibility Header : YES

    Et importer un header magique dans le .m où on veut utiliser la classe swift. Ce header magique, on va trouver son nom dans les build settings aussi à  la rubrique "Objective-C Generated Interface Header Name"


    Un petit clean & rebuild et ça fonctionne 

  • Etape 0: Faire attention aux nommages de toutes tes classes swift parce que dans ce cas tu vas retomber dans l'espace de nommage Objective-C pour toutes tes classes swift et pas seulement celles que tu utilises en Objective-C.


     


    En tous cas moi, je n'ai pas trouvé de moyen d'exclure des classes swift du "header magique".


  • Joanna CarterJoanna Carter Membre, Modérateur
    août 2016 modifié #7

    à‰tape 2 n'est pas nécessaire si la classe dérive d'une classe qui dérive elle-même de NSObject.


     


    Tu aurais pu créer un fichier dans Xcode en cliquant sur File | New | File... , choisissant Cocoa Touch Class - Next - Subclass... UIViewController - Language: Swift - et diff, paf, pouf, ça y est !  Même avec le import UIKit déjà  là   8--)


     


    Et, comme dit FKDEV, ne mets pas les préfixes avant les noms des classes en Swift ; c'est pas nécessaire parce que tous les type Swift appartient du namespace du mode où ils se trouvent. Dans le cas d'un projet MonApp, le type Swift ViewController serait nommé, selon le compilateur, MonApp.ViewController


  • LeChatNoirLeChatNoir Membre, Modérateur

    Tout à  fait. En fait, j'avais timidement créé un "single swift file" + une UI...


     


    Effectivement, en passant par CocoaTouch, c'est mieux :)


     


    Pour les préfixes, je ne comprens pas trop ce que vous dites... ???

  • Joanna CarterJoanna Carter Membre, Modérateur
    août 2016 modifié #9

    La raison qu'il nous fallait mettre les préfixes avant les noms des types était pour éviter les conflits entre nos propres types et ceux des types d'autres APIs.


    En Swift, tous les types sont automatiquement fait partie du module où ils sont écrits. Du coup, c'est pas nécessaire pour toi d'utiliser CA comme préfixe , car le nom sera déjà  préfixé avec (e.g.) MyLibrary ou MyApp.


    Si quelqu'un écrivait un type avec le même nom, il serait préfixé par TheirLibrary ou TheirApp.


     


    Je viens de faire les expériences avec les types qui représentent les mesures et les unités de mesure. Or, il existe déjà  les types similaires avec les mêmes noms dans le framework Foundation. Pour éviter les conflits de noms, il ne faut qu'écrire mes types comme :



    let m = MyFramework.Measurement<LengthUnit>(value: 1000.0, unit:.meters)

  • FKDEVFKDEV Membre
    août 2016 modifié #10


    Et, comme dit FKDEV, ne mets pas les préfixes avant les noms des classes en Swift ; c'est pas nécessaire parce que tous les type Swift appartient du namespace du mode où ils se trouvent. Dans le cas d'un projet MonApp, le type Swift ViewController serait nommé, selon le compilateur, MonApp.ViewController




     


    En fait j'ai dit le contraire. :)


    C'est vrai que dans Swift, on n'a plus besoin de préfixer les nom de classes.


    Mais, d'après mon expérience, quand on utilise une classe swift en Objective-C on retombe dans le problème.


     


    Par exemple dans un projet swift j'ai créée une classe nommé MusicTrack.


    Puis un jour, j'ai voulu utiliser une classe objective-C alors j'ai suivi la même procédure que celle décrite par LeChatNoir.


    Et là  je me suis retrouvé avec un conflit de nom avec la classe MusicTrack définie dans le framework Apple "AudioToolbox".


     


     


    Bon, comme je suis un vieux briscard, j'ai trouvé le contournement suivant :



    #define MusicTrack MusicTrack1
    #import "mixlib-swift.h"

    ...

    Mais je ne sais pas s'il y a une meilleure solution.




  • En fait j'ai dit le contraire. :)


    C'est vrai que dans Swift, on n'a plus besoin de préfixer les nom de classes.


    Mais, d'après mon expérience, quand on utilise une classe swift en Objective-C on retombe dans le problème.


     


    Par exemple dans un projet swift j'ai créée une classe nommé MusicTrack.


    Puis un jour, j'ai voulu utiliser une classe objective-C alors j'ai suivi la même procédure que celle décrite par LeChatNoir.


    Et là  je me suis retrouvé avec un conflit de nom avec la classe MusicTrack définie dans le framework Apple "AudioToolbox".


     


     


    Bon, comme je suis un vieux briscard, j'ai trouvé le contournement suivant :



    #define MusicTrack MusicTrack1
    #import "mixlib-swift.h"
    #endif
    ...

    Mais je ne sais pas s'il y a une meilleure solution.




     


    As tu tenté de changer le nom de la classe vu par Objective C avec la directive @objc ?



    @objc(MyMusicTrask) class MusicTrask : NSObject { }


  •  


    As tu tenté de changer le nom de la classe vu par Objective C avec la directive @objc ?



    @objc(MyMusicTrask) class MusicTrask : NSObject { }



     


     


    Non car je ne connaissais pas cette méthode   (pourtant j'ai cherché sur stackoverflow).


     


    Par contre, cette classe est également utilisée dans realm et cela semble poser problème à  Realm si j'utilise cette méthode. 


     


    C'est peut-être juste une migration de schema à  faire mais comme cette classe est également utilisée dans des relations avec d'autres classes (playlist), je préfère ne pas rentrer là -dedans.

  • LeChatNoirLeChatNoir Membre, Modérateur
    août 2016 modifié #13

    Bon ben voilà , je me retrouve avec les questions de base de noob....



    NSArray toto=@[;@{@nom:@test1,@autreClé:@AutreValeur},@{dico suivant}];

    Comment je fais ça en swift ?


    :'(


  • Joanna CarterJoanna Carter Membre, Modérateur
    août 2016 modifié #14

    let toto = [["nom" : "test1", "autreClé" : "autreValeur"], [dico suivant]]

  • LeChatNoirLeChatNoir Membre, Modérateur

    Simple... Thank you ;)


  • LeChatNoirLeChatNoir Membre, Modérateur
    août 2016 modifié #16

    Bon... Je galère pas mal avec les types... Moi qui croyais que c'était plus simple en Swift...


     


    J'ai un tableau de dictionnaires issu de JSON. Seulement parfois j'ai en value des NSStrings, parfois des NSNumber.


     


    Donc en Swift, je voudrais bien lui dire : 



    var toto: [[String:Any]] = [];

     

    Mais si je mets ça, quand je veux récupérer une valeur, et que j'écris ça :

     



     let element=toto[indexPath.row]["height"];


     

    si je fais

     



      return CGFloat(element.floatValue)


     

    J'ai une erreur : value of type 'Any?' has no member 'floatValue'

     

    On fait comment ? :(

  • Tu peux peut être caster element en nsnumber avant d'accéder à  floatvalue.
  • Joanna CarterJoanna Carter Membre, Modérateur
    Le dictionnaire, il prevu à  stocker seulement les floats comme valeurs ? Si oui, tu devrais utiliser [String : float] ou [String : double]. Ce n'est plus nécessaire à  utiliser NSNumber.
  • LeChatNoirLeChatNoir Membre, Modérateur

    ben non... J'ai aussi des String:String...

  • LeChatNoirLeChatNoir Membre, Modérateur
    août 2016 modifié #20

    Et y a un truc dingue aussi.


     


    Si je mets :



        var toto: [[String:String]] = [];

     

    Depuis ma classe objective-C, je peux l'initialiser comme ça :

    monControllerSwift.toto=....;

     

    Si je mets 



        var toto: [[String:Any]] = [];


     

    Ca me dit : property 'toto' not found on object of type (monObjet obj-c).

     

    ?!

  • S'il y a un lien avec Objective C alors il vaut mieux (faut ?) utiliser AnyObject plutôt que Any qui permet de représenter en Swift plus que des instances d'objets.


  • Joanna CarterJoanna Carter Membre, Modérateur

    Si tu utilises NSDictionary en Objective-C, il faut utiliser [NSObject: AnyObject] en Swift.


  • LexxisLexxis Membre
    août 2016 modifié #23


     


    Bon... Je galère pas mal avec les types... Moi qui croyais que c'était plus simple en Swift...


     


    J'ai un tableau de dictionnaires issu de JSON. Seulement parfois j'ai en value des NSStrings, parfois des NSNumber.


     


    Donc en Swift, je voudrais bien lui dire : 



        var toto: [[String:Any]] = [];

     

    Mais si je mets ça, quand je veux récupérer une valeur, et que j'écris ça :



            let element=toto[indexPath.row]["height"];


     

    si je fais



    return CGFloat(element.floatValue)


    J'ai une erreur : value of type 'Any?' has no member 'floatValue'

     

    On fait comment ? :(

     





    if let element=toto[indexPath.row]["height"] as? NSNumber {
    ici element.floatValue est accessible
    }

  • Joanna CarterJoanna Carter Membre, Modérateur

    En plus, avec les nouveaux types génériques en Objective-C, tu pourrait utiliser NSDictionary<NSString *, NSObject *> *toto; pour mieux gérer les clés.

  • LeChatNoirLeChatNoir Membre, Modérateur
    août 2016 modifié #25

    Mes datas sont du JSON qui provient d'un webservice.


    En fait, j'ai réussi à  le faireavec AnyObject effectivement et en considérant que  mes chiffres étaient des strings.


     


    Ca donne ça :



        var toto: [[String:AnyObject]] = [];

     

    Et dans ma fonction heightForRow :

     



            let element=toto[indexPath.row]["height"] as? NSString;
            return CGFloat(element!.floatValue)



     


     Ca le fait.


    Merci à  vous 2 pour votre aide précieuse !


     


    C'est chaud le swift :)


  • MagiicMagiic Membre
    août 2016 modifié #26

    Ta dernière ligne force à  croire que element ne sera pas nil et qu'il peut donc accéder à  la propriété floatValue or tu n'en sais rien. C'est une mauvaise pratique en Swift. Tu dois tester ton element à  l'aide d'une condition ou d'un guard afin de l'utiliser avec l'assurance qu'il est bien un String.


  • Si tu n'es pas certain du type (NSNumber ou NSString), tu peux faire ça :
    protocol Floatable
    {
    var floatValue:Float {get}
    }
    extension NSString : Floatable
    {
    }
    extension NSNumber : Floatable
    {
    }
    Puis :
    if let element=toto[indexPath.row]["height"] as? Floatable {
    return CGFloat(element.floatValue)
    } else {
    print("je ne suis pas qui vous croyez \(element)")
    }
    Ou même un protocole qui renvoie directement un CGFloat.
  • LeChatNoirLeChatNoir Membre, Modérateur

    J'ai pu finaliser mon 1er view contrôleur swift :)


     


    Merci à  vous !


     

    o:)


  • Joanna CarterJoanna Carter Membre, Modérateur


    J'ai pu finaliser mon 1er view contrôleur swift :)




     


    Tu ne veux pas nous le montrer ?

  • Je donne mon avis un peu tard mais c'est pas la meilleure idée de produire du code Swift 2.x pour le moment...


    Une fois la 3 releasée tu pourras (presque) tout recommencer et réapprendre à  penser Swift. L'inter-opération entre Swift et Obj-C est différente dans Swift 3 et vu les SE proposées et acceptées ça va encore changer. 


     


    Exit AnyObject pour les types Obj-C, bonjour Any ! Les NSDictionnary typent <AnyHashable : Any>. Ce genre de choses... Donc soit tu attends la release soit tu passe à  la 3 beta et tu vas t'amuser à  chaque beta d'Xcode à  réadapter ton code.


     


    Bref c'est pas le bon moment pour commencer Swift.


  • LeChatNoirLeChatNoir Membre, Modérateur
    août 2016 modifié #31

    Oui. Seulement, je finalise encore un truc et je rencontre à  nouveau des difficultés :(


     


    J'ai un tableau défini comme suit :



        var mwPhotos=[MWPhoto]();

     

    Dans une méthode "delegate" du MWPhotoBrowser (un objet que je souhaite utiliser)

    je dois renvoyer la photo a un index précis :



      func photoBrowser(photoBrowser: MWPhotoBrowser!, photoAtIndex index: UInt) -> MWPhotoProtocol!
    {
    if Int(index) < self.bigPhotos.count
    {
          return self.bigPhotos[index] as! MWPhoto

           //return mwPhotos[index] as MWPhoto
        }
        
        return nil
      }


    Or ça, il en veut pas : 



      return self.bigPhotos[index] as! MWPhoto


    Il me dit : Cannot subscript a value of type [MWPhoto] with an index of type UIInt...

     

    Je comprends pas trop... Si je mets

     return self.bigPhotos[0] as! MWPhoto

    ça passe bien...
Connectez-vous ou Inscrivez-vous pour répondre.