Introduction à  CoreData - Swift 3

DrakenDraken Membre
mai 2017 modifié dans API UIKit #1


Je me souviens avoir réalisé un tuto il y a quelques temps déjà  une "Trésorerie" opérationnelle avec Core Data et Zéro ligne de code...


 


J'espérais retrouver la même chose....




Je suis en train de regarder le tutoriel de Sandy sur CoreData. Je ne connais pas le sujet, pourtant sa manière de faire me semble inutilement compliqué. Je comprend mieux ta réaction sur l'utilisation des !. J'ai  l'impression que l'auteure  écris du Swift en pensant Objective-C.


 


Je vais réécrire une partie du code de son exemple coreDataDemo. Tu verras la différence.


 


EDIT : CE TOPIC EST UN SPIN-OFF DE CELUI-CI :


 


http://forum.cocoacafe.fr/topic/15235-udemy-swift-3-on-en-parle/


 


Initialement la conversation parlait de l'usage des "!" pour dériver sur l'utilisation de CoreData, j'ai demandé à  la modération de découper les sujets en deux. 


«1

Réponses

  • DrakenDraken Membre
    mai 2017 modifié #2

    Chose promise, chose dus ..


     


    Je reprend l'exercice 64 du cours de Sandy, où elle montre les bases de CoreData.


     


    L'exercice traite de l'utilisation d'une entité Todo contenant une Date et un String. Pour lire le fichier, elle procède de la manière suivante :



    func lireTodos(completion:(_ array:NSArray)->()) {

    let requete:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Todo")
    var todosArray:[NSDictionary] = [NSDictionary]()

    do {
    let resultats = try context?.fetch(requete)

    for resultat in resultats! {

    let todo = (resultat as! NSManagedObject).value(forKey: "texte") as? String
    let date = (resultat as! NSManagedObject).value(forKey: "date") as? Date

    let todoExistant = ["todo": todo!, "date": date!] as [String : Any]

    todosArray.append(todoExistant as NSDictionary)
    }

    completion(todosArray as NSArray)

    } catch {
    print("erreur requete CoreData")
    }

    }


    Il y a beaucoup trop d'objective-C dans ce code (NSArray, NSDictionnary, [String:Any]), et  vraiment trop de !. Quand à  la manière de retourner l'information sous forme d'un bloc avec  un NSArray dans une complétion, je trouve ça carrément étrange.


     


    Voici ma version du même code, lisant le même fichier CoreData :



    // Utilisation d'un tupple (un regroupement de données)
    typealias TuppleTodo = (date:Date, texte:String)

    func lireTodos() -> [TuppleTodo] {

    let requete = NSFetchRequest<NSFetchRequestResult>(entityName: "Todo")
    var liste = [TuppleTodo]()

    do {
    // Requete et casting du résultat en une seule ligne
    let resultats = try context?.fetch(requete) as! [NSManagedObject]

    for resultat in resultats {
    let todo = resultat.value(forKey: "texte") as? String
    let date = resultat.value(forKey: "date") as? Date
    // Utilisation de if let pour tester les optionals
    if let date = date,
    let todo = todo {
    // Création d'un tupple encapsulant les données
    let todoExistant = TuppleTodo(date, todo)
    liste.append(todoExistant)
    }
    }

    } catch {
    print("erreur requete CoreData")
    }

    return liste
    }
     

    Il y a 5 "!" dans sa version, pour un dans la mienne. Et encore, celui que j'utilise n'est pas réellement dans du code exécutable, mais dans la définition d'un objectif.


     


    Le try signifie "essaye d'arriver à  faire ça" et non "exécute moi ça de manière inconditionnelle".


    a



    let resultats = try context?.fetch(requete) as! [NSManagedObject]

    a


    Si l'opération échoue, l'application ne vas pas se planter, mais exécuter le code défini par l'instruction catch.


     


    Tu peux aussi remarquer que j'ai ajouté un casting dans le try, pour créer un tableau de [NSManagedObject]. Je ne vois vraiment pas l'intérêt de générer un tableau de [Any], qu'il faudra ensuite caster en NSManagedObject à  chaque lecture.


     


    C'est quand même plus lisible d'écrire  :


    a



    let todo = resultat.value(forKey: "texte") as? String
    let date = resultat.value(forKey: "date") as? Date

    a


    que :


    a



    let todo = (resultat as! NSManagedObject).value(forKey: "texte") as? String
    let date = (resultat as! NSManagedObject).value(forKey: "date") as? Date



    a


    La méthode de Sandy pour lire les données est curieuse : Elle retourne un tableau [Any] contenant des dictionnaires qu'elle caste ensuite en dictionnaire [String:Any], pour accéder aux données du Todo. Pourquoi ne pas fabriquer un tableau de dictionnaires [Date:String] dans le corps de sa fonction ?  C'est ce que j'aurais fait, s'il n'y avais pas les tupples, si pratique à  l'emploi.


    a



    lireTodos { (array) in

    for todo in array {
    let aTodo = todo as! [String : Any]
    print("\(aTodo["date"]!): \(aTodo["todo"]!) à  faire")
    }
    }
     

    a


    La mienne est plus courte. Je n'ai pas besoin d'un casting supplémentaire, mon mécanisme retournant un tableau de tupple encapsulant les valeurs. Les tupples c'est le Bien !


    a



    for todo in lireTodos() {
    print ("Date : ", todo.date, " : ", todo.texte)
    }


    En plus l'affichage n'utilise aucun "!" (3 dans la version de Sandy).


     


    Des questions ? * touche du bois en attendant que la foudre Joanna passe sur le topic *


  • DrakenDraken Membre

    Joanna a approuvé, donc je continue.


    Voici une version plus compacte, davantage dans l'esprit de Swift :



    func lireTodos() -> [TuppleTodo] {

    let requete = NSFetchRequest<NSFetchRequestResult>(entityName: "Todo")
    var liste = [TuppleTodo]()

    do {
    let resultats = try context?.fetch(requete) as! [NSManagedObject]

    for resultat in resultats {
    if let date = resultat.value(forKey: "date") as? Date,
    let todo = resultat.value(forKey: "texte") as? String {
    liste.append(TuppleTodo(date, todo))
    }
    }

    } catch {
    print("erreur requete CoreData")
    }

    return liste
    }

  • DrakenDraken Membre
    mai 2017 modifié #4

    Autre variante :


    On peut aussi laisser tomber les value(forKey) pour utiliser des classes.


     


    Il faut cliquer sur l'entité Todo dans CoreData, sélectionner  l'option "codegen" et valider "Class Definition" (voir image jointe en bas du post). Cela indique à  Xcode de créer une classe portant le nom de l'entité CoreData.


     


    Petite remarque technique : Il arrive qu'Xcode ne reconnaisse pas la classe Todo "générée" à  partir de CoreData. Il faut alors faire un Clean du projet pour lui forcer la main.


     


    Cela facilite le chargement. On peut caster directement les données du Fetch dans un tableau d'objets Todo.


    a



    func chargementTodos() -> [Todo]? {
    let requete = NSFetchRequest<NSFetchRequestResult>(entityName: "Todo")
    var liste:[Todo]?

    do {
    liste = try context?.fetch(requete) as? [Todo]
    } catch {
    print ("erreur requete CoreDate")
    }
    return liste
    }


    aa


    Cela permet d'accéder aux données de la manière habituelle avec des propriétés, sans passer par les horribles setValue(forKey).


    a



    if let listeTodos = chargementTodos() {
    for todo in listeTodos {
    if let date = todo.date,
    let texte = todo.texte {
    print ("Date : ", date, " : ", texte)
    }
    }
    }


    a


    De la même manière on peut employer la classe Todo() pour écrire les données dans CoreData.


    a



    func creationTodo(texte:String, date:Date) {
    // if let pour transformer le context optional de manière propre
    if let context = context {
    let entite = NSEntityDescription.entity(forEntityName: "Todo", in: context)
    if let entite = entite {
    let todo = Todo(entity: entite, insertInto: context)
    todo.texte = texte
    todo.date = date as NSDate
    do {
    try context.save()
    } catch {
    print ("Erreur de sauvegarde CoreData")
    }
    }
    }
    }


  • le "l'option "codegen" et valider "Class Definition" était déjà  coché par défaut je suppose...


     


    J'ai donc modifié la fonction ajouter et remplacé par ton code, ça marche super bien, c'est que je trouvais les explications de Sandy un peu compliquées et archaà¯que.


     


    Je ne sais pas ou tu en es dans les leçons ? Peut-être que cette démarche est voulue pour justement montrer pas la suite une solution plus automatique et simple, plus Swift aussi...


     


    Je suis en train de ma battre avec les dateFormatter, pour couronner le tout la documentation n'est pas a jour je pensais que ça se faisait tout seul l'option correspondante est pourtant cochée, mais bon je vois que ça semble se décocher un peu tout seul...


     


    En tous cas merci...


  • Joanna CarterJoanna Carter Membre, Modérateur
    mai 2017 modifié #6

    Et avec les classes, ça se fait :



    let request = Todo.fetchRequest()

    Et ça te donnera un NSFetchRequest<Todo>

  • DrakenDraken Membre

    Xcode ne semble pas vraiment d'accord avec toi, Joanna !


     


     


  • DrakenDraken Membre


    Je ne sais pas ou tu en es dans les leçons ? 


     




    Nul part et partout. Je n'aime pas suivre des leçons classiques. Je fait partie de ces gens qui ne peuvent apprendre qu'en créant des choses. Il y a un mot pour ça en psychologie, mais j'ai oublié lequel. Bref, je papillote sur les vidéos, chaque fois que j'ai besoin de résoudre un problème particulier.


     


    Si t'es inscrit au MOOC du professeur Kordon (en rouge dans ma signature), ce que je te recommande chaudement, tu verras qu'on peut afficher le texte du cours. J'adore ça, cela me permet de survoler très rapidement les explications (je lis extrêmement vite)  pour me focaliser sur les parties utiles à  ma compréhension.

  • Joanna CarterJoanna Carter Membre, Modérateur


    Xcode ne semble pas vraiment d'accord avec toi, Joanna !




    ... as FetchRequest<Todo>
  • DrakenDraken Membre
    mai 2017 modifié #10

    Effectivement, et cela évite même de caster le résultat de la requête !


    a



    func chargementTodos() -> [Todo]? {
    let requete = Todo.fetchRequest() as NSFetchRequest<Todo>
    var liste:[Todo]?

    do {
    liste = try context?.fetch(requete)
    } catch {
    print ("erreur requete CoreDate")
    }
    return liste
    }


    a


    Merci joanna.


    a



  • Si t'es inscrit au MOOC du professeur Kordon (en rouge dans ma signature), ce que je te recommande chaudement, tu verras qu'on peut afficher le texte du cours. J'adore ça, cela me permet de survoler très rapidement les explications (je lis extrêmement vite)  pour me focaliser sur les parties utiles à  ma compréhension.




    J'avais suivi les cours précédents, démarré en Swift, un poil fatiguant toutes les intros avec l'Iphone en tourniquet, ensuite il balaie l'Auto-Layout (qui m'apparait indispensable...) pour finir il précise abandonner Swift pour continuer en ObjC...


    J'ai vu qu'il avait refait des Vidéos avec Swift3, par contre j'ai cherché a m'inscrire gratuitement et chaque fois je suis renvoyé sur un truc me demandant de valider 48€ J'ai probablement raté quelque chose ?


     


    J'avais précédemment les vidéos sur iTunes U, bref ça m'intéresse mais comment je fais donc ?



  • J'ai vu qu'il avait refait des Vidéos avec Swift3, par contre j'ai cherché a m'inscrire gratuitement et chaque fois je suis renvoyé sur un truc me demandant de valider 48€ J'ai probablement raté quelque chose ?


     




    Les 48 euros c'est pour obtenir un certificat attestant que tu as suivis les cours et répondu aux QCM de vérification. Ce n'est pas obligatoire pour regarder les vidéos et accéder aux forums du MOOC. C'est vrai qu'edx est assez insistant sur l'achat du certificat, mais on peut s'en passer (je ne l'ai pas acheté pour ma part).

  • ça ne me dit pas comment je fais ?
  • DrakenDraken Membre
    mai 2017 modifié #14

    Euh .. que répondre à  ça. Tu crées un compte sur Edx.org, tu choisit un cours et tu valide l'option Free, sans répondre aux sollicitations "c'est mieux si vous cliquez ici pour la formule avec certification à  48 $". Il n'y a rien de particulier à  faire. Je me suis inscrit à  3 cours d'edx (MOOC iOS première partie, MOOC iOS seconde partie - septembre 2017, et un autre sur les rapports entre la philosophie et le christianisme dans l'antiquité, sans la moindre difficulté.


     


    D'ailleurs, ils me proposent encore d'acheter le certificat payant pour la seconde partie du MOOC sur iOS (voir image jointe).


     


     


    edx.png 112.7K
  • GercofisGercofis Membre
    mai 2017 modifié #15


    Euh .. que répondre à  ça. Tu crées un compte sur Edx.org, tu choisit un cours et tu valide l'option Free...




     


    Le compte c'est fait, par contre aucune option Free ???


     


    N'y aurait-il pas eut un changement... de toutes façons a 48$ je n'y vais pas...


     


    Donnes moi le lien du/des cour/s que tu suis


  • Cela me surprend. Je vais essayer de me créer un autre compte, avec Chrome et de m'inscrire une nouvelle fois.

  • DrakenDraken Membre
    mai 2017 modifié #17

    Ah oui, il y a une ruse ..  >:(


     


    Je me suis désinscrit de la seconde partie du MOOC du professeur Kordon et re-inscrit pour voir. Effectivement, seul l'option payante apparait à  l'écran. Il faut scroller vers le bas de la fenêtre pour faire apparaitre l'option gratuite.

    edx2.png 258.1K


  • Ah oui, il y a une ruse ..  >:(


     


    Je me suis désinscrit de la seconde partie du MOOC du professeur Kordon et re-inscrit pour voir. Effectivement, seul l'option payante apparait à  l'écran. Il faut scroller vers le bas de la fenêtre pour faire apparaitre l'option gratuite.




     


    Rien a faire je n'ai pas la même page et donc pas les images que t'as postées..

  • DrakenDraken Membre
    mai 2017 modifié #19

    Tu doit arriver à  cette page en cliquant sur le bouton vert "Enroll Now", dans la description du cours.


     


    Sur l'image j'ai cerclé le texte disant que le cours peut être FREE.


     


     


    Sinon, peut-tu faire une copie d'écran de ce que edx.org t'affiche ?

    edx1.png 490.9K
  • GercofisGercofis Membre
    mai 2017 modifié #20

    sauf que moi j'ai "You are enrolled"


     


    explication voilà  le petit mot que je viens de trouver


     


    Bonjour à  tous,


    Suite à  quelques soucis techniques, le lancement de la partie II du MOOC "Programmation iOS" a été décalée au 5 septembre. Mais vous pouvez d'ores et déjà  vous y inscrire ici.


    Ce second MOOC constitue une suite logique de celui-ci et nous aborderons plein de sujets passionnants comme l'utilisation des cartes et de la géolocalisation, des capteurs et des gestes, etc. Nous laissons l'inscription à  la partie I ouverte de manière à  ce que ceux qui auraient entendu parler de ces enseignements trop tard puissent quand même accéder aux vidéos et aux échanges.


    Nous espérons vous revoir sur la deuxième partie de cet enseignement.


    L'équipe pédagogique du MOOC "Programmation iOS (Partie I)"


     


    j'arrive a voir "Programmation iOS (Partie I)" déjà  ça


  • Bah oui, ça veut dire que tu as réussi à   t'inscrire, alors. La partie 2 commence effectivement en Septembre, il n'y a aucune vidéo en ligne et le forum n'est pas activé.


     


    Pour le moment, c'est la partie 1 qui est intéressante pour toi. Elle est terminée depuis quelques semaines, mais tu peux quand même t'y inscrire pour regarder les vidéos, consulter les discussions sur le forum, faire les exercices proposés chaque semaine et télécharger les exercices des autres participants. J'y ai pas mal posté sous le pseudo IceDraken.

  • l'inscription est faite , tu utilises l'application iPad ?


  • Non, mon MacBookPro 13 pouces avec Safari.

  • Pour revenir a CoreData et les TableView...


    Quelle est la différence entre TableViewController et une ViewController qui reçoit une TableView...


    La plupart des tutos CoreData récents utilise ViewController et non TableViewController (dans lequel on ne peut pas rajouter ce qu'on veut ?)


  • Avec CoreData aucun il me semble. La différence entre les 2 cas que tu cites se situe davantage sur la flexibilité de ta vue. Si tu souhaites construire une vue avec plusieurs composants UI en plus d'une tableView, un UIViewController est privilégié. UITableViewController est préférée quand tu souhaites afficher seulement une liste, qui plus est si elle doit contenir des champs de texte dès lors le système de Scroll est géré pour toi automatiquement.


  • Oui, je suis du même avis que Magiic sur le sujet. A une époque, les gens aimaient faire des tutos avec des UITableViewController parce que les écrans étaient petits et ne pouvaient pas contenir grand chose en dehors de la tableView. Ce n'est plus le cas de nos jours avec les iPad, les smartphones de grande taille et les applications universelles. Avec UIViewController on peut respirer à  son aise.

  • Merci a tous les deux c'est on ne peux plus clair  j'ai tout compris du coup je comprends carrément mieux...


  • Dans un attribut CoreData on peut préciser Décimal ?


    En fait je voudrais stocker des fréquences RadioFM donc par exemple 101.4 etc...


    Depuis un TextField que dois-je donc affecter a cette attribut ? S'il y a moins gourmand je peux préciser autre chose que Decimal



    frequence est L'outlet TextField
    _radio.frequence est l'attribut déclaré "Decimal"
    if let frequ = Decimal(frequence.text!) { // Double (Decimal) est accepté
            _radio.frequence = frequ //NSDecimalNumber(frequence.text!) // mais coince ici
            }

    pour le fun j'aimerais bien comprendre ?


     


    a vous lire




  • Dans un attribut CoreData on peut préciser Décimal ?


    En fait je voudrais stocker des fréquences RadioFM donc par exemple 101.4 etc...


    Depuis un TextField que dois-je donc affecter a cette attribut ? S'il y a moins gourmand je peux préciser autre chose que Decimal



    frequence est L'outlet TextField
    _radio.frequence est l'attribut déclaré "Decimal"
    if let frequ = Decimal(frequence.text!) { // Double (Decimal) est accepté
            _radio.frequence = frequ //NSDecimalNumber(frequence.text!) // mais coince ici
            }

    pour le fun j'aimerais bien comprendre ?


     


     




     


    Tu peux utiliser un attribut Float ou Double pour stocker ta fréquence, puisque ce sont des nombres à  virgule.



  • Dans un attribut CoreData on peut préciser Décimal ?


    En fait je voudrais stocker des fréquences RadioFM donc par exemple 101.4 etc...


    Depuis un TextField que dois-je donc affecter a cette attribut ? S'il y a moins gourmand je peux préciser autre chose que Decimal



    frequence est L'outlet TextField
    _radio.frequence est l'attribut déclaré "Decimal"
    if let frequ = Decimal(frequence.text!) { // Double (Decimal) est accepté
            _radio.frequence = frequ //NSDecimalNumber(frequence.text!) // mais coince ici
            }

    pour le fun j'aimerais bien comprendre ?


     


    a vous lire




     


    Qu'entends-tu par "ca coince" ? Une erreur de compilation ? au runtime ?

  • Joanna CarterJoanna Carter Membre, Modérateur
    De mon avis, les fréquences sont comme les numéros de téléphone ; du coup, dans un String.


    Il faut te demander - est-ce que je fais les calculs avec ?
Connectez-vous ou Inscrivez-vous pour répondre.