PALoadingView

juillet 2010 modifié dans Objective-C, Swift, C, C++ #1
Bonjour à  tous,

Voici une vue permettant d'afficher une petite bulle d'attente.
Il y a plusieurs options comme l'affichage d'un voile derrière cette bulle, afin de montrer à  l'utilisateur que l'action effectuée bloque l'interface pendant un petit moment (ça peut être utile dans un login par exemple).
Plusieurs possibilité de réglage aussi comme la durée des fade in/out (qui peuvent aussi être désactivés), etc..
«1

Réponses

  • CeetixCeetix Membre
    16:58 modifié #2
    Ah tu gères trop ça tombe pile poil au poil ;)
    Merci !
  • 16:58 modifié #3
    Merci  :P
    Bon je me suis pas foulé pour la présentation mais comme je vous l'ai dis, vous en faite ce que vous voulez. Moi je me suis fait ça justement parce que c'était ultra-simple à  mettre en place  ;)
  • CeetixCeetix Membre
    16:58 modifié #4
    Ouai mais c'est cool et ça sert toujours. Vu que je fais de la connexion entre iPhone et MAc ça peut être sympa de rajouter ça. Par contre le soucis c'est qu'il se connecte directe il n'y a pas besoin d'attente. Donc ce serait juste pour le style...
  • CeetixCeetix Membre
    16:58 modifié #5
    Je up ce post car j'essai de me servir de ce loader mais j'ai un petit soucis.
    En effet je me mets dans mon app delegate et dans appdidfinishlaunching je commence à  parser un xml retiré du web.
    Je voudrais donc mettre le loader pendant que je parse.
    J'ai fait :

    <br />- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {&nbsp; <br />&nbsp; &nbsp; &nbsp; <br />&nbsp;  if ([event count] == 0) {<br />&nbsp; &nbsp; &nbsp; NSString * path = @&quot;http://www.wayfarer.fr/iphone.xml&quot;;<br />&nbsp; &nbsp; &nbsp; <br />&nbsp; &nbsp; &nbsp; self.activity = [PALoadingController controllerWithSuperview:window];<br />&nbsp; &nbsp; &nbsp; activity.descriptionLabel.text = @&quot;Connecting...&quot;;<br />&nbsp; &nbsp; &nbsp; <br />&nbsp; &nbsp; &nbsp; [self parseXMLFileAtURL:path];<br />&nbsp; &nbsp; &nbsp; <br />&nbsp; &nbsp; &nbsp; self.activity = nil;<br />&nbsp; &nbsp; &nbsp; return YES;<br />&nbsp;  }<br />}<br />
    


    Mais au lieu d'avoir un loader, j'ai toujours mon splashscreen qui se met pendant pas mal de temps (car le xml est assez lourd) dans mon loader par dessus. Il apparait juste 0,5 sec quand mon splashscreen disparait et que le parsage est terminé. Comment faire pour le faire apparaitre correctement?

    Merci ! :)
  • 16:58 modifié #6
    C'est normal, ton "parseXMLFileAtURL" bloque tout à  ce que je vois. Tu retournes YES quand il a fini de parser, donc ton splashscreen reste jusqu'à  ce que l'app intercepte ce fameux return YES.

    La meilleure méthode serait de faire le parsing dans un nouveau thread et de mettre self.activity à  nil une fois fini, via une notification ou quelque chose du genre.
    Mais je trouve ça bizarre que le parsing bloque ton appli... Normalement ça ne devrait pas. Donc je sais pas comment tu t'y prend.
    En tout cas, à  vue de nez, si le splashscreen dure le temps du parsing, c'est bien que ton [self parseXMLFileAtURL:path] bloque tout.
    Et même si ça ne bloquait pas l'appli, tu ne verrais pas ton "activity" s'afficher vu que tu le mets à  nil directement.
  • CeetixCeetix Membre
    16:58 modifié #7
    Bah je mets ça dans le meme thread car sinon mon splashscreen s'enlève et fait place à  un UITableViewController qui est cencé utiliser les données du xml parsé.
  • 16:58 modifié #8
    Bha donc tu n'as pas le choix vu que là  tu bloques le splashscreen pendant le parsing  :P
    Et puis rien ne t'empêches d'envoyer une notification lorsque ton parsing est terminé (sans monopoliser ton main thread) afin d'instancier la viewController censée gérer l'affichage de ces données.
    Pendant ce temps, tu auras juste la fenêtre de chargement.
  • CeetixCeetix Membre
    16:58 modifié #9
    Donc il faut mettre mon parsing dans un autre thread?
  • CeetixCeetix Membre
    16:58 modifié #10
    Bon ça marche bien maintenant. J'aimerai rajouté un label qui compte le nombre d'éléments.
    Je l'ai rajouté dans tes classes et sous IB.
    Dès qu'un element est parsé et donc créé j'incrémente une variable que je mets ensuite à  ce label. Le truc c'est qu'il ne change pas le label comme si celui-ci était bloqué.

    Et depuis que j'ai mis le loading dans un nouveau thread j'ai une tonne de messages sur la console (cf image)
    Je comprends pas pourquoi  :(

  • savepandasavepanda Membre
    16:58 modifié #11
    C'est certainement parce que tu as oublié de créer un autoreleasepool dans ton thread.
  • AliGatorAliGator Membre, Modérateur
    mars 2010 modifié #12
    C'est parce que tu as voulu créer un thread sans avoir lu la doc sur le Threading Programming Guide avant.
    Passer son appli en multithreeading et créer des nouveaux threads nécessite un minimum de choses à  savoir, la création d'une AutoReleasePool (chose que tu n'as manifestement pas fait), la protection des variables et la synchronisation (mutex, @synchrinize,...), les risques de deadlock, ...

    Bref, faut pas se lancer dedans avant d'avoir lu le Programming Guide et suivre les pratiques obligatoires pour mettre en place un thread. C'est pas super compliqué mais ça nécessite de la rigueur et de comprendre les problématiques et risques (protection ressources partagées, update de l'UI dans mainthread, deadlock...) pour s'assurer qu'on les évite. 
  • CeetixCeetix Membre
    16:58 modifié #13
    En effet j'ai lu un peu la doc et créé un autoreleasepool dans la méthode que j'appelle.
    Par contre dans le programming guide je ne vois pas où ils indiquent comment changer un élément d'une UI .


  • AliGatorAliGator Membre, Modérateur
    16:58 modifié #14
    Threading and your User Interface (et tout les autres trucs qui sont mentionnés dans les Design Tips sont à  suivre également d'ailleurs)

    Sinon tu as d'autres docs importantes à  lire si tu commences le threading pour avoir conscience du LifeCycle de ton application, genre Application's Life Cycle : Do not block the main thread.


    Mais de manière générale, il y a plein de trucs importants dans le Threading PG : comme je l'ai dit, créer un thread à  la base c'est simple, mais il faut déjà  être sûr que c'est bien la réponse à  notre problématique (car cela a des conséquences), qu'un mécanisme existant, en particulier des méthodes asynchrones (avec utilisation de delegates pour les notifications et resynchronisations) comme il en existe un peu partout dans le framework Cocoa, n'est pas plus adapté... (pour le parsing XML t'as tout ce qu'il faut, créer un thread pour faire du parsing XML alors qu'on a un parseur XML asynchrone est de l'overhead et déconseillé pour moi), et regarder sinon s'il n'y a pas d'autres solutions plus adéquates (genre NSOperation, etc)

    Là  si tu n'as jamais fait de threads et vu les petits trucs qu'il va te falloir vérifier pour s'assurer que l'utilisation d'un thread ne risque pas de planter aléatoirement ton appli (par défaut de synchronisation ou accès concurrent à  des ressources que tu n'aurais pas protégées par mutex, etc), alors que tu peux éviter tout ça et que NSXMLParser a tout ce qu'il faut pour faire du parsing asynchrone (parsing SAX événementiel)... tu risques de te compliquer la vie et introduire des risques dans ton appli avec les threads à  mon avis.
  • CeetixCeetix Membre
    16:58 modifié #15
    Merci, j'ai pas tout tout compris mais je retiens qu'il faut éviter d'utiliser un thread. Dans ce cas je vois pas trop vers quelle méthode me tourner même si tu me dis que le parser développer par Apple peu faire la meme chose :s
  • AliGatorAliGator Membre, Modérateur
    16:58 modifié #16
    Bah c'est pas qu'il faut éviter à  tout prix d'utiliser un thread, c'est qu'avant d'utiliser un thread, il faut bien réfléchir à  savoir si c'est la bonne solution, car cela peut ajouter de la complexité et des risques là  où il y a bien souvent des alternatives bien plus adaptées.
    ça aussi c'est expliqué dans le Threading PG.

    Sinon j'ai pas suivi tout ton contexte, mais je vois pas trop le souci : le NSXMLParser sous iPhone (et il existe sous Mac aussi d'ailleurs) est de toute façon forcément un parsing SAX (donc événementiel, donc asynchrone). Cf la doc de NSXMLParser, et le PG assicié (Event-Driven XML PG).

    Donc de toute façon normalement tu n'as pas le choix, quand tu fais du parsing XML sous iPhone, c'est du event-driven, donc c'est non bloquant, et grace au delegate que tu affectes au NSXMLParser, tu es notifié quand le parsing est terminé, etc...

    Tu le tiens d'où ton parseXMLFileAtURL ? Car à  priori c'est lui qui est bloquant.
    Il faut que tu crées un NSXMLParser initialisé avec une URL, règle son delegate, puis demandé "parse" dessus. Après je suis sûr à  100% que le parsing est asynchrone, par contre la requête de récupération du fichier (pour récupérer le contenu de l'URL spécifiée) j'ai un doute tout à  coup, je ne sais pas si dans ce cas c'est pas un download bloquant... mais bon ça m'étonnerait, normalement tout dans cette procédure est asynchrone.
  • CeetixCeetix Membre
    16:58 modifié #17
    mon parseXMLFileAtURL est ma méthode qui fait justement appelle à  l'url.

    <br />NSURL *xmlURL = [NSURL URLWithString:@&quot;http://6par4.wayfarer.fr/up/iphone.xml&quot;];<br /> parser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];<br />[parser setDelegate:self];<br />	<br />[parser setShouldProcessNamespaces:NO];<br />[parser setShouldReportNamespacePrefixes:NO];<br />[parser setShouldResolveExternalEntities:NO];<br />[parser parse];<br />
    


    Je pense que c'est correct car il me parse tout correctement. C'est après que j'ai décidé de me servir du travail d'Eagle pour faire une page de chargement afin que l'user ne s'inquiète pas du temps de chargement.

    Et pour l'UI en faisant du perform ça marche, en fait j'affiche combien d'éléments j'ai parsé. D'ailleurs j'arrive pas à  retirer tout au départ le nombre d'élément total que j'ai (pour pouvoir faire un pourcentage).
  • AliGatorAliGator Membre, Modérateur
    16:58 modifié #18
    Dans ce cas c'est pas normal que ta méthode soit bloquante.
    Comme je le disais plus haut, j'ai un doute, peut-être que la partie "téléchargement du XML se trouvant à  l'URL spécifiée" est bloquante dans ce genre de cas, même si je crois que non, ça reste à  vérifier. Mais le reste n'est pas bloquant, puisque justement c'est de l'event-driven.

    Pour mieux comprendre le tout, tu peux mettre des traces dans ton parser:didStartDocument et parser:didEndDocument (voire même mieux, des breakpoints sonores qui vont faire un bip quand ça passe dans ces méthodes) pour comprendre la correspondance entre ces évènements (début du download du XML, fin du download et début du parsing, fin du parsing) et ce que tu as à  l'écran (l'affichage de la vue du PALoadingController, mise à  jour de l'interface, affichage de ton spashscreen, ...)

    Si tu pouvais nous faire un mini diagramme de séquence (genre via websequencediagrams.com) entre ce qui est attendu, et ce que tu as réellement, qu'on comprenne bien l'enchaà®nement que tu as et la corrélation entre ce qui se passe sous le capot (parsing, etc) et ce que tu as à  l'écran.
  • AliGatorAliGator Membre, Modérateur
    mars 2010 modifié #19
    En gros voilà  le scénario auquel tu veux aboutir, c'est ça ?

    Si tu peux nous décrire du coup ce que tu obtiens à  la place et donc exactement à  quel moment interviennent les divers affichages en réalité à  l'écran (PALoading qui s'affiche que juste avant la fin, etc...)

    Voir avec Louka aussi bien sûr, j'ai pas du tout regardé comment sa classe PALoadingController était fichue :P
  • mars 2010 modifié #20
    dans 1267626907:

    Dans ce cas c'est pas normal que ta méthode soit bloquante.
    Comme je le disais plus haut, j'ai un doute, peut-être que la partie "téléchargement du XML se trouvant à  l'URL spécifiée" est bloquante dans ce genre de cas, même si je crois que non, ça reste à  vérifier. Mais le reste n'est pas bloquant, puisque justement c'est de l'event-driven.

    Ce qui est bloquant c'est la récupération du XML à  mon avis. Enfin ça dépend, si t'es en edge oui ça peut mettre du temps :p
    Mais :
    <br /> parser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];<br />
    

    C'est pas très cool. Pourquoi ne pas utiliser NSURLConnection?

    Enfin, vu la taille du XML c'est pas bien grave :p Mais n'empêche que dans le cas où l'utilisateur est contraint d'utiliser le Edge, ça peut vite être bloquant pendant 30 bonnes secondes.
    En revanche, depuis le simulateur, tu utilises la connexion du Mac.. donc si ça bloque via le simulateur c'est qu'il y a un problème effectivement.

    Mais ça n'empêche pas qu'encore une fois, tu instancies le PALoadingController pour le mettre à  nil directement après : utilité ?
  • CeetixCeetix Membre
    16:58 modifié #21
    Olalala il y eu pleins de réponses ^^
    Donc pour faire dans l'ordre, j'ai fais la trace de mes méthodes de parsing et voilà  comment c'est appelé au lancement de l'application:


    2NQH


    A la fin la méthode parseDidEndDocument est bien appelée.


    Ensuite ton scénario est correct quand je mets le parsing dans un second thread . Sinon sans faire ça j'ai cet UML


    Pour NSURLConnection faut que je regarde comment on s'en sert.
    Quand je mets le loader à  nil je le mets dans ma méthode parseDidEndDocument.


    J'espere que c'est un peu plus clair :)
  • 16:58 modifié #22
    <br /><br />- (void)dealloc<br />{<br />&nbsp; &nbsp; if(connection!=nil)<br />&nbsp; &nbsp; &nbsp; &nbsp; [connection cancel];<br />&nbsp; &nbsp; self.connection=nil;<br />&nbsp; &nbsp; self.data=nil;<br />&nbsp; &nbsp; self.xmlParser=nil;<br />&nbsp; &nbsp; self.loadingController=nil;<br />&nbsp; &nbsp; [super dealloc];<br />}<br /><br />- (void)startParsingXMLAtURL:(NURL*)myURL<br />{<br />&nbsp; &nbsp;  self.loadingController = [PALoadingController controllerWithSuperview:self.view];<br />&nbsp; &nbsp;  self.data = [NSMutableData data];<br />&nbsp; &nbsp;  self.connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:myURL] delegate:self];<br />}<br /><br />- (void)connection:(NSURLConnection*)theConnection didReceiveData:(NSData*)aPieceOfData<br />{<br />&nbsp;  [data appendData:aPieceOfData];<br />} <br /><br />- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection<br />{<br />&nbsp; &nbsp; xmlParser = [[NSXMLParser alloc] initWithData:self.data];<br />&nbsp; &nbsp; xmlParser.delegate=self;<br />&nbsp; &nbsp; self.data = nil;<br />&nbsp; &nbsp; [xmlParser parse];<br />} <br />....<br /><br /><br />- (void)parserDidEndDocument:(NSXMLParser *)parser<br />{<br />	self.loadingController = nil;<br />&nbsp; &nbsp; &nbsp;  // do sumthin&#39; with my parsed XML.<br />}<br /><br />
    


    Aussi simple que ça d'utiliser NSURLConnection
  • CeetixCeetix Membre
    16:58 modifié #23
    Merci Eagle !
    Avec NSURLConnection plus besoin de thread, le loader se met super bien.
    Par contre mon UILabel que j'ai rajouté ne veut pas se rafraichir pendant que je parse.
    Avant je l'avais performer a cause du thread mais là  normalement il n'y a plus besoin ...

    Ah et aussi j'aimerai bien connaitre genre le nombre de balise <new> que j'ai dans mon XML avant de parser pour pouvoir faire 1/13 ; 2/13 ; etc .... Mais je vois pas comment faire :s
  • AliGatorAliGator Membre, Modérateur
    16:58 modifié #24
    Par définition ce n'est pas possible puisque c'est de l'événementiel (parsing SAX).
  • CeetixCeetix Membre
    16:58 modifié #25
    D'accord, bon bah je vais juste afficher 1, 2, 3, ...
    Enfin faut que je trouve pourquoi mon UILabel veut pas se rafraà®chir :)
  • mars 2010 modifié #26
    Un parsing XML est généralement rapide, je pense pas que tu ai besoin d'afficher tant d'infos. Surtout que l'utilisateur n'aura sûrement pas le temps de toutes les lire.
    Tu veux de bonnes infos essentielles ?

    <br /><br />- (void)startParsingXMLAtURL:(NURL*)myURL<br />{<br />&nbsp; &nbsp;  self.loadingController = [PALoadingController controllerWithSuperview:self.view];<br />&nbsp; &nbsp;  self.loadingController.descriptionLabel.text = NSLocalizedString(@&quot;Connecting...&quot;,nil);<br />&nbsp; &nbsp;  .......<br />}<br /><br />- (void)connection:(NSURLConnection*)theConnection didReceiveResponse:(NSURLResponse*)theResponse<br />{<br />&nbsp; &nbsp;  self.loadingController.descriptionLabel.text = NSLocalizedString(@&quot;Gathering data...&quot;,nil);<br />}<br /><br />....<br />- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection<br />{<br />&nbsp; &nbsp; self.loadingController.descriptionLabel.text = NSLocalizedString(@&quot;Parsing data...&quot;,nil);<br />&nbsp; &nbsp; xmlParser = [[NSXMLParser alloc] initWithData:self.data];<br />&nbsp; &nbsp; xmlParser.delegate=self;<br />&nbsp; &nbsp; self.data = nil;<br />&nbsp; &nbsp; [xmlParser parse];<br />} <br />....<br />
    


    Enfin c'est mon avis et un conseil que je te donne, tu fais comme tu veux, mais disons qu'il faut aller à  l'essentiel plutôt que de trop détailler. (Les détails comme ça, c'est un vilain défaut que j'avais à  l'époque aussi, et je t'avoues que c'est pas user-friendly du tout).

    Par contre, le méchant Aligator dis que ce n'est pas possible de compter le nombre de balises portant le nom "<new>", mais je suis un grand adepte du "cheat" :
    <br />- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection<br />{<br />&nbsp; &nbsp; self.loadingController.descriptionLabel.text = NSLocalizedString(@&quot;Parsing data...&quot;,nil);<br />&nbsp; &nbsp;  <br />&nbsp; &nbsp; NSString* xmlAsString = [[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding];<br />&nbsp; &nbsp; newTagsCount = [[xmlAsString componentsSeparatedByString:@&quot;&lt;new&gt;&quot;] count];<br />&nbsp; &nbsp; [xmlAsString release];<br /><br />&nbsp; &nbsp; xmlParser = [[NSXMLParser alloc] initWithData:self.data];<br />&nbsp; &nbsp; xmlParser.delegate=self;<br />&nbsp; &nbsp; self.data = nil;<br />&nbsp; &nbsp; [xmlParser parse];<br />} <br />
    

    Un peu la flemme de vérifier, mais je penses que tu devras sûrement faire un "newTagsCount--;"
  • CeetixCeetix Membre
    16:58 modifié #27
    Oui j'avoue ne pas avoir pensé à  cette optique là .
    Parfois c'est long car j'ai des images à  télécharger mais je pense que mettre ça est mieux en effet :)
    Par conte j'ai toujours le même soucis : ça ne rafraichi pas :(
  • AliGatorAliGator Membre, Modérateur
    16:58 modifié #28
    Ceci dit je sais pas pourquoi ton parsing est si long général ça prend pas non plus trois plombes. Sauf si évidemment tu traites tes données au fur et à  mesure de ton parsing et que ce traitement prend du temps (ou n'est pas optimisé)

    Ce qui prend du temps c'est donc :
    - Le téléchargement de ton XML (selon la vitesse de connexion, la rapidité de réponse du serveur qui l'héberge, la taille du XML)
    - Le traitement que tu fais de tes données lors du parsing, par exemple si le code que tu mets dans didStartElement ou didEndElement est long

    Si c'est le téléchargement qui prend du temps, tu peux éventuellement essayer de récupérer la taille attendue de tes données (nombre d'octets de ton XML quoi) au moment où tu reçois les headers HTTP (NSURLConnectionDelegate te retourne la NSURLResponse dans laquelle tu as les headers dont le Content-Length, enfin j'ai pas essayé mais ça parait logique).

    Si c'est le traitement que tu fais dans ton parsing qui prend du temps, tu peux peut-être faire ton parsing en deux temps : d'abord un parsing rapide, qui se contente de faire le parsing événementiel de ton XML pour compter les tags <new> sans rien faire d'autre, et un deuxième parsing, le réel, qui traite tes données, construit ton arbre de données de ton modèle du MVC à  partir du XML, etc.
    Mais est-ce que ça en vaut vraiment la peine, à  toi de voir.
  • AliGatorAliGator Membre, Modérateur
    16:58 modifié #29
    Attends, quand tu dis "parfois c'est long car j'ai des images à  télécharger", tu veux dire que tu prefetch tout au lancement de ton appli, tu ne fais pas du On Demand ?
    Tu m'étonnes que ça soit long au démarrage ^^
  • CeetixCeetix Membre
    16:58 modifié #30
    Lol oui oui mais c'est pour pas avoir de temps d'attente entre deux tableview etc ... Comme ça tout est dedans hop ^^
  • AliGatorAliGator Membre, Modérateur
    16:58 modifié #31
    Et comme ça ta mémoire explose, aussi. (Voire risque de faire crasher l'appli si ton XML référence trop d'images, éventuellement trop grosses)
    Et comme ça l'utilisateur attend au lancement de l'appli même s'il veut juste vite fait consulter un truc parmi tout ce que tu charges...

    "> Hop, poubelle :P
Connectez-vous ou Inscrivez-vous pour répondre.