création et remplissage tableView (code) sans storyBoard

toolsDevtoolsDev Membre
février 2017 modifié dans Objective-C, Swift, C, C++ #1

Bonjour,


 


 


j'aimerais savoir comment implémenter une tableView (code), sans utilisé le storyBoard et dans un viewController ?


 


Le début est correcte mais c'est pour l'ajout des données que je sèche :



let tableView:UITableView = UITableView(frame: CGRect(x:130, y: 30, width:200, height:400))
tableView.reloadData()
tableView.backgroundColor = UIColor(red: 85/255, green: 175/255, blue: 101/255, alpha: 1.0)

// compte le nombre d'éléments de mon tableau
tableView.numberOfRows(inSection:connexion.arrayData.count)

/ puis ma boucle pour ajouter mes données :
for data in connexion.arrayData
{
let cell = tableView.dequeueReusableCell( withIdentifier:??, for:??)
cell.myLabel.text = data
}
self.view.addSubview(tableView)

// avec ce code ma tableView s'affiche mais je ne sais pas comment remplir les champs "??"

En fait, j'aimerais pouvoir "mapper" automatiquement les colonnes et les lignes de ma requête en base, sur ma tableView.


Car mon tableau "data" contient les champs de mes tables, exemple :


id:1


name: nom


description: description


etc


 


Sans forcément les mêmes champs 


 


En somme remplir un tableau automatiquement en respectant colonnes/lignes... (mais avec la possibilité de ne pas afficher certaines lignes (exemple les id)


 


Merci à  toutes et tous :)


«1

Réponses

  • Joanna CarterJoanna Carter Membre, Modérateur
    février 2017 modifié #2


    j'aimerais savoir comment implémenter une tableView (code), sans utilisé le storyBoard et dans un viewController ?




     


    Mais pourquoi ? Tu veux aussi poser et gonfler les pneus sur les roues avant chaque fois que tu conduises ta bagnole ?


     




    Le début est correcte mais c'est pour l'ajout des données que je sèche :



    let tableView:UITableView = UITableView(frame: CGRect(x:130, y: 30, width:200, height:400))
    tableView.reloadData()
    tableView.backgroundColor = UIColor(red: 85/255, green: 175/255, blue: 101/255, alpha: 1.0)

    // compte le nombre d'éléments de mon tableau
    tableView.numberOfRows(inSection:connexion.arrayData.count)

    / puis ma boucle pour ajouter mes données :
    for data in connexion.arrayData
    {
    let cell = tableView.dequeueReusableCell( withIdentifier:??, for:??)
    cell.myLabel.text = data
    }


    // avec ce code ma tableView s'affiche mais je ne sais pas comment remplir les champs "??"



     


    Le début n'est pas du tout correct.


     


    1. Tu n'as pas considéré les contraintes pour maintenir la position à  la rotation


     


    2. Le seul moyen de remplir une tableView, c'est par son delegate et son dataSource


     


    2. tu appelles reloadData avent que tu as connecté la tableView à  son delegate et son dataSource


     


    3. tu n'as pas implémenté ni le delegate ni le dataSource


     


    4. les méthodes que tu as appelé sur la tableView sont les méthodes que l'on ne doive pas appeler, elles devraient être appelé seulement par le tableView lorsque elle en a besoin.


     




    En fait, j'aimerais pouvoir "mapper" automatiquement les colonnes et les lignes de ma requête en base, sur ma tableView.


     


    En somme remplir un tableau automatiquement en respectant colonnes/lignes... (mais avec la possibilité de ne pas afficher certaines lignes (exemple les id)




     


    Les tableViews ne comprennent pas les colonnes. Il faut considérer construire les cellules avec multiples parties ou d'utiliser une UICollectionView.


     


    Mais je répète - pourquoi ne pas utiliser un storyboard ?


  • toolsDevtoolsDev Membre
    février 2017 modifié #3

    si je voulais faire ainsi, c'est pour ne pas avoir ma tableView sur ma vue.


    donc si je la place sur le storyboard je vais avoir un empilement d'objets, voilà  tout...


     


    la tableView doit être positionné à  un certain endroit au clique d'un bouton. Alors quoi de plus pratique que je la construire à  la volé ! plutôt que de la mettre déjà  en place.


     


    Mais vu ta réponse je pensais encore mal :(


  • FKDEVFKDEV Membre
    février 2017 modifié #4

    Ton approche est logique, et valable dans d'autres contextes, mais ce n'est pas de cette manière que fonctionnent les table view sous iOS.

     

    Pour des questions d'efficacité mémoire et de vitesse d'exécution, ce n'est pas toi qui mets des cellules dans la tableview, c'est elle qui te demande ce qu'elle contient quand elle a besoin de le savoir.

     

    Pour répondre à  ses besoins, tu implémentes le protocol UITableViewDataSource.

    Généralement, on implémente ce protocol dans le view controller qui gère la vue dans laquelle tu as mis ta table view.

    Si ta vue ne contient que la table view, tu peux utiliser la classe UITableViewController comme controller.

     

    Au minimum ton objet qui implémente le protocol datasource doit répondre à  ces deux questions :

     



    func tableView(UITableView, numberOfRowsInSection: Int)

    func tableView(UITableView, cellForRowAt: IndexPath)


     

    La première méthode est appelée dès que la table view doit s'afficher. tu dois renvoyer le nombre total de cellules dans ta vue (tu verras les sections plus tard).

     

    La deuxième méthode te demande de renvoyer la cellule à  afficher pour une certaine ligne.


    Exemple de ce qu'il faut faire au minimum :



    //via https://github.com/codepath/ios_guides/wiki/Table-View-Guide

    import UIKit

    class ViewController: UIViewController, UITableViewDataSource {
    @IBOutlet weak var tableView: UITableView!

    let data = ["New York, NY", "Los Angeles, CA", "Chicago, IL", "Houston, TX",
    "Philadelphia, PA", "Phoenix, AZ", "San Diego, CA", "San Antonio, TX",
    "Dallas, TX", "Detroit, MI", "San Jose, CA", "Indianapolis, IN",
    "Jacksonville, FL", "San Francisco, CA", "Columbus, OH", "Austin, TX",
    "Memphis, TN", "Baltimore, MD", "Charlotte, ND", "Fort Worth, TX"]

    override func viewDidLoad() {
    super.viewDidLoad()
    tableView.dataSource = self
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
    cell.textLabel?.text = data[indexPath.row]
    return cell
    }


    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return data.count
    }
    }

    Ici on suppose que la tableview est déjà  créée mais tu peux la créer dans la méthode viewDidLoad.

    Depuis qu'on utilise Autolayout, il plus pratique de créer et de positionner les vues dans un storyboard.

    Mais tu peux toujours le faire dans le code en créant toutes les contraintes Autolayout à  la main. quand je le fais, j'utilise les macros d'Erica Sadun (mais il y a surement plus récent).


     

    Petite précision sur dequeueReusableCell :

    En plus du principe du datasource, il ya le fait que les cellules peuvent être recyclées. Ce n'est pas obligatoire. Par exemple si tu n'as qu'une poignée de cellules à  afficher tu peux te passer du recyclage, comme dans l'exemple ci-dessus.

     

    dequeueReusableCell est utilisé pour obtenir une nouvelle cellule (elle sera créée ou issue d'un recyclage).

    L'identifiant correspond au type de cellule que tu veux créer (tu peux faire cohabiter des cellules qui ont des formats différents).

    Tu dois utiliser register(_:forCellReuseIdentifier) pour faire correspondre ton identifiant à  ton type de cellule avant l'affichage, par exemple dans ViewdidLoad.


     


     


    Il y a des centaines de tuto sur le sujet. Il faut juste en trouver un le plus récent possible.


  • Quelques tutos français en vidéo sur le sujet : http://pagesperso.lip6.fr/Fabrice.Kordon/5I452-2014/semaine-08.php

  • toolsDevtoolsDev Membre
    février 2017 modifié #6
    Je vais tout reprendre que ça soit bien clair.

     

    Mon principal soucis est que je construit des boutons en fonction du nombre de table que j'ai en base. Si dans ma base j'ai 5 tables, j'aurais 5 boutons portant leur nom.

     

    là , où je bloque, c'est que mes index(sender) pour savoir sur quel bouton l'utilisateur a appuyé est passé en paramètre dans une méthode pour exécuter une requête (celle qui doit affiché dans ma fameuse tableView, qui à  ce moment là  n'est pas visible)

     

    exemple, je clique sur le bouton consoles et voici la requête que je souhaite afficher dans la tableView :


    ("nom", "GameCube")
    ("id", 8)
    ("nom", "Wii")
    ("id", 9)
    ("nom", "PS3")
    ("id", 10)
    ("nom", "PS4")
    ("id", 11)
    ("nom", "3DS")
    ("id", 12)
    ("nom", "XBOX360")
    ("id", 13)
    ("nom", "DS")



    En fait, un tableau à  plusieurs entrées serait parfait ! (comme ce qu'on à  en java JTable...)


  • My 2 cents :

    Essaye d'abord de faire avec une UITableView dans un Storyboard/xib.


    Ensuite, passe ensuite ta UITableView (au niveau du rendering) uniquement par code.


     


    Pourquoi ?


    J'ai l'impression que tu as d'abord un soucis avec ton modèle (construction et implémentation du dataSource), et du coup, tu te rajoutes une complication en plus en voulant faire l'UI aussi uniquement par code.


  • Joanna CarterJoanna Carter Membre, Modérateur
    février 2017 modifié #8

    Si tu veux changer ce que la tableView montre, il suffit de changer la liste que tu utilises pour fournir les données. Ce n'est pas nécessaire de changer la tableView. C'est les méthodes du delegate qui fournissent les données.


     


    Et il faut cinq méthodes pour les boutons, c'est bien plus simple que devoir récupérer les "senders".


     


    Du coup, tu aurais qqch comme :



    {
    var liste1: [...]

    var liste2: [...]

    var liste3: [...]

    var liste4: [...]

    var liste5: [...]

    var listeActuelle: [...]

    @IBAction func bouton1()
    {
    listeActuelle = liste1

    tableView.reloadData()
    }

    @IBAction func bouton2()
    {
    listeActuelle = liste2

    tableView.reloadData()
    }

    @IBAction func bouton3()
    {
    listeActuelle = liste3

    tableView.reloadData()
    }

    @IBAction func bouton4()
    {
    listeActuelle = liste4

    tableView.reloadData()
    }

    @IBAction func bouton5()
    {
    listeActuelle = liste5

    tableView.reloadData()
    }

    ...
    }


    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
    return listeActuelle.count
    }

    public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
    let cell = tableView.dequeue...

    let selectedObject = listeActuelle[indexPath.row]

    cell.object = selectedObject

    return cell
    }


  • Joanna CarterJoanna Carter Membre, Modérateur

    '... En fait, un tableau à  plusieurs entrées serait parfait ! (comme ce qu'on à  en java JTable...)




    UITableView ne marche pas du tout comme JTable.


    JTable est rempli avant de l'utiliser.


    UITableView est rempli dynamiquement des méthodes du dataSource et on peut changer les données à  n'importe quel moment.
  • toolsDevtoolsDev Membre
    février 2017 modifié #10

    Pour aller doucement et bien comprendre, j'ai un gros soucis de compréhension sur les types :



    Cannot subscript a value of type '[Any]' with an index of type 'IndexPath'

    j'ai mes données (provenant de ma requête/méthode) sous forme d'un tableau var arrayDataTable:[Any] = [] :



    ("nom", "GameCube")
    ("id", 8)
    ("nom", "Wii")
    ("id", 9)
    ("nom", "PS3")
    ("id", 10)
    ("nom", "PS4")
    ("id", 11)
    ("nom", "3DS")
    ("id", 12)
    ("nom", "XBOX360")
    ("id", 13)
    ("nom", "DS")

    et au niveau de 



    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
    let cell = UITableViewCell(style: .default, reuseIdentifier: nil)

    guard var connexion = ConnexionSQLite.sharedInstance else
    {
    print("Erreur de connexion")
    }

    // donc voilà  ce que je voudrais faire, mettre dans cell.textLabel?.text mon tableau de données arrayDataTable:[Any] = []
    cell.textLabel?.text = connexion.arrayDataTable[indexPath.row]
    return cell
    }

    Donc, je sais que cell.textLabel? = "attends" un type String, mais je lui affecte un type Any...


    et même en le parsant (as! Any) j'ai cette erreur de compilation.


     


    voilà , :(


  • Joanna CarterJoanna Carter Membre, Modérateur
    Il faut créer ta propre cellule en ajoutant les labels ou autres choses, puis tu devrais ajouter une var du type des données qui tu veuilles passer à  la cellule. Dans le didSet, tu peux là  écrire le code pour assigner les données aux labels.
  • DrakenDraken Membre
    février 2017 modifié #12

     


    Donc, je sais que cell.textLabel? = "attends" un type String, mais je lui affecte un type Any...


    et même en le parsant (as! Any) j'ai cette erreur de compilation



    a


    Tu sais que Xcode attend un String, alors tu cherches à  convertir un [Any] en un Any   ???


    Pourquoi ne pas convertir le [Any] en un .. String ?


    d



    // Tableau de type [Any]
    let connexion:[Any] = ["GameCube",
    "Wii",
    "PS3",
    "PS4"]
    // Lecture d'une ligne
    let index = 1
    let str = connexion[index] as! String
    print (str)


    Wii



  • DrakenDraken Membre
    février 2017 modifié #13

    J'ai l'impression que tu maitrises mal les conversions de type en Swift. 



    let data = ["New York, NY", "Los Angeles, CA", "Chicago, IL", "Houston, TX",
    "Philadelphia, PA", "Phoenix, AZ", "San Diego, CA", "San Antonio, TX",
    "Dallas, TX", "Detroit, MI", "San Jose, CA", "Indianapolis, IN",
    "Jacksonville, FL", "San Francisco, CA", "Columbus, OH", "Austin, TX",
    "Memphis, TN", "Baltimore, MD", "Charlotte, ND", "Fort Worth, TX"]

    a


    Dans l'exemple de code donné par FKDEV, le tableau data est de type [String]. C'est le type de base quand on tape quelque chose comme :


    a



    let tableau = ["Chaine A", "Chaine B", "Chaine C"]


    qa


    Ce qui lui permet de lire directement le tableau pour obtenir un String


    a



    ...
    cell.textLabel?.text = data[indexPath.row]
    ...

    a


    Pour être dans les mêmes conditions que toi, j'ai créé un tableau de [Any], en précisant sa nature:



    let connexion:[Any] = ["GameCube",
    "Wii",
    "PS3",
    "PS4"]


    a


    Pour avoir un String je dois lire un Any dans le tableau et le convertir  :


    a



    let index = 1
    let str = connexion[index] as! String


    A lire le message d'erreur :


    a



     


     


    Cannot subscript a value of type '[Any]' with an index of type 'IndexPath'

    a


    j'ai l'impression que ton tableau de [Any] n'est PAS un tableau, mais juste un Any contenant plein de trucs dedans.



    guard var connexion = ConnexionSQLite.sharedInstance else
    {
    print("Erreur de connexion")
    }
    cell.textLabel?.text = connexion.arrayDataTable[indexPath.row]


    Je ne sais pas trop ce que tu récupères avec ta connexionSQLite (je ne comprend rien du tout à  SQL), mais ce n'est certainement pas un tableau de String bien structuré, utilisable par Swift.


     


    EDIT : J'ai relu le début du post. Ton problème est bien plus compliqué que de lire le contenu d'un tableau. Tu as un flux de données contenant différentes informations. Il faut analyser ce flux pour en extraire les informations et les stocker quelque part avant de les utiliser. C'est un problème distinct de l'utilisation d'un UITableView.

  • toolsDevtoolsDev Membre
    février 2017 modifié #14


     


    Si tu veux changer ce que la tableView montre, il suffit de changer la liste que tu utilises pour fournir les données. Ce n'est pas nécessaire de changer la tableView. C'est les méthodes du delegate qui fournissent les données.


     


    Et il faut cinq méthodes pour les boutons, c'est bien plus simple que devoir récupérer les "senders




    J'arrive pourtant très bien à  récupérer le bon index et à  le passé en paramètre dans ma méthode qui "requête" la base en fonction du choix de l'utilisateur.


    Mon réelle soucis est comme expliquer plus bas, est bien le type ("Row") que je récupère directement depuis ma requête via GRDB.swift :( Impossible de itéré ce "faux" tableau...


     




    Tu sais que Xcode attend un String, alors tu cherches à  convertir un [Any] en un Any   ???


    Pourquoi ne pas convertir le [Any] en un .. String ?




    alors comme tu as put le lire dans le quote du code, j'ai cité le soucis.


    c'est juste une erreur de ma part j'ai bien sur tenté un parse en String  ;) mais sans succès.


     



    // Tableau de type [Any]

    let connexion:[Any] = ["GameCube",

    "Wii",

    "PS3",

    "PS4"]

    // Lecture d'une ligne

    let index = 1

    let str = connexion[index] as! String

    print (str)

     




    Et non :) pas si simple que ça malheureusement (enfin pour moi)


    J'ai un tableau Brut sans clé/valeur, c'est bien GRDB.swift qui me mène la vie dur et non ma méthode.


    Dans le sens, ou je ne peut rien parser ou pire parcourir...


     


     




    EDIT : J'ai relu le début du post. Ton problème est bien plus compliqué que de lire le contenu d'un tableau. Tu as un flux de données contenant différentes informations. Il faut analyser ce flux pour en extraire les informations et les stocker quelque part avant de les utiliser. C'est un problème distinct de l'utilisation d'un UITableView.




     


    Tout à  fait  ;)


    J'allais vous le redire hier... Mon soucis est que je n'ai pas la main sur GRBD.swift et il me renvois un tableau dont je ne peut rien parsé (à  ma connaissance, sans le traité AVANT...)


     


    J'aurais bien aimer que ma requête renvois un dictionary: clé/valeur.


    du coup je me retrouve avec un tableau brute ou comme tu le signales un flux, inutilisable, finalement BEAUCOUP d'énergie pour pas grand chose encore...


    Pourquoi cela n'est il pas aussi simple ?... une requête dans un tableau, je parcours ce tableau et basta... 


    Si je dois ENCORE changer de framework... ou si vous avez des idées sur une façon simple de récupérer directement ma requête en dictionary ou array je suis preneur ?


     


    Edit :


    je réitère ma demande (de mon autre poste)


    VOUS qu'est ce que vous utilisez pour avoir la main sur vos requêtes SQLite et en même temps de "simple" ?


    Je ne comprends vraiment pas ce système usine à  gaz :(


  • Je ne connait rien à  SQL, encore moins à  SRBD.swift, mais en faisant une recherche simple sur Google, on trouve de la documentation et des exemples d'utilisation :


     


    http://cocoadocs.org/docsets/GRDB.swift/0.4.0/


     


    Tu devrais trouver ton bonheur dedans, non ?

  • toolsDevtoolsDev Membre
    février 2017 modifié #16

    merci pour ton geste, vraiment ;)


     


    mais, avant de posté je cherche toujours (de mon mieux) et pour ça aucun soucis.


    J'ai trouvé comment "requêter" Mais, le soucis est bien ce que me ramène GRDB.swift... C'est à  un tableau(brut) que je ne sais pas parcourir visiblement...


    je te met mon code que tu vois (il n'y à  rien de compliquer) :



    // // ---- récupère toutes les données de chaque table : ----
    // let dataTables = try connexion?.inDatabase{db in try Row.fetchAll(db,"SELECT * FROM \(table)")}
    for dataTable in dataTables!
    {
    // Je parcours et ajoute dans un tableau globale toutes les infos pour chaque colonne/ligne
    // Le réellement soucis est ici :) à  chaque itération, il me met dans les "clé/valeur" ensemble...
    for data in dataTable
    {
    self.arrayDataTable.append(data)
    }

    pas de clé/valeurs ! et ça, c'est pénible...


    je n'ai aucun soucis coté SQL, je me débrouille bien de ce coté :) (en même temps je fais du très simple... la seule requêtes "compliquer" est celle pour récupérer dynamiquement toutes les tables de ma base...)


     


    Non, mon vrai soucis est de traiter l'info !


    C'est vraiment pénible pur pas grand chose (enfin de mon point vue) 


    Je me doute que vous, vous aillez l'habitude, mais franchement SQL et ios :( ... c'est pas simple pour rien du tout...


     


    Edit:


    je rajouterais que je souhaite tout récupérer au lancement de l'application, pour tout mettre dans un tableau et du coup, ne plus faire de requêtes.


    Cela rest bien mieux niveau performances, de mon point vue. Une requête, un fois, une affectation dans un tableau/dico... puis des itérations clé/valeur.


  • LarmeLarme Membre
    février 2017 modifié #17
    Il faudrait revenir sur ton topic précédent parlant de GRDB.swift.
     
    Si j'en crois leur Read.me 
     
    try dbQueue.inDatabase { db in
        let rows = try Row.fetchCursor(db, "SELECT * FROM pointOfInterests")
        while let row = try rows.next() {
            let title: String = row.value(named: "title")
            let isFavorite: Bool = row.value(named: "favorite")
            let coordinate = CLLocationCoordinate2DMake(
                row.value(named: "latitude"),
                row.value(named: "longitude"))
        }
    J'ai l'impression que toi tu fais un truc bizarre an ayant:
    ("nom", "GameCube")
    ("id", 8)
    ("nom", "Wii")
    ("id", 9)
    ("nom", "PS3")
    ("id", 10)
    ("nom", "PS4")
    ("id", 11)
    ("nom", "3DS")
    ("id", 12)
    ("nom", "XBOX360")
    ("id", 13)
    ("nom", "DS")
     

    Comme l'avait signalé la nounourse Joanna, il faut que tu aies une classe qui représente ton object correctement, représentant ce que faut ton object fetché.
     
     

    En Objective-C, j'aurais :
    @interface Console : NSObject
    @property (nonatomic, strong) NSString *consoleName;
    @proprety (nonatomic, assing) NSInteger consoleID;
    @end

    @implementation Console
    -(id)initWithName:(NSString *)name andID:(NSInteger)uniqueID
    {
    self = [super init];
    if (self)
    {
    _consoleName = name;
    _consoleID = uniqueID;
    }
    }
    @end
     
    Lors du fetch, je ferais alors quelque chose du genre (pseudo code) :
    while (rows.next)
    {
    Console *uneConsole = [Console alloc] initWithName:[row valueForName:@title] andID:[row valueForName:@id];
    [monBigArray addObject:uneConsole];
    }
    Or, j'ai l'impression que toi tu fais quelque chose du genre:
    while (rows.next)
    {
    [monBigArray addObject:[row valueForName:@title]];
    [monBigArray addObject:[row valueForName:@id]];
    }
    Pour faire du pseudo code Swift:
    try dbQueue.inDatabase { db in
        let rows = try Row.fetchCursor(db, "ta requête")
        while let row = try rows.next() {
    let console: Console = Console()
    console.name = row.value(named: "name")
    console.uniqueID = row.value(named: "id")
    monBigArray.addObject(console)
        }
  • Joanna CarterJoanna Carter Membre, Modérateur
    février 2017 modifié #18


    En Objective-C, j'aurais :



    @interface Console : NSObject
    @property (nonatomic, strong) NSString *consoleName;
    @proprety (nonatomic, assing) NSInteger consoleID;
    @end

    @implementation Console
    -(id)initWithName:(NSString *)name andID:(NSInteger)uniqueID
    {
    self = [super init];
    if (self)
    {
    _consoleName = name;
    _consoleID = uniqueID;
    }
    }
    @end

     

    Lors du fetch, je ferais alors quelque chose du genre (pseudo code) :

    while (rows.next)
    {
    Console *uneConsole = [Console alloc] initWithName:[row valueForName:@title] andID:[row valueForName:@id];
    [monBigArray addObject:uneConsole];
    }



     


    Et, en Swift, tu aurais :



    struct Console
    {
    let id: Int

    let nom: String
    }


    while let row = try rows.next()
    {
    let console = Console(id: row.value(named: "id"), nom: row.value(named:"nom"))

    self.consoles.append(console)
    }

  • toolsDevtoolsDev Membre
    février 2017 modifié #19


    Il faudrait revenir sur ton topic précédent parlant de GRDB.swift.

     

    Si j'en crois leur Read.me 

     


    try dbQueue.inDatabase { db in

        let rows = try Row.fetchCursor(db, "SELECT * FROM pointOfInterests")

        while let row = try rows.next() {

            let title: String = row.value(named: "title")

            let isFavorite: Bool = row.value(named: "favorite")

            let coordinate = CLLocationCoordinate2DMake(

                row.value(named: "latitude"),

                row.value(named: "longitude"))

        }


     


    J'ai l'impression que toi tu fais un truc bizarre an ayant:


     


    ("nom", "GameCube")

    ("id", 8)

    ("nom", "Wii")

    ("id", 9)

    ("nom", "PS3")

    ("id", 10)

    ("nom", "PS4")

    ("id", 11)

    ("nom", "3DS")

    ("id", 12)

    ("nom", "XBOX360")

    ("id", 13)

    ("nom", "DS") 




    ce que tu m'indiques est fait :



    // récupère toutes les tables :
    let tables = try connexion.inDatabase{db in try Row.fetchAll(db,"SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'")}
    for table in tables
    {
    // // ---- récupère les noms de chaque table : ----
    // let nomTable:String = table.value(named:"name")
    self.arrayNomTable.append(nomTable)
    }

    mais pour les champs je les veut tous... du coup je suis bloquer avec un tableau qui agglomère clé/valeur, et je ne peut pas les distingués.


    Mais quand je tente la même chose pour les données (lignes)




    // // ---- récupère toutes les données de chaque table : ----
    // let dataTables = try connexion?.inDatabase{db in try Row.fetchAll(db,"SELECT * FROM \(table)")}
    for dataTable in dataTables!
    {
    for data in dataTable
    {
    let id:Int = data.value(named:"id")
    let name:String = data.value(named:"name")
    //erreur de compilation...
    Value of tuple type '(String, DatabaseValue)' has no member 'value'
    }
    }

    Bon, je vais réfléchir à  tout ça, j'avoue je suis paumé là  ! soucis de conception, peut être, mais GRDB.swift est pas très lisible pour moi.


    Mappé ? oui bien sur mais je ne souhaite pas une classe "Jeux" (par exemple) statique... je veux du dynamique, tu vois ?


    si ma table à  des ajout de colonne, car si je fait un ORM je dois lui renseigner a la mano toutes les colonnes...


    Voilà  ma vision.


  • Tant que tu n'aurais pas réussi à  le faire/comprendre en "statique" comme tu dis, tu auras du mal à  le faire en "dynamique".


    Et tu auras tout de même un soucis à  l'affichage, dans tous les cas.


    Tu te rajoutes une dose de complexité, qui te ralentis énormément. Tu as besoin d'abord de maà®triser des concepts.

  • Joanna CarterJoanna Carter Membre, Modérateur
    février 2017 modifié #21


    Mappé ? oui bien sur mais je ne souhaite pas une classe "Jeux" (par exemple) statique... je veux du dynamique, tu vois ?


    si ma table à  des ajout de colonne, car si je fait un ORM je dois lui renseigner a la mano toutes les colonnes...


    Voilà  ma vision.




     


     


    Donc, il te faudra utiliser un dictionnaire de [String : Any] mais, là , tu auras les mêmes soucis de mauvais types.


     


    Il faut accepter que tu n'est plus dans le pays de Java ; On parle ici de Swift, qui est un langage dont les types sont strictement appliqués.


     


    Si tu veux le dynamisme, il faut chercher ailleurs


  • toolsDevtoolsDev Membre
    février 2017 modifié #22


    Tant que tu n'aurais pas réussi à  le faire/comprendre en "statique" comme tu dis, tu auras du mal à  le faire en "dynamique".


    Et tu auras tout de même un soucis à  l'affichage, dans tous les cas.


    Tu te rajoutes une dose de complexité, qui te ralentis énormément. Tu as besoin d'abord de maà®triser des concepts.



     


    je comprends et j'entends ce que tu me dis.


     


    à  vouloir faire simple et dynamique, ça devient une usine à  gaz :(


     


    les concepts de type optional() je commence à  m'y faire, et le reste aussi, mais je ne me sent pas si naze que ça, mais ! oui j'ai des lacunes...


     


    mon principale soucis, je le redis... c'est la maitrise de ce framework et de géré mes tableaux... niveau objets/méthode etc.


    je trouve que je m'en sort pas trop mal non ?


     


     


     




    Donc, il te faudra utilise un dictionnaire de [String : Any] mais, là , tu auras les mêmes soucis de mauvais types.


     


    Il faut accepter que tu n'est plus dans le pays de Java ; On parle ici de Swift, qui est un langage dont les types sont strictement appliqués.


     


    Si tu veux le dynamique, il faut chercher ailleurs




     

     

    Oui 100% d'accord avec toi ;)

     

    Mais quoi utilisé ? CoreData  ?

    Qui m'interdira (si j'ai tout pigé) d'utilisé une base existante, ou pire de faire des backup sur une autre base (miroir) MySQL ? edit: et surtout pas de possibilité de faire des requêtes un peut exotique...

     

    Non, ça non... donc je répète, mon soucis est quoi utilisé et m'y tenir, comprendre l'outil ! et m'y tenir si il me convient...

    donc, j'ai un soucis de conception...

     

    Edit :

    je pensais pas dire ça... mais bon...

    Sinon, FMDB est parfait je crois ? totale maitrise des requêtes etc.

    Mais ! je ne voulais plus utilisé objective-c, je pensais que swift3 était mature et utilisé de façon aussi userFriendly que ces consorts... (niveau SQL) j'aimerais du 100% swift3

     

     

    Edit 2:

    bon, j'ai trouvé une piste avec GRDB.swift... je vous la post bientôt ;)

  • Joanna CarterJoanna Carter Membre, Modérateur


    à  vouloir faire simple et dynamique, ça devient une usine à  gaz :(




     


    Avec Swift, tu as la choix - simple ou dynamique  8--)


     




    les concepts de type optional() je commence à  m'y faire, et le reste aussi, mais je ne me sent pas si naze que ça, mais ! oui j'ai des lacunes...




     


     


    Les optionals sont là  pour te protéger contre les pièges de nils inattendus. C'est assez facile en concept mais il faut apprendre comment ils se comportent et, si tu te trouves a avec les ! partout, il faut repenser. Les if let et guard let sont là  pour t'aider mais il faut arrêter de penser en Java où (presque) tous est permis et penser de la sécurité et fiabilité de ton code.


     




    mon principale soucis, je le redis... c'est la maitrise de ce framework et de géré mes tableaux... niveau objets/méthode etc.


    je trouve que je m'en sort pas trop mal non ?




     


     



    J'ai noté que, souvent tu manques les try catch autour plusieurs APIs de GRDB. Regardes bien le code exemple et lis le code source et tu y trouveras où il faut les utiliser. Encore une fois, c'est pour ta protection.


     




    Mais quoi utilisé ? CoreData  ?


    Qui m'interdira (si j'ai tout pigé) d'utilisé une base existante, ou pire de faire des backup sur une autre base (miroir) MySQL ? edit: et surtout pas de possibilité de faire des requêtes un peut exotique...



     


     



    Oui, tu ne pourrais utiliser une BDD existante dans l'appli mais tu pourrais "importer" les données dans Core Data et après, tu n'aurais plus de besoin. Il y a une appli OS X qui s'appelle Base, qui peut exporter les données d'une BDD SQLite en CSV ou autres formats si tu préfères.

     

    Côté requêtes "exotiques", Je ne suis pas encore tombée sur une telle requête, mais, encore une fois, il faut repenser qu'est-ce qu'il veut dire d'avoir une BDD d'objets à  la place des tables, rangs et colonnes.

     




    je pensais pas dire ça... mais bon...


    Sinon, FMDB est parfait je crois ? totale maitrise des requêtes etc.



     



    Bah non ! Tu penses de recommencer avec encore un framework ? Tiens à  ce que tu as déjà  appris et le maitriser ; la plupart de tels frameworks sont écrit de la même manière. C'est plutôt toi qui dois changer, pas les frameworks  :-*


     




    Mais ! je ne voulais plus utilisé objective-c, je pensais que swift3 était mature et utilisé de façon aussi userFriendly que ces consorts... (niveau SQL) j'aimerais du 100% swift3




     


     


    Ce n'est pas Swift qui n'est pas user friendly, c'est plutôt SQL, surtout dans le monde de programmation d'aujourd'hui avec les objets, classes, structs, type-safety, etc. Les frameworks doivent être compliqués à  raison qu'il doivent traduire le monde de SQL vers le monde des objets.


     


    --


     


    Mais, dis moi : tu parles du dynamisme mais, surement, les tables en SQL ne sont pas dynamiques. Leurs structures sont figées au moment de conception, comme les structs et les classes.


     


    Si tu veux vraiment rester avec la BDD SQLite, tu pourrais créer une struct par table ; tu connais le structure de chaque table, il ne reste que les "copier" en structs. Après ça, quand tu récupères les données d'une table, il ne reste qu'à  créer une liste d'instances de la struct qui corresponde. Oui, il faudrait créer une méthode pour chaque table mais ce n'est pas difficile a faire.

  • merci :)


     


    je vais lire et digéré toutes tes infos.


     


    Pour le coté difficile ou non de créer une méthode pour chaque table/objet, aucun soucis, mais c'est surtout répétitif... donc pas "objet style" une classe qui créer tout ça de façon dynamique, voilà  mon idée.


     


    bref, pour te répondre, la structure d'une base de données est bien définit/réfléchi en amont, MAIS ! peut évolué, ça on le vois bien dans le temps, besoin du client etc. Voilà  pourquoi je voulais m'amusé à  ça, pour comprendre. Et c'est inconcevable d'aller remettre les main dans le code tout le temps... 


  • Joanna CarterJoanna Carter Membre, Modérateur


    Pour le coté difficile ou non de créer une méthode pour chaque table/objet, aucun soucis, mais c'est surtout répétitif... donc pas "objet style" une classe qui créer tout ça de façon dynamique, voilà  mon idée.




     


    Si tu regardes dans le source de GRDB, tu y trouveras beaucoup de répétitif ; ça, c'est la nature des classes/structs adaptatrices.


     


    Il y a pas mal de fonctionnalités que tu n'as pas encore touché dans GRDB ; il te faut l'étudier.


     


    Là  tu as déjà  le concept d'un dictionnaire assez sophistiqué dans la classe Row ; la méthode value(named:_) renvoie un objet qui implémente le protocol DatabaseValueConvertible, qui te donne le type de chaque valeur comme un enum, avec lequel tu peux vérifier et convertir la valeur selon tes besoins.


     


    Tu pourrais bien créer une struct/classe qui s'occuperait des conversions mais, là  dedans, tu devras écrire le code qui fasse les conversions pour chaque table "à  la main"


     




    bref, pour te répondre, la structure d'une base de données est bien définit/réfléchi en amont, MAIS ! peut évolué, ça on le vois bien dans le temps, besoin du client etc. Voilà  pourquoi je voulais m'amusé à  ça, pour comprendre. Et c'est inconcevable d'aller remettre les main dans le code tout le temps... 




     


     


    Si tu écrivais bien tes structs/classes pour les conversions, modifier le code pour les colonnes additionnelles ne mettra pas beaucoup de temps.


     


    à‰tudes, études, études    8--)

  • toolsDevtoolsDev Membre
    février 2017 modifié #26

    j'ai enfin réussi, mais je dois à  chaque fois préciser quelle colonne je veux afficher(dans le sens ou si je précise pas ce que je souhaite, je pourrais pas itérer !! :( )... bref, voici le dernier code :



    let dataTables = try connexion.inDatabase{db in try Row.fetchAll(db,"SELECT * FROM \(table)")}
    for dataTable in dataTables
    {
    let name:String = dataTable.value(named:"name")
    // en gros si je fais ça : self.arrayData.append(dataTable) pas possible d'itérer...
    self.arrayData.append(name)
    }


    class ViewController: UIViewController, UITableViewDataSource
    {
    var dataTable:[String] = []
    var tableView = UITableView()

    override func viewDidLoad()
    {
    super.viewDidLoad()

    guard let connexion = ConnexionSQLite.sharedInstance else
    {
    print("Erreur de connexion")
    return
    }
    var posY:Int = 30
    var index = 0

    for table in connexion.arrayNomTable
    {
    let button:UIButton = UIButton(frame: CGRect(x:10, y: posY, width:100, height:30))
    button.tag = index
    button.setTitle(table, for: .normal)
    button.addTarget(self, action:#selector(self.pushButton), for: .touchUpInside)
    self.view.addSubview(button)
    posY += 40
    index += 1
    }
    }

    func pushButton(_ sender: UIButton)
    {
    guard var connexion = ConnexionSQLite.sharedInstance else
    {
    print("Erreur de connexion")
    return
    }
    connexion.getDataTables(table:sender.currentTitle! as String)
    tableView = UITableView(frame: CGRect(x:130, y: 30, width:200, height:400))
    tableView.backgroundColor = UIColor(red: 85/255, green: 175/255, blue: 101/255, alpha: 1.0)
    tableView.dataSource = self
    self.view.addSubview(tableView)
    for data in connexion.arrayData
    {
    self.dataTable.append(data)
    }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
    let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
    cell.textLabel?.text = self.dataTable[indexPath.row]
    return cell
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
    return self.dataTable.count
    }

    override func didReceiveMemoryWarning()
    {
    super.didReceiveMemoryWarning()
    }
    }

    Mais je ne comprends pas ou placer le refresh des données affichés dans la tableView :



    self.tableView.reloadData()

    j'ai tenté plusieurs essais en vain...


    Et j'obtient une concaténation des données dans la tableView...


     


    Si vous pouviez juste me dire votre ressenti, je trouve pas ça très jolie/clair/propre


     


    En regardant mon code :




    for data in connexion.arrayData
    {
    self.dataTable.append(data)
    }

    vous constatez que je perds la référence des datas donc je les place dans ma vue dans un autre tableau, j'ai trouvé que cette solution pas très élégante... 


     


    Merci ;)


  • Joanna CarterJoanna Carter Membre, Modérateur
    Ne remplaces pas la UITableView. Tu peux le mettre en place dans le storyboard et la laisser là .


    La tableView ne contient aucune donnée, Dans l'action pour le bouton, il ne faut que changer la source des données, comme je t'ai déjà  montré.
  • toolsDevtoolsDev Membre
    février 2017 modifié #28

    Je ne comprends pas ce que tu me dis :




    Ne remplaces pas la UITableView. Tu peux le mettre en place dans le storyboard et la laisser là .




    Elle fonctionne déjà  et sans le passer par le storyboard. (ça change quoi ? Via le code je suis maitre de ce que je fais, je peut plus intervenir que via l'interface graphique qui (je trouve, dans mon cas présent) surcharge la vision d'ensemble...


     




    La tableView ne contient aucune donnée, Dans l'action pour le bouton, il ne faut que changer la source des données, comme je t'ai déjà  montré.




     


    Pourtant,  à  présent, ma tableView est bien remplis avec les bonnes données, c'est "juste" au niveau du reload que j'ai un soucis (pour la changer les datas à  chaque pression d'un nouveau bouton. Et ma data source est pourtant bien affecter comme suis :



    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
    let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
    // affectation de mes données :
    cell.textLabel?.text = self.dataTable[indexPath.row]
    return cell
    }

    Quand tu me dis "ce que je t'ai montré" tu parles des tes 5 méthodes (boutons) et leurs affectation de la data source ?


    Parce que, tout est identique à  ton idée mais sans la répétition et les plusieurs tableau, moi j'en ai juste un du coup :



    class.....

    var dataTable:[String] = []

    func pushButton(_ sender: UIButton)
    {
    guard var connexion = ConnexionSQLite.sharedInstance else
    {
    print("Erreur de connexion")
    return
    }
    connexion.getDataTables(table:sender.currentTitle! as String)
    tableView = UITableView(frame: CGRect(x:110, y: 30, width:300, height:400))

    tableView.dataSource = self
    tableView.delegate = self

    self.view.addSubview(tableView)

    for data in connexion.arrayData
    {
    self.dataTable.append(data)
    }
    // Donc, si je dis pas de bêtises, ici c'est bien ça place, mais rien ne ce refresh :
    tableView.reloadData()

    Tout ça fonctionne bien dans mon code et ça via une boucle qui construit mes boutons à  la volé.(Et comme tu le vois je n'ai aucun soucis avec la récupération de mes senders)


     


    Donc, désolé, mais je comprends pas trop ou tu veux en venir ?


  • Joanna CarterJoanna Carter Membre, Modérateur
    février 2017 modifié #29


    Je ne comprends pas ce que tu me dis :




     


    à‰videmment.


     




    Pourtant,  à  présent, ma tableView est bien remplis avec les bonnes données, c'est "juste" au niveau du reload que j'ai un soucis (pour la changer les datas à  chaque pression d'un nouveau bouton. Et ma data source est pourtant bien affecter comme suis :




     


     


    Mais la méthode est appelé combien de fois ?


     


     




    Quand tu me dis "ce que je t'ai montré" tu parles des tes 5 méthodes (boutons) et leurs affectation de la data source ?




     


     


    Oui


     


     




    Parce que, tout est identique à  ton idée mais sans la répétition et les plusieurs tableau, moi j'en ai juste un du coup :




     


     


    Est c'est là  ton problème.


     


     





    class.....

    var dataTable:[String] = []

    func pushButton(_ sender: UIButton)
    {
    guard var connexion = ConnexionSQLite.sharedInstance else
    {
    print("Erreur de connexion")
    return
    }
    connexion.getDataTables(table:sender.currentTitle! as String)
    tableView = UITableView(frame: CGRect(x:110, y: 30, width:300, height:400))

    tableView.dataSource = self
    tableView.delegate = self

    self.view.addSubview(tableView) **************



     


     


    ********** Là , tu ajoutes la tableView à  sa superView.


     


    Chaque fois que tu passes par la boucle, tu crées une UITableView, tu mets self comme dataSource, tu mets self comme delegate, tu l'ajoutes à  sa superView qui la garde à  jamais et, c'est quand que tu la supprimes de sa superView ? Du coup, après 10 passes par la boucle, tu as 10 tableViews, tous connectées à  son dataSource te delegate - c'est fou !


     


    C'est pour les raisons comme ci que je t'ai conseillé de ne pas le créer en code. Tu n'as aucun besoin de remplacer (dans ton cas ajouter une deuxième, troisième, etc) ; il ne faut que changer la liste que tu utilises comme dataSource.


     


     





    class.....

    var dataTable:[String] = []

    func pushButton(_ sender: UIButton)
    {
    ...

    for data in connexion.arrayData
    {
    self.dataTable.append(data)
    }



     


     


    Et, en plus, chaque fois que tu passes par la boucle, tu ajoutes les prochaines données au même liste qu'avant que as appuyé sur le bouton. Du coup, tu une liste qui s'agrandisse chaque fois que l'on appuie sur un bouton.


     


    Il ne m'étonne pas que ne vois de change dans la/les tableView(s)


     


     




    Tout ça fonctionne bien dans mon code et ça via une boucle qui construit mes boutons à  la volé.(Et comme tu le vois je n'ai aucun soucis avec la récupération de mes senders)




     


     


    Mais, tout ça ne fonctionne pas bien ; si ça fonctionnait bien, tu n'aurais pas de soucis avec le reloadData()


  • toolsDevtoolsDev Membre
    février 2017 modifié #30


    à‰videmment.




    Mais encore ??




    Mais la méthode est appelé combien de fois ?




    Une seule fois, je ne peut pas appuyer sur plusieurs boutons (en tout ça pas là )...


     




    Est c'est là  ton problème.




    en quoi ça serait un soucis,  je vais pas faire 5 méthodes sender car j'ai 5 boutons quand même, la boucle les ajoute dans la même vue, je ne te suis pas du tout là . Elle les ajoute pas dans tableView.. ça n'à  rien à  voir pourtant.

     

    J'aurais 5 tableView d'empiler je verrais dans le simu les données s'empilerais non ? (Edit: je comprends ce que tu m'as dis, j'avais pas pensée à  ça ! mais un coup de remove superview devrait arrangé...)

     

    je vais lire tes explications, mais je ne vois pas pourquoi un boucle comme celle ci n'est pas faisable. Sinon, sacré répétition...

     

    bref, je vais voir avec



    subview.removeFromSuperview()


    puisque j'avais oublié sont existence... ça changerais tout

  • Joanna CarterJoanna Carter Membre, Modérateur
    février 2017 modifié #31

    Chaque fois que appuies sur un bouton, tu fais ce code :



    tableView = UITableView(frame: CGRect(x:110, y: 30, width:300, height:400))

    tableView.dataSource = self

    tableView.delegate = self

    self.view.addSubview(tableView)

    Du coup, tu crées une UITableView, tu connectes son dataSource et son delegate à  self (le viewController) et tu l'ajoutes comme subView de ta self.view. Dès ce moment, self.view retient la tableView et, la prochaine fois que tu appelles ce code, tu crées une autre aux même coordonnées, cette tableView couvrira la précédente afin que tu ne la puisses voir, mais elle reste là  tout même, demandant les données par son dataSource.


     


    Mais, tu as raison en disant qu'il faut appeler reloadData() au fin du code du bouton.

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