Problème de design pattern, comment le controller sait quelle vue représente quel modelObject ?

-------------------------------------------------- L'Histoire --------------------------------------------------

Bonjour à toutes et à tous,

Avant j'étais dans le mal, mais ça c'était avant ! Enfin j'espère..

Je travaille depuis pas mal de temps sur une application personnelle d'édition musicale, et plus j'apprends plus je me rend compte que ce que j'ai écris au début est horrible.
Pour résumer la situation je me suis étalé de tout mon long dans tous les pièges possibles, j'ai un fort couplage entre mon modèle et mes vues et j'ai bien évidemment un massive view controller. J'utilisais aussi NSKeyedArchiver pour sauvegarder mes données, mais j'ai maintenant besoin de plus de flexibilité et je veux tout réécrire pour utiliser CoreData. Il est donc maintenant temps de payer les erreurs et les dettes du passé.

J'ai bien lu un peu tout ce que je pouvais trouver sur le design pattern MVC et je tente donc de l'implementer au mieux dans mon application.
Je pense avoir bien compris le principe du MVC selon Apple, mais il y a quelque chose qui m'échappe dans l'implémentation pratique, je ne comprends pas comment le controller fait pour savoir quelle vue est la représentation à l'écran de tel objet du model.

Dans tous les exemples que j'ai pu trouver sur internet, les tutoriels utilisent une UITableView et joignent le modèle à la vue grace à un index, ou alors font une référence à la vue par un IBOutlet puis bêtement affiche les valeurs en fonctions des données du modèle. cela reste facile, mais je n'arrive pas à voir comment faire avec des vues crées programmatiquement.

Le seul topic qui se réfère de loin à une implémentation concrète que j'ai trouvé sur CocoaCafé est

Mais AliGator et FKDEV ne tombent pas d'accord. Ali propose d'utiliser les delegate, et si je vois bien comment une vue peut notifier le controller par ce moyen, je ne vois toujours pas comment faire lier ensuite tout cela au modèle. Alors que FKDEV propose d'ajouter le modèle à la vue. J'avais initialement fait comme cela également, mais le problème est que la relation inverse n'est pas connue, autrement dit, si je change programmatiquement mon modèle, je n'ai pas de moyen d'update les vues correspondantes, à moins de boucler sur toutes les vues et de regarder si le modèle de la vue est celui que je viens de changer. De plus cela ne me semble pas très propre..

-------------------------------------------------- Le Code --------------------------------------------------

J'ai crée un sample project qui illustre cela et je l'ai mis sur Github ici.

J'ai donc un modèle qui reprend le strict minimum pour représenter une partition musicale.


J'ai écris le minimum de logique nécéssaire pour illustrer tout cela. On a donc un écran avec un bouton qui permet d'ajouter des notes dans un temps sur une ligne. Si j'arrive bien à créer le modèle et les vues en fonctions des actions de l'utilisateur et à recréer l'écran au chargement, les choses se compliquent lorsque que je veux effectuer une action en cliquant sur une des notes:


En effet admettons que je change l'orientation de la note, comment faire pour mettre à jour le modèle ? Même problème avec Delete qui va effacer la note actuelle et toutes les suivantes; s'il est facile d'obtenir les objets suivant dans le Store, comment faire encore une fois pour savoir quelles noteView sont à supprimer de l'interface?

Le code contient des commentaires aux endroits où il faudrait faire le liens entre la vue et le modèle dans le controller:

// Delegate method for MVCNote view when orientation changed:
-(void) orientationDidChange:(BOOL)orientationUp forNote:(id)note{
    NSLog(@"Orientation changed for note");
    // Update the model:

    // --------------------------------------------------------------
    // TODO: How to find which noteManagedObject is represented by this MVCNote?
    // --------------------------------------------------------------

}

-------------------------------------------------- Les Questions --------------------------------------------------

-La première est celle qui m'obsède depuis le début, comment le controller peut-il faire le lien entre le modèle et la vue lorsque cette dernière est crée programmatiquement et qu'on n'utilise pas de UITableView pour afficher les données ou une référence direct à un IBOutlet ?

-Bien que le code ici soit relativement simple, on sent rapidement que je vais retomber sur un massive view controller. Je pense qu'il est préférable de mettre les méthodes de communication avec le modèle dans une classe à part, ce qui m'évitera d'avoir à les réécrire ailleurs si jamais. Mais selon vous, le code qui est sous #pragma mark -- VIEW SPECIFIC CODE --, doit-il être déplacé dans les classes des vues ?
Quelle stratégie est-il possible d'adopter pour empêcher la logique de placement des vues de trop enfler et où doit-elle être?

-Est-ce que ma classe MVCNote doit hériter directement de NSObject ou de UIImageView ? Dit autrement, est-ce que les classes des vues qui représentent les objets du modèle à l'écran, doivent dans l'idéal être des NSObject contenant quelques attributs et une UI(Image)View, ou directement une UI(Image)View avec des attributs? Quelle est la différence fondamentale ?

-Je recalcule à chaque fois la position de toutes les notes quand on charge la partition, si je conçois bien qu'il est impur de stocker le x et le y dans le model, il serait bien pratique d'avoir ces informations au lieu de les recalculer à chaque fois. En effet dans la réalité et en production ce sera une opération complexe qui demande énormément de choses, bien loin de notre exemple, pour augmenter les performances il serait donc bien de ne recalculer la position de tous les éléments que quand on change de device ou d'orientation par exemple. Quel serait le meilleur moyen d'y arriver ? Un autre Store dans CoreData ?

Merci beaucoup de m'avoir lu, j'espère que vous aurez quelques réponses pour moi car je suis vraiment dans la panade avec mon code actuel et je n'ai qu'une envie, c'est de revenir dans le droit chemin. <3

«1

Réponses

  • Ha ben ça c'est une question !
    Je prends ma respiration, je lis (tout) et je reviens si je comprends...

  • Moi je pensais plutôt sortir acheter une boîte d'aspirine, avant de répondre. C'est dense comme topic. Franchement, Mayerick, je crois que tu n'as pas vraiment compris le concept du MVC. Je vais tenter de répondre à tes attentes, mais pas en une seule fois. Il y a matière à plusieurs topic, là. Je m'y met ce soir (course et cuisine pour le moment)

  • Bonjour à tous,

    Merci pour vos réponses, et ne vous inquiétez pas je fournis l'aspirine et/ou le café :)

    Oui c'est plutôt de la bonne question je veux bien le concéder et en effet il y a matière à plusieurs topics. Oui Draken je sens bien qu'il me manque une brique ou plus..

    Je vais essayer de faire simple, la situation peut se résumer comme cela:

    1) L'utilisateur appuie sur un bouton
    1.1) Le viewControler répond à l'évènement:
    1.2) Le viewController crée un NSManagedObject et l'insère dans le store
    1.3) Le viewController crée une vue qui représente le NSManagedObject pour l'UI et l'affiche à l'écran

    2) L'utilisateur clique sur une vue à l'écran
    2.1) Le viewController affiche un UIMenuController

    3) L'utilisateur clique sur un menuItem; ex: Orientation:
    3.1) La vue répond à ce message et se redessine
    3.2) Grace à un délégué, le viewController est notifié que la vue à changé et que le modèle doit être mis à jour
    3.3) Je suis *****, je n'ai pas de moyen de savoir quel objet du modèle doit être mis à jour.

    Voilà, c'est la logique principale.

    Maintenant concernant ces trois points, j'ai plusieurs remarques et questions.

    Pour le 1),

    • Bon on a compris, bien que le viewController ajoute les vues à l'écran et les datas dans le store, il n'y a pas de moyen de savoir quelle vue représente quel objet dans le store.
    • Est-ce au viewController de faire tout cela ?

    Pour le 2),

    • À qui incombe la responsabilité de répondre au tapGesture, la vue ou le controller ? Je sens que ça devrait être la vue.
    • À qui incombe la responsabilité de crée le UIMenuController?

    Pour le 3),

    • À qui incombe la responsabilité de répondre au UIMenuItem cliqué par l'utilisateur ? Dans le sample que je fournis, un @selector() est géré par la vue (toggleOrientation) et un est géré par le controller (removeCurrentAndFurther), je sens bien que ça ne va pas, mais je vois aussi que dans le cas de removeCurrentAndFurther, la vue ne peut pas s'en charger à elle seule, car le modèle doit être manipulé.
  • klogklog Membre
    avril 2018 modifié #5

    Ce sont les controllers qui font la glue entre le modèle et la vue... C'est eux qui doivent garder la relation de correspondance entre les uns et les autres. C'est ce que font par exemple les NSOutlineView qui conservent une représentation interne en parallèle du modèle pour stocker les "liens" entre vue et données. Si des controllers de vues (vue note) ne sont pas suffisants, car ils ne permettent pas d'avoir une vue d'ensemble de la structure (partition), il faut en toute logique un controller de partition.

    Si ta partition est par exemple une séquence de vues et une séquence de données, tu peux créer une séquence interne dans un controller de partition qui conserve les liens ordonnés vue / donnée.

    Bien sûr, il faut que toute modification d'un côté (données) se répercute de l'autre (vues) et réciproquement. Ça peut être fait de bien des façons par délégation, observation, etc.

  • DrakenDraken Membre
    avril 2018 modifié #6

    N'ayant pas touché l'objective-C depuis des années, je n'ai pas compris grand chose à ton code. C'est fou comme on oublie vite les trucs casse-pieds. Bon, je ne vais pas faire de la propagande pour le Swift mais tu devrais vraiment penser à sauter le pas .. o:)

    3) L'utilisateur clique sur un menuItem; ex: Orientation:
    3.1) La vue répond à ce message et se redessine
    3.2) Grace à un délégué, le viewController est notifié que la vue à changé et que le modèle doit être mis à jour
    3.3) Je suis *****, je n'ai pas de moyen de savoir quel objet du modèle doit être mis à jour.


    Pour le 1),

    Bon on a compris, bien que le viewController ajoute les vues à l'écran et les datas dans le store, il n'y a pas de moyen de savoir quelle vue représente quel objet dans le store.
    Est-ce au viewController de faire tout cela ?

    Oui, c'est au viewController de le faire.

    Chaque vue possède un attribut numérique .tag, contenant 0 par défaut. Tu peux y stocker une valeur unique pour identifier la vue lors d'un événement utilisateur. Par exemple, la première note peut avoir le tag 1, la seconde le tag 2, etc .. Rien de plus simple à utiliser.

    Quand le viewController reçoit un évenement indiquant qu'une note a été modifiée il lui suffit de lire le tag pour connaître son numéro d'identité.

    https://developer.apple.com/documentation/uikit/uiview/1622493-tag

    Si l'utilisation d'une seule valeur numérique pour identifiant ne conviens pas, tu peux aussi sous-classer UIView pour créer une nouvelle classe graphique avec des propriétés d'identification spécifiques (noms, types, etc.).


    Pour le 2),

    À qui incombe la responsabilité de répondre au tapGesture, la vue ou le controller ? Je sens que ça devrait être la vue.

    Non, Non, NON ... PAS LA VUE, JAMAIS LA VUE !

    Le viewController doit être averti de l'événement "L'UTILISATEUR VIENT DE MODIFIER LA NOTE IDENTIFIANT 134" pour réagir en conséquence. La vue est un objet statique, elle ne fait rien par elle-même, se contente d'afficher des choses et réagit aux ordres du contrôleur. Un peu comme une femme dans les films américains des années 50 : soit belle, ne fais rien et soit obéissante.


    Pour le 3),

    À qui incombe la responsabilité de répondre au UIMenuItem cliqué par l'utilisateur ? Dans le sample que je fournis, un @selector() est géré par la vue (toggleOrientation) et un est géré par le controller (removeCurrentAndFurther), je sens bien que ça ne va pas, mais je vois aussi que dans le cas de removeCurrentAndFurther, la vue ne peut pas s'en charger à elle seule, car le modèle doit être manipulé.

    Même réponse que pour le 2). La Vue ne doit RIEN FAIRE ! C'est le contrôleur qui doit TOUT GERER.

  • DrakenDraken Membre
    avril 2018 modifié #7

    -Bien que le code ici soit relativement simple, on sent rapidement que je vais retomber sur un massive view controller. Je pense qu'il est préférable de mettre les méthodes de communication avec le modèle dans une classe à part, ce qui m'évitera d'avoir à les réécrire ailleurs si jamais.

    Bah oui, il n'y a absolument aucune raison pour placer le code du Modèle dans le viewController. Tu dois créer une classe spécifique pour le modèle avec son code à part, COMPLETEMENT INDEPENDANTE de la technologie de sauvegarde sous-jacente. Le viewController ne doit pas savoir si c'est du CoreData, des fichiers textes, du binaire ou un mini-démon avec un parchemin magique (sisi c'est possible, enfin dans l'univers du Disque Monde de Terry Pratchett).

    Le viewController doit juste être capable de créer le Modèle et lire/écrire/effacer des notes.

  • MayerickMayerick Membre
    avril 2018 modifié #8

    @klog

    Merci pour ta réponse. En effet dans la théorie je suis entièrement d'accord avec toi. C'est au viewController de faire la glue entre le modèle et les vues.
    Si je ne m'abuse j'ai déjà un controller de partition PartitionViexController et oui une partition est
    une séquence de lignes qui contiennent des temps qui contiennent des notes.

    Concernant la dernière partie de ton message, c'est justement l'implémentation pratique/concrète de tout cela qui pose problème :)

    @Draken

    Merci beaucoup également !
    D'accord pour le code du modèle, je vais modifier cela. Bien entendu également pour les remarques 2 et 3.

    Par contre pour les tag je ne pense pas que ce soit la solution appropriée, je connais cet attribut et en effet j'aimerai mieux faire autremement.

    Quand l'utilisateur clique sur la troisième note en partant de la gauche, j'aimerai pouvoir dire: Il s'agit de la note X dans le Temps 3 de la Ligne 1 et donc pouvoir mettre à jour currentTemps et currentLigne si le derniers temps était sur une autre ligne.

    La seule solution que je vois serait de créer une classe glue qui aurait une référence sur l'objet du modele (NoteMO) et sur la vue (MVCView):

    @interface noteController : NSObject
    @property (strong, nonatomic) Note *noteMO;
    @property (strong, nonatomic) MVCView *noteView;
    

    puis de stocker ça dans une hiérarchie de dictionnaires dans le controller:
    @{@"numeroLigne":@{@"numeroTemps": [<noteController*>] } }
    Ce qui donne de manière écrite: un dictionnaire de Ligne, qui contient en clé le numéro de la Ligne et en objet un dictionnaire qui contient en clé le numéro du Temps et en objet un NSArray de note (pfiou :))

    C'est un peu ce que je faisait avant, mais cela me gène un peu car je suis entrain de recréer/mimer la structure du modèle ? C'est à dire finalement créer réplique de la structure qui ne vivra que dans le controller ?
    Et du coup je n'ai pas d'autre choix que de boucler sur le dictionnaire pour trouver la correspondance entre le noteMO et noteView, et je trouve ça pas très propre, en plus de réclamer pas mal de ressources.

    Je vois bien qu'il me manque quelque chose, il doit bien avoir un moyen de faire un lien concret entre un objet du model et la vue qui le représente à l'écran sans être obliger de boucler sur tous les éléments.

    un mini-démon avec un parchemin magique (sisi c'est possible, enfin dans l'univers du Disque Monde de Terry Pratchett).

    J'aime cette idée :smiley:

    Je dois y aller mais j'adapte le code aux premiers conseils dès que je suis de retour !

  • MayerickMayerick Membre
    avril 2018 modifié #9

    @Draken a dit :

    À qui incombe la responsabilité de répondre au tapGesture, la vue ou le controller ? Je sens que ça devrait être la vue.

    Non, Non, NON ... PAS LA VUE, JAMAIS LA VUE !

    Le viewController doit être averti de l'événement "L'UTILISATEUR VIENT DE MODIFIER LA NOTE IDENTIFIANT 134" pour réagir en conséquence. La vue est un objet statique, elle ne fait rien par elle-même, se contente d'afficher des choses et réagit aux ordres du contrôleur. Un peu comme une femme dans les films américains des années 50 : soit belle, ne fais rien et soit obéissante.

    Une seconde, dans quelles conditions la vue doit notifier le controller alors? Qu'il y ai un tapGesture, un pan, ou un drag, dans tout les cas c'est le controller qui est responsable de l'évènement et qui initie le redraw de la vue, donc elle peut notifier pour quoi et quand ?

  • @Mayerick a dit :

    Une seconde, dans quelles conditions la vue doit notifier le controller alors? Qu'il y ai un tapGesture, un pan, ou un drag, dans tout les cas c'est le controller qui est responsable de l'évènement et qui initie le redraw de la vue, donc elle peut notifier pour quoi et quand ?

    Oui, dans tous les cas c'est le Contrôleur qui doit gérer les réactions de l'application suite à une action de l'utilisateur. Les seules notifications "légitimes" de la Vue sont des messages prévenant le Contrôleur que l'utilisateur vient de faire quelque chose (toucher un bouton, sélectionner une image, cocher quelque chose, modifier une valeur, etc..).

    Cela permet d'avoir une séparation stricte entre l'aspect graphique d'une application et son comportement.

  • Par contre pour les tag je ne pense pas que ce soit la solution appropriée, je connais cet attribut et en effet j'aimerai mieux faire autremement.

    Quand l'utilisateur clique sur la troisième note en partant de la gauche, j'aimerai pouvoir dire: Il s'agit de la note X dans le Temps 3 de la Ligne 1 et donc pouvoir mettre à jour currentTemps et currentLigne si le derniers temps était sur une autre ligne.

    La seule solution que je vois serait de créer une classe glue qui aurait une référence sur l'objet du modele (NoteMO) et sur la vue (MVCView):

    Pourquoi ne pas créer des tags personnalisés ?

    Tes notes sont présentes "physiquement" sur l'écran sont la forme d'une UIImageView contenant une petite image. Plutôt que d'utiliser un composant standard, tu peux en créer une version personnalisée en sous-classant UIImageView pour ajouter de nouvelles propriétés.

    J'ai fait quelque chose de similaire, dans un petit projet de jeu vidéo. Pour identifier la nature des différents objets présent sur l'écran, j'ai créé une classe Sprite en sous-classant une UIImageView pour ajouter une propriété natureObjet (un String) et un identifiant numérique.

    Quand un événement se produisait sur un Sprite je n'avais qu'à tester sa propriété natureObjet sur savoir si c'était un "ALIEN", un "MISSILE", un "VAISSEAU JOUEUR" ou un "CANARD" et agir en conséquence.

  • Bonjour à tous,

    J'ai mis à jours le code en implémentant un ModelMediatorController qui est responsable des intéractions avec le modèle comme son nom l'indique et j'ai déplacé le code approprié dans ce nouvel élémént.

    @Draken
    Le problème n'est pas de réagir en conséquence pour la vue, le problème est ensuite de lier l'action sur la vue à la conséquence dans le modèle. Par exemple quand un Alien meurt:

    • il doit être retiré de la vue, ça c'est ok.
    • le modèle doit savoir quel Alien est mort parmi tous ceux éxistant. C'est ici que je ne comprends pas.

    Dans ton exemple, je veux bien que la vue soit une sous-classe de UIImageView, c'est d'ailleurs ce que je fais dans mon code avec l'attribut upOrientation.
    Mais à moins que cette sous-classe contienne une référence faible sur l'objet du modèle en attribut, je ne vois pas comment faire, c'est la solution de FKDEV mais ça ne me semble pas pur.

    Je cherche à savoir comment en cliquant sur une vue, on peut savoir que cette vue représente la Note X dans le Temps Y sur la Ligne Z.
    Je ne pense pas qu'un tag ou des attributs répondent à cette question. En effet comment savoir si la noteView avec le tag 153 appartient à la ligne 5 ou bien à la 6, qu'elle est sur le temps 3 ou 42 et enfin qu'elle représente la note A ou B dans ce fameux temps.

    Et je ne vois pas la solution élégante et pratique qui me permettra de faire ça sans:

    • Faire une référence du modèle sur la vue. // Solution de FKDEV
    • Faire une référence du tag de la vue ou pire de la vue dans le modèle et donc boucler
    • Essayer de bidouiller avec le X et le Y pour retrouver quel objet du modèle se trouverait ici si on avait à en placer un.

    Le problème des références est que la relation inverse n'existe pas (à moins de les croiser); si la vue référence le modèle, alors quand le modèle change programmatiquement, je dois boucler sur toutes les vues pour savoir la quelle est affectée. À l'inverse si le modèle contient une référence vers le tag de la view, je dois faire une requête à chaque fois que l'utilisateur change une vue et cela n'est pas MVC.

    Est-ce que je pourrais également avoir ton avis s'il te plait sur la classe noteController que j'ai déclaré plus haut ainsi que le fait d'avoir une structure qui mime la représentation du modèle pour accéder à ces noteController grâce à l'état de currentLigne et currentTemps ? Elles servent de lien entre le modèle et les vues et existent uniquement au run time, mais ne résolvent pas le problème de boucle quand on clique sur une vue.

    Un ami vient de me parler des design pattern composite et chain of responsibility, je vais aller jeter un oeil.

    Peut-être que je cherche trop loin quelque chose de simple, mais je ne sais pas pourquoi, les solutions évoquées plus haut ne me semblent pas pure.

    Merci encore d'accorder du temps à mon problème @Draken j'apprécie vraiment tes réponses.

  • @Draken a dit :
    J'ai fait quelque chose de similaire, dans un petit projet de jeu vidéo. Pour identifier la nature des différents objets présent sur l'écran, j'ai créé une classe Sprite en sous-classant une UIImageView pour ajouter une propriété natureObjet (un String) et un identifiant numérique.

    Quand un événement se produisait sur un Sprite je n'avais qu'à tester sa propriété natureObjet sur savoir si c'était un "ALIEN", un "MISSILE", un "VAISSEAU JOUEUR" ou un "CANARD" et agir en conséquence.

    @Mayerick a dit :
    Le problème n'est pas de réagir en conséquence pour la vue, le problème est ensuite de lier l'action sur la vue à la conséquence dans le modèle. Par exemple quand un Alien meurt:

    • il doit être retiré de la vue, ça c'est ok.
    • le modèle doit savoir quel Alien est mort parmi tous ceux éxistant. C'est ici que je ne comprends pas.

    L'identification de l'Alien se faisait en combinant les propriétés de type et l'identifiant numérique, que j'aurais du appeler numéro de référence pour être plus précis.

    Quand le Contrôleur était avertis d'un événement sur un objet graphique, il lisait le TYPE et l'IDENTIFIANT pour déterminer qu'il s'agissait de ALIEN 3, ou de CANARD 6.

    Modèle du jeu :
    - position du joueur
    - Une liste d'aliens
    - Une liste de missiles
    - Une liste de canards

    L'identifiant était tout simplement la position de l'objet dans sa liste (un simple tableau). Il y avais des défauts à ce système, que je ne présenterais pas ici, par manque de temps. L'idée générale est de tager les objets graphiques avec un marqueur permettant au viewController de l'identifier. Si je devais refaire un jeu du même type, j'utiliserais probablement un dictionnaire à la place d'une liste.


    Je cherche à savoir comment en cliquant sur une vue, on peut savoir que cette vue représente la Note X dans le Temps Y sur la Ligne Z.
    Je ne pense pas qu'un tag ou des attributs répondent à cette question. En effet comment savoir si la noteView avec le tag 153 appartient à la ligne 5 ou bien à la 6, qu'elle est sur le temps 3 ou 42 et enfin qu'elle représente la note A ou B dans ce fameux temps.

    Là franchement, je ne comprend pas ta logique. L'appartenance de la Note sur le temps Y de la ligne Z est une information définissant l'état de la Note. CELA APPARTIENT DONC AU MODELE !

    Le tag permet d'identifier l'objet. Il suffit ensuite de demander les infos au Modèle !

  • MayerickMayerick Membre
    avril 2018 modifié #14

    Ok je crois que je cherche peut-être trop loin alors.
    Dans la pratique, il faut donc stocker les vues dans un NSArray appartenant au controller et boucler dessus, ou alors utiliser un dictionnaire en effet, je vais réfléchir à cela.

    J'ai peur que mon histoire de musique n'aide pas à la compréhension du modèle mais dans le fond c'est la même chose que d'avoir :
    Une Etagere (Partition) qui contient des Rangee (Ligne) sur lesquels sont disposé des Vase (Temps) dans lesquels il y a une Fleur (Note) de couleur rouge par défaut.

    On a juste un bouton pour ajouter un Vase à la Rangee en cours et quand on clique sur une Fleur, via un UIMenuController on peut en changer la couleur vers bleu ou casser le Vase et tous les suivants, comme si on mettait son bras sur l'étagère et qu'on les renversait tous en allant sur la droite.

    Ce n'est bien sûr pas pressé et je comprendrais parfaitement que tu aies mieux à faire, mais cela m'aiderait beaucoup de voir de manière concrète comment tu lies tout ça ensemble dans une application. Bien sûr en Swift, avec ou sans coreData, le plus simple possible, juste la fleur sans représentation visuelle du vase, ce qui ajouterai du code et une classe supplémentaire sans servir l'exemple. Vraiment j'en serais très reconnaissant.

    J'ai réfléchis aussi ardemment à réécrire mon application en Swift, je me dis qu'il faudra de toute façon le faire le jour où Apple dira: "La récréation est finie les enfants, c'est Swift ou rien".
    Les Pour:

    • Je peux repartir sur de bonnes bases,
    • Je dois de toute façon réécrire une bonne partie du code
    • La transition sera douloureuse dans tous les cas mais je ne veux pas accumuler les dettes
    • C'est assez "futur proof"

    Les Contres:

    • 20k lignes de logique à réécrire
    • J'aurais aimé me concentrer d'abord sur coreData
    • De nouveaux concepts à maitriser avant d'être prêt à commencer.

    Il va bien falloir que j'avance de toute façon et reprendre du début me permettra de simplifier le code même si cela va me couter un peu plus de temps pour retrouver l'état actuel de mon application avec l'intégration de coreData. Bref je me décide vite et bientôt.

    Merci comme toujours ! :)

  • Ce n'est bien sûr pas pressé et je comprendrais parfaitement que tu aies mieux à faire, mais cela m'aiderait beaucoup de voir de manière concrète comment tu lies tout ça ensemble dans une application. Bien sûr en Swift, avec ou sans coreData, le plus simple possible, juste la fleur sans représentation visuelle du vase, ce qui ajouterai du code et une classe supplémentaire sans servir l'exemple. Vraiment j'en serais très reconnaissant.

    Je te fais ça dés que j'ai quelques heures de libre.

  • Merci infiniment !

  • Une dernière précision car je sais que c'est tentant ici: il ne faut absolument pas utiliser de UICollectionView car si ça peut convenir pour ce simple exemple ce n'est pas ce qui convient pour l'application finale. Merci encore !

  • @Mayerick a dit :
    Une dernière précision car je sais que c'est tentant ici: il ne faut absolument pas utiliser de UICollectionView car si ça peut convenir pour ce simple exemple ce n'est pas ce qui convient pour l'application finale. Merci encore !

    UIScrollView ?

  • MayerickMayerick Membre
    avril 2018 modifié #19

    Bonjour Draken,

    J'avais présenté le problème dans sa plus simple expression, mais oui pour une UIScrollView qui fait défiler les rangées d'étagère vers le haut/bas, cela sous-entends juste d'aller à la rangée suivante quand celle en cours est pleine et c'est ajouter une petite sophistication, mais c'est exactement ce que je vais faire et c'est plus complet comme cela, donc je ne vais pas t'empêcher de le faire.
    Merci !

  • Joanna CarterJoanna Carter Membre, Modérateur

    Pour connecter les "notes" dans la vue à ses données, je crois le meilleur serait de suivre la même démarche que UITableView et UICollectionView, en créant une delegate pour que la vue puisse notifier le viewController quand elle à besoin d'une "cellule" (voire note)

    Donc, je prends l'exemple du delegate d'une UICollectionView, mélangé ave test idées des notes :

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
      {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "NoteCell", for: indexPath) as! NoteCell
    
        let note = dataModel.notes[indexPath.row]
    
        cell.note = note
    
        cell.delegate = NoteCellDelegate(IndexPath.row)
    
        self.noteDelegates.append(cell.delegate)
    
        return cell
      }
    

    Du coup, tu auras une list de delegates et chaque delegate sait l'index du note dans les données du modèle.

  • Bonjour @Joanna Carter

    Merci pour ta réponse. Oui je sens bien que je vais devoir mimer le comportement d'un UIBlablaDelegate et d'un UIBlablaDataSource. D'ailleurs tous les exemples que j'ai vu sur internet reprennent l'idée d'une simple liste affichée dans une tableView, mais ne vont jamais plus loin. Du coup j'ai du mal à transposer le principe dans une structure imbriquée comme la mienne et avec des vues customs, mais je réfléchis dans ce sens.
    Je vais tout réécrire en Swift et j'aimerai être sûr de repartir sur de bonnes bases et de ne pas recommencer avec une horreur mal implémentée/architecturée dès le début. C'est sûr que de voir comment d'autres feraient pour résoudre ce problème aide beaucoup.

  • Joanna CarterJoanna Carter Membre, Modérateur

    Oh, et n'utilises pas les UIImageView pour tes notes. Ça consomme beaucoup de memoire.

    Il ne faut qu'avoir une image pour chaque note (balance, noire, croche, etc) et la dessiner au bonnes coordonnées dans une vue.

    Je te conseille de penser d'avoir une petite hiérarchie de classes non-visuelles qui décrit chaque des notes, pauses, mesures, etc

    UINotation

    • UINote
    • UIPause
    • UIMesure

    Chaque classe contiendrait : les coordonnées, la taille, l'orientation, une référence à l'image, etc.

    Plus d'idées à suivre

  • On a juste un bouton pour ajouter un Vase à la Rangee en cours et quand on clique sur une Fleur, via un UIMenuController on peut en changer la couleur vers bleu ou casser le Vase et tous les suivants, comme si on mettait son bras sur l'étagère et qu'on les renversait tous en allant sur la droite.

    Quand un vase est cassé, il faut effacer le reste de la rangée, mais faut-il aussi détruire les rangées en dessous ?

  • MayerickMayerick Membre
    avril 2018 modifié #24

    @Draken
    Eventuellement mais ce n'est pas obligatoire, car ça ne sert pas tant pas l'exemple, le simple fait de supprimer le vase suivant, si c'est bien fait, devrait déjà me montrer comment lier une action du modèle vers la vue. Je suppose que tu va répéter l'action sur la rangée suivante donc vraiment comme tu le sens c'est sans conséquence autre que de la sophistication à mon avis.

  • @Joanna Carter
    Merci pour ta réponse. Tu veux dire que ma classe MVCNote devrait hériter de NSObject et non de UIImageView, et que MVCNote devrait avoir une référence sur une UIImage et non une UIImageView ? C'est en partie l'avant dernière question de mon post initial.
    Le problème de stocker la position ou un index directement dans un objet est que tout est dynamique et peut changer du tout au tout en une action: par exemple on peut insérer une mesure entre deux existantes ou décaler des notes.
    Aussi ces classes feraient partie du modèle, et donc avoir une référence sur une vue, (mais peut-être que tu veux dire une UIImage et alors ne tiens pas compte de la cela) ou même une position propre à la vue (iPad Pro 12,9' ou iPad mini, on ne met pas le même nombre de notes sur une ligne), cela ne me semble pas très MVC, d'où mes questions ici.
    D'un autre coté comme je l'expliquais dans la dernière question de mon post initial, recalculer à chaque fois la position de chaque élément sera très gourmand en processeur. J'aurais donc besoin d'un cache de positions qui ne changera que si vous partagez la partition avec un ami qui à un iPad Pro 12,9" et vous un iPad classique par exemple. Si vous partagez la partition avec un ami qui a un iDevice avec le même écran, inutile de recalculer la position, on peut afficher sans rien recalculer. C'est l'idée générale, mais comme le soulignait Draken, cela pourrait facilement aller dans un autre post. Ceci dit j'apprécie les suggestions! :)

  • FKDEVFKDEV Membre
    avril 2018 modifié #26

    Quand on ajoute un temps au milieu d'une ligne, cela peut-il décaler des temps sur les lignes suivantes ou est-ce que la ligne s'agrandit ?

    Je ne comprends pas bien le rôle de la ligne, mais je ne connais presque rien en musique.
    Je pensais que les temps s'enchainaient et qu'on allait à la ligne en bout de page.

  • A vue de nez, je ferais une vue par temps, dans laquelle je représenterais les notes en dessinant les images des notes à la bonne position, un peu comme suggéré par Joanna.
    On pourrait aussi imaginer une solution à base de texte avec NSAttributedString en utilisant des caractères unicode pour représenter les notes (♩ ♪ ♫ ♬).
    Mais en fait le choix de la solution graphique dépend vraiment des interactions possibles, et en particulier de la sélection. Est-ce une sélection par note, par temps ? Un seul item à la fois ou plusieurs, de manière continue comme du texte, ou discontinu comme des items dans une tableview, etc.

    En ce qui concerne la solution de mettre un pointeur vers le modèle directement dans la vue, j'assume l'impureté.
    Pour moi la pureté doit résider dans le modèle et dans les vues génériques (UILabel, UImageView, etc).
    Le code métier qui permet d'agir sur le modèle doit lui aussi être pur.
    Une fois qu'on a compris ça, on est quasiment sauvés, le reste consiste à faire les compromis les mieux adaptés au contexte.

    Pour que des vues pures finissent par afficher des objets purs du modèle, il faut bien que les deux se rencontrent dans des lieux impurs. Si ce lieu est unique (le contrôleur), on obtient souvent un contrôleur difficile à maintenir.
    A partir de cette constatation, on peut soit créer des contrôleurs intermédiaires, soit donner plus de responsabilités aux vues. Le choix se fait en fonction du contexte.
    L'avantage des vues spécialisées, c'est qu'on peut les réutiliser à différents endroits du projet.

  • MayerickMayerick Membre
    avril 2018 modifié #28

    Bonjour @FKDEV

    Merci pour ta réponse.
    C'est compliqué de répondre mais oui tu as raison, les temps s'enchainent et on passe à la ligne suivante en bout de page. Cependant quand on ajoute une note dans une mesure, la mesure doit s'agrandir pour les accueillir. Mais une ligne fait une taille fixe (typiquement la largeur de l'iPad), donc si une mesure s'agrandit au point de ne plus rentrer dans la ligne en cours, elle doit être déplacé avec toutes ses notes sur la ligne suivante.
    => toute les notes dans les mesures précédentes sur la ligne en cours doivent être replacé pour avoir une position cohérente et remplir tout l'espace de la ligne. La même vérification de taille et de position doit être faite sur toutes les lignes suivantes, en effet on peut 'pousser' une mesure sur une ligne déjà pleine, il faut alors décaler la dernière mesure de cette ligne et ainsi de suite..
    Ici un exemple où tu vois qu'une mesure peut avoir une taille différente quand elle contient plus de notes:

    Pour la sélection oui j'ai voulu mimer du texte, ce qui donne dans mon app:
    Quand le curseur est sur un temps, une seule note est sélectionné:

    Quand on double clique sur le temps, toutes les notes du temps sont sélectionnées:

    Enfin il est possible de sélectionner des temps différent dans plusieurs mesures également:

    Donc en effet je suis bien d'accord sur une vue par note, (les temps ne sont que des container qui définissent la position dans la séquence, il n'y a pas vraiment de représentation graphique du temps).

    J'entends aussi tes arguments du derniers paragraphe et je dois encore les méditer car c'est très intéressant.
    S'il s'agit de donner plus de responsabilité aux vues je pense bien voir comment faire. Mon incompréhension était plus sur ces controller intermédiaires que je ne vois pas comment organiser et lier avec la vue et le modèle, mais oui tu résumes bien le coeur du dilemme ici ! Je suis assez curieux de voir la solution de Draken, et surtout un peu de concret !

    Je me rend compte que j'ai peut-être fais une crise existentialiste sur la pureté. o:) À voir ! :)
    Merci !

  • MayerickMayerick Membre
    avril 2018 modifié #29

    Bonjour à tous,

    Je réfléchis toujours à mon problème et comme tout le monde me conseille de donner plus de responsabilité aux vues je vais dans ce sens là, mais j'aimerais quand même être sûr d'une chose à propos du lien entre le modèle et la vue, j'ai fais un playground:

    class MyModelObject {
        var aProperty : Int;
        init(_ property : Int) {
            self.aProperty = property;
        }
    }
    
    class MyViewObject {
        var aViewProperty : Int;
        weak var aReferenceToTheModel : MyModelObject?;
        init(_ property : Int, _ modelReference : MyModelObject) {
            self.aViewProperty = property;
            self.aReferenceToTheModel = modelReference;
        }
    }
    
    class Controller {
        var modelReferences : [MyModelObject] = [MyModelObject](); // From CoreData, NSKeyedArchiver or literal, doesn't matter.
        var viewReferences : [MyViewObject] = [MyViewObject]();
    
        func addObject() {
            let aProperty = Int(arc4random_uniform(42));
            let aNewObject = MyModelObject(aProperty);
            // Create the model and save it:
            modelReferences.append(aNewObject);
            // Create the view:
            viewReferences.append(MyViewObject(aProperty, aNewObject));
            print("Model and View references successfully added");
        }
    
        func reactToViewInput(_ aView : MyViewObject? = nil) {
            // For the sake of exemple pick up a random viewObject:
            let aRandomView : MyViewObject = viewReferences[Int(arc4random_uniform(UInt32(viewReferences.count)))];
            // Something changed for the view, reflect it in the model:
            let newProperty = aRandomView.aViewProperty;
            aRandomView.aReferenceToTheModel?.aProperty = newProperty;
            print("We changed the model successfully");
        }
    
        func initiateAModelChange(_ aModelObject : MyModelObject? = nil) {
            // For the sake of exemple pick up a random ObjectModel:
            let aRandomModel : MyModelObject = modelReferences[Int(arc4random_uniform(UInt32(modelReferences.count)))];
            // Something changed for the model, reflect it in the view:
            let indexOfView : Int? = viewReferences.index(where: { $0.aReferenceToTheModel === aRandomModel }); // <- PAS D'AUTRES CHOIX QUE DE FAIRE UNE BOUCLE IMPLICITE OU EXPLICITE ?
            let affectedView : MyViewObject = viewReferences[indexOfView!];
            affectedView.aViewProperty = aRandomModel.aProperty;
            print("We changed the view successfully");
        }
    }
    
    let controller = Controller();
    controller.addObject();
    controller.addObject();
    controller.addObject();
    controller.reactToViewInput();
    controller.reactToAModelChange();
    

    Mon principal rebut était de devoir faire une boucle sur les vues quand le modèle change (le seul commentaire en français dans le code). En me renseignant un peu sur les références faible, je me rend compte que je pourrai aussi avoir avoir une référence faible dans l'objet model.

    Seulement comme je compte utiliser CoreData, je ne peux pas stocker directement la référence dans le model CoreData, @FKDEV est-ce que créer au runtime un wrapper de l'objet CoreData est une bonne idée pour avoir une référence de l'objet sur la vue ? En gros ça revient à rajouter une référence faible à un NSManageObject et à une MyViewObject dans la classe MyModelObject, serait-ce horrible ?

    @Draken, est-ce que j'oserai te demander un petit retour s'il te plaît ? Navré de te solliciter, si tu n'as pas le temps je comprends parfaitement !

    Merci à tous !

  • Joanna CarterJoanna Carter Membre, Modérateur

    Mauvaise piste. J'essayerai de faire un petit exemple plus tard

  • Courage, je te donne un premier jet ce soir.

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