Tag Archives: Xamarin

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9814)

En informatique, les problèmes les plus simples sont parfois les plus bizarres à comprendre et c’est ce qui m’est arrivé il y a 2 jours sur un prototype Xamarin.iOS sur laquelle je travaille.

L’application est on ne peut plus classique puisqu’elle fait simplement des appels à des webservices pour afficher des données sous forme d’une liste.

Alors que tout fonctionne correctement au début, en passant les webservices de HTTP à HTTPS, l’erreur, dont la description est très parlante (NSURLSession/NSURLConnection HTTP load failed kCFStreamErrorDomainSSL, -9814), se produit de manière subite.

La première réaction a été de se dire que j’ai oublié de configurer ATS (App Transport Security) dans le fichier Info.plist de l’application. Après vérification, ce n’est pas le cas puisque la configuration autorise tous les appels comme indiqué ci-dessous.

<key>NSAppTransportSecurity</key>
  <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
  </dict>
</key>

Après recherche dans la documentation officielle Apple, le code 9814 signifie que le certificat SSL est expiré mais ce n’est pas le cas puisque celui-ci expire en 2019.

D’où peut donc bien venir cette erreur dans ce cas ?

Après plus d’une heure à tourner en rond, en cherchant sur StackOverflow et toutes les sources classiques d’aide pour un développeur, une illumination m’est apparue : “Vérifions quelle date est utilisée par mon périphérique actuellement”.

BINGO !!! Le périphérique qui n’a apparemment jamais réussi à se synchroniser avec une source de temps (ou défini manuellement dans les paramètres du système), utilise une date en Janvier 1970.

L’erreur ne signifie pas dans ce cas que le certificat est réellement expiré, mais qu’il n’a pas réussi à être validé dans la séquence normale de validation des communications HTTPS.

Je partage donc cette petite mésaventure qui vous évitera de perdre du temps, simplement parce que le périphérique utilisé n’était pas à l’heure. Après vérification, la même erreur s’applique sur tvOS si jamais vous travaillez sur cette plateforme.

Reconnaissance vocale et analyse de texte dans vos applications Xamarin.iOS

Bien qu’existante depuis très longtemps dans l’univers informatique, la reconnaissance vocale est une fonctionnalité encore peu utilisée au sein des applications (aussi bien sur mobile que sur desktop).

Depuis l’annonce de Siri par Apple en 2011, les assistants personnels ont le vent en poupe et une nouvelle étape a été franchie sur iOS 10 lors de la WWDC 2016 puisque le moteur de reconnaissance vocal utilisé par Siri est désormais accessible aux développeurs.

Voyons comment utiliser celui-ci pour intégrer la reconnaissance vocale au sein d’une application Xamarin.iOS, et comment le coupler à des APIs plus anciennes mais relativement méconnues, pour faire de l’analyse de texte.

Speech Recognition API au service des développeurs

Comme nous l’évoquions précédemment, Apple a rendu disponible dans iOS 10 le moteur qui propulse Siri pour la reconnaissance vocale. Pour cela, une nouvelle classe est à la disposition des développeurs : SFSpeechRecognizer.

Ce qu’il est important de noter pour utiliser cette classe, c’est qu’une connexion réseau est nécessaire car tout ce qui sera enregistré via le microphone, sera envoyé à Apple pour que les serveurs qui propulsent Siri, traitent et analysent les informations avant de vous renvoyer la transcription du texte.

Cette phase de communication avec les serveurs Apple a lieu tout au long du processus de reconnaissance vocale (vous récupérez la transcription au fil de l’eau), ce qui fait que si vous utilisez la fonctionnalité trop souvent, cela a tendance à consommer beaucoup de données (sur votre forfait 3G/4G) et d’énergie sur la batterie de votre téléphone/tablette.

C’est pourquoi Apple impose certaines limitations (ex: 1mn maximum pour chaque enregistrement audio) et a également mis en place des quotas d’utilisation permettant à une application d’effectuer un nombre limité de requêtes par jour (un message d’erreur est renvoyé si le quota est dépassé).

Comme pour tout ce qui touche à la vie privée chez Apple, avant de pouvoir utiliser cette API, il est nécessaire de demander l’autorisation à l’utilisateur et de réagir en conséquence selon la réponse qu’il donnera :

SFSpeechRecognizer.RequestAuthorization((SFSpeechRecognizerAuthorizationStatus status) =>
{
  switch (status)
  {
    case SFSpeechRecognizerAuthorizationStatus.Authorized:
      break;
    case SFSpeechRecognizerAuthorizationStatus.Denied:
      break;
    case SFSpeechRecognizerAuthorizationStatus.NotDetermined:
      break;
    case SFSpeechRecognizerAuthorizationStatus.Restricted:
      break;
  }
});

Pour que tout ceci fonctionne sans soucis, il est également nécessaire d’ajouter 2 lignes dans le fichier Info.plist, afin d’indiquer à l’application que vous avez besoin d’utiliser la reconnaissance vocale et le microphone.

Speech Recognition - Info.plist

Maintenant que les bases sont posées pour pouvoir utiliser les composants nécessaires, il ne reste plus qu’à écrire le code. Celui-ci repose sur 3 classes importantes :

  • AVAudioEngine ==> Le moteur audio permettant d’enregistrer ce que l’utilisateur dira via le microphone de son téléphone ou de sa tablette
  • SFSpeechRecognizer ==> Le moteur de reconnaissance vocal qui communiquera avec les serveurs Apple
  • SFSpeechAudioBufferRecognitionRequest ==> Le buffer audio qui servira à stocker les données renvoyées par le moteur audio avant d’être envoyées chez Apple pour analyse

Un exemple de code étant toujours plus parlant qu’un long discours, voici ci-dessous le code associé à notre application de démonstration, qui initie l’enregistrement quand l’utilisateur tape sur un bouton, et met fin à l’enregistrement quand l’utilisateur relâche ce même bouton.

private AVAudioEngine AudioEngine;
private SFSpeechRecognizer SpeechRecognizer;
private SFSpeechAudioBufferRecognitionRequest LiveSpeechRequest;
private SFSpeechRecognitionTask RecognitionTask;
private string sentence;

partial void StartSpeechRecognition(UIButton sender)
{
  SpeechRecognizer = new SFSpeechRecognizer(NSLocale.FromLocaleIdentifier("fr"));
  if (!SpeechRecognizer.Available)
    return;

  AudioEngine = new AVAudioEngine();
  if (AudioEngine.InputNode == null)
    return;

  NSError audioError;
  LiveSpeechRequest = new SFSpeechAudioBufferRecognitionRequest();

  var recordingFormat = AudioEngine.InputNode.GetBusOutputFormat(0);
  AudioEngine.InputNode.InstallTapOnBus(0, 1024, recordingFormat, (AVAudioPcmBuffer buffer, AVAudioTime when) => LiveSpeechRequest.Append(buffer));
  AudioEngine.Prepare();
  AudioEngine.StartAndReturnError(out audioError);
  if (audioError != null)
    return;

  RecognitionTask = SpeechRecognizer.GetRecognitionTask(LiveSpeechRequest, (SFSpeechRecognitionResult result, NSError taskError) =>
  {
    if (taskError != null)
      return;

    if (!result.Final)
    {
      SpeechLabel.Text = result.BestTranscription.FormattedString;
      return;
    }
 
    sentence = result.BestTranscription.FormattedString;
    ParseSpeechRecognition();
  });
}

partial void StopSpeechRecognition(UIButton sender)
{
  AudioEngine?.Stop();
  AudioEngine?.InputNode?.RemoveTapOnBus(0);
  LiveSpeechRequest?.EndAudio();
}

Outre l’initialisation du moteur audio via la définition de paramètres sur la source d’enregistrement (AudioEngine.InputNode), la partie importante repose sur la fonction de “callback” passée à la méthode GetRecognitionTask, qui permet de récupérer la transcription du texte (result.BestTranscription.FormattedString) au fil de l’eau, et de savoir si la reconnaissance est terminée ou non (result.Final).

Une fois tous les éléments assemblés et en réalisant un peu de cosmétique autour de l’interface graphique, cela donne le résultat visible dans la vidéo ci-dessous :

Comme on peut le voir dans cette vidéo, avec juste quelques lignes de code, il est possible d’intégrer la reconnaissance vocale dans une application. Mais hormis si l’on souhaite juste permettre à l’utilisateur de remplir des zones de texte sans rien saisir au clavier, il serait plus intéressant de pouvoir analyser ce texte.

NSLinguisticTagger : Une classe méconnue mais très pratique

C’est à ce moment qu’une classe relativement méconnue surgit, bien qu’elle existe dans iOS depuis la version 5.0. Cette classe porte un nom relativement explicite : NSLinguisticTagger.

Celle classe est capable, à partir d’un texte plus ou moins long, de “tagger” celui-ci, c’est-à-dire d’identifier tous ses composants (noms, pronoms, verbes, adverbes, déterminants, etc…) mais également d’identifier certains éléments marquants (noms de lieux, noms de sociétés…). Malheureusement cette dernière possibilité ne fonctionne qu’en Anglais.

Différentes langues sont supportées par NSLinguisticTagger (Anglais, Français, Allemand…) afin de couvrir les langues les plus fréquemment rencontrées sur iOS.

Comment fonctionne tout ceci ? C’est relativement simple comme on peut le voir dans l’exemple de code ci-dessous :

private void ParseSpeechRecognition()
{
  var options = NSLinguisticTaggerOptions.OmitWhitespace | NSLinguisticTaggerOptions.OmitPunctuation | NSLinguisticTaggerOptions.JoinNames;
  var schemes = NSLinguisticTagger.GetAvailableTagSchemesForLanguage("fr");
  var tagger = new NSLinguisticTagger(schemes, options);
  tagger.AnalysisString = sentence;
  tagger.EnumerateTagsInRange(new NSRange(0, sentence.Length), NSLinguisticTag.SchemeLexicalClass, options, HandleNSLingusticEnumerator);
}

private void HandleNSLingusticEnumerator(NSString tag, NSRange tokenRange, NSRange sentenceRange, ref bool stop)
{
  var token = sentence.Substring((int)tokenRange.Location, (int)tokenRange.Length);
  Console.WriteLine($"{token} = {tag}");
}

A partir d’une liste d’options (ignorer les espaces, ignorer la ponctuation, etc…) et d’une liste de types d’éléments analysables pour une langue (appelées “schemes”), on énumère les “tags” contenus dans le texte à analyser (AnalysisString).

La méthode permettant d’énumérer les tags du texte, prend en paramètre une fonction de “callback” qui sera appelée pour chaque tag identifié.

Si on exécute le code précédent avec la phrase utilisée dans la vidéo de notre application de démonstration, nous obtenons le résultat ci-dessus dans la fenêtre de sortie de Xamarin Studio.

2016-11-27 09:02:57.714 SpeechDemo[919:415497] C' = Pronoun
2016-11-27 09:02:57.714 SpeechDemo[919:415497] est = Verb
2016-11-27 09:02:57.714 SpeechDemo[919:415497] bientôt = Adverb
2016-11-27 09:02:57.715 SpeechDemo[919:415497] Noël = Noun
2016-11-27 09:02:57.715 SpeechDemo[919:415497] j' = Pronoun
2016-11-27 09:02:57.715 SpeechDemo[919:415497] espère = Verb
2016-11-27 09:02:57.716 SpeechDemo[919:415497] que = Conjunction
2016-11-27 09:02:57.716 SpeechDemo[919:415497] vous = Pronoun
2016-11-27 09:02:57.716 SpeechDemo[919:415497] passerez = Verb
2016-11-27 09:02:57.716 SpeechDemo[919:415497] tous = Adjective
2016-11-27 09:02:57.717 SpeechDemo[919:415497] de = Preposition
2016-11-27 09:02:57.717 SpeechDemo[919:415497] bonnes = Adjective
2016-11-27 09:02:57.717 SpeechDemo[919:415497] fêtes = Noun

Comme on le voit ci-dessus, à partir des informations renvoyées par NSLinguisticTagger, il est possible d’envisager les traitements nécessaires à la logique propre à une application (ex: extraire les noms et rechercher des valeurs particulières pour filtrer des résultats).

Pour aller encore plus loin…

Comme nous l’avons vu dans cet article, en couplant la reconnaissance vocale, désormais disponible dans iOS 10, avec un outil de taggage comme NSLinguisticTagger, de nouveaux horizons s’ouvrent pour intégrer des fonctionnalités inédites au sein des applications mobiles.

Toutefois si vous voulez aller encore plus, il existe d’autres éléments qu’il est toujours bon de connaitre, à commencer par la catégorisation de contenus.

Si vous réalisez des applications pour OSX, Apple a mis à disposition un framework appelé Latent Semantic Mapping (LSM) qui permet de catégoriser un contenu (ex: une page web, un fichier…) à partir d’une base d’autres contenus. Une session dédiée à ce framework avait été présentée lors de la WWDC 2011 si vous souhaitez en savoir plus à ce sujet.

Si vous voulez faire une analyse plus poussée du texte renvoyé par la reconnaissance vocale (ex: extraire des entités, identifier le sujet de fond d’un document, etc…) il existe différentes APIs basées sur le Machine Learning qui satisferont tous vos désirs.

On pourra citer comme exemple un projet sur lequel mise beaucoup Microsoft, à savoir Cognitive Services et plus particulièrement la brique Language Understanding Intelligent Service (LUIS). Des alternatives telles que TextRazor méritent également le coup d’oeil.

Akavache : Stockage de données en cache dans vos applications Xamarin

Afin de palier aux problèmes de connectivité inhérents à toutes applications mobiles, le stockage de données sur le périphérique de l’utilisateur est une quasi nécessité, sauf si l’on accepte que l’utilisateur ne puisse utiliser l’application sans connexion internet (parti pris de nombreuses personnes).

La solution la plus souvent utilisée dans le monde des applications mobiles repose sur SQLite, qui a l’avantage d’être à la fois léger, performant et d’utiliser les standards du marché puisqu’il s’agit d’un moteur de base de données SQL classique.

Ce type de base de données a toutefois quelques inconvénients dont le plus important concerne la difficulté de modifier le schéma de la base lors d’une montée de version de l’application.

Afin de palier à ces problèmes, différentes alternatives existent (stockage clé/valeur, base NoSQL, etc…) et nous allons aborder l’une de celles-ci nommée Akavache.

Présentation de ce qu’est / ce que n’est pas Akavache

AkavacheComme sa description l’indique sur son repository Github (https://github.com/akavache/Akavache) vu qu’Akavache est un projet open-source, il s’agit d’un système de stockage de données asynchrone de type clé/valeur à destination des applications native (iOS, Android, UWP, WPF, OSX…) .

Un système de stockage clé/valeur ne fonctionne pas comme une base de données, ce qui sous-entend que pour pouvoir récupérer une donnée, il est nécessaire de connaitre la clé utilisée par celle-ci, ou bien de faire une lecture séquentielle des données pour retrouver la valeur souhaitée.

Les valeurs stockées par Akavache sont gérées sous forme de tableau d’octets, quels que soient leur forme originale (document JSON, image, chaine de caractères…). Il est possible de définir pour chacune des clés stockées, une date d’expiration du contenu au delà de laquelle les données seront automatiquement supprimées.

Sous le capot, Akavache utilise une base de données SQLite pour stocker les données. Cette base de données ne contient qu’une seule table servant à stocker les clés, valeurs et quelques autres éléments (type de données, date d’expiration…).

Pour être clair, Akavache n’est pas là pour concurrencer les bases de données comme SQLite, mais pour offrir une alternative sur une utilisation très ciblée qu’est la mise en cache de données.

Installation au sein d’une application Xamarin

Pour utiliser Akavache au sein d’une application Xamarin, rien de plus simple car cette solution se présente sous la forme de paquets NuGet, qu’il est possible de trouver assez facilement via la boite de dialogue que vous pouvez voir ci-dessous.

Akavache - NuGet Packages

On peut voir ci-dessous que la dernière version publiée (5.0.0) a été finalisée il y a seulement quelques jours (le 4 Novembre 2016).

Utilisation des APIs proposées par la bibliothèque

Comme pour toute application .NET, tout commence par référencer l’espace de noms :

using Akavache;

Une fois cette opération effectuée, tout se passe via l’utilisation de la classe nommée BlobCache, sur laquelle il est nécessaire de définir le nom de l’application (utilisé pour créé la base de données SQLite).

BlobCache.ApplicationName = "AkavacheDemo";

Il ne reste plus ensuite qu’à insérer et récupérer des données. Pour cela il existe différentes possibilités :

var userAccount = new UserAccount
{
  Id = 12345,
  FirstName = "Stephane",
  LastName = "Cordonnier"
};

BlobCache.InMemory.InsertObject("userAccount", userAccount, TimeSpan.FromDays(31));
BlobCache.LocalMachine.InsertObject("userAccount", userAccount, TimeSpan.FromDays(31));
BlobCache.Secure.InsertObject("userAccount", userAccount, TimeSpan.FromDays(31));
BlobCache.UserAccount.InsertObject("userAccount", userAccount, TimeSpan.FromDays(31));

BlobCache.LocalMachine.GetObject<UserAccount>("userAccount").Subscribe(u =>
{
  Console.WriteLine($"{u.Id} - {u.FirstName} - {u.LastName}");
});

BlobCache.LocalMachine.GetAllObjects<UserAccount>().Subscribe(userAccounts =>
{
  foreach (var u in userAccounts)
    Console.WriteLine($"{u.Id} - {u.FirstName} - {u.LastName}");
});

La classe UserAccount est un POCO, initialisé et stocké dans différents conteneurs mis à disposition par Akavache. Ces conteneurs sont au nombre de quatre :

  • InMemory => Stockage non persistent (données perdues au redémarrage de l’application)
  • LocalMachine => Stockage persistent dans une base SQLite
  • Secure => Stockage persistent dans une base SQLite avec cryptage des données
  • UserAccount => Stockage persistent dans une base SQLite. La base de données est stockée dans un répertoire qui est automatiquement sauvegardé par le mécanisme standard de la plateforme (ex : synchronisation iTunes / iCloud sur iOS)

Pour récupérer les données, il suffit d’interroger le même conteneur que celui utilisé pour le stockage. Si la clé de l’objet recherché est connue, la méthode GetObject<T>(…) est disponible. Pour récupérer tous les objets en se basant sur leur type, il suffit d’utiliser la méthode GetAllObjects<T>().

La suppression de données se fait de la même manière en appelant les méthodes InvalidateObject, InvalidateObjects ou InvalidateAllObjects.

Téléchargement et mise en cache de données

Outre la mise en cache de données réalisée par programmation, Akavache propose des fonctionnalités de téléchargement et de mise en cache de données directement depuis Internet.

BlobCache.UserAccount.DownloadUrl(...);
BlobCache.UserAccount.LoadImageFromUrl(...);

Ces fonctionnalités sont très pratiques pour la mise en cache de données provenant par exemple de webservices renvoyant des flux JSON. En revanche pour ce qui est de la mise en cache d’images, il faut garder à l’esprit que les données sont stockées en arrière plan dans une base SQLite, et il n’est pas forcément recommandé de stocker de gros flux binaires (ex: des images HD) dans ce type de base de données.

Conclusion

Si vous cherchez une solution rapide à mettre en oeuvre pour faire de la mise en cache simple de données, Akavache est une solution efficace et performante qui mérite que l’on s’attarde dessus.

En revanche si vous cherchez une solution puissante de stockage et d’interrogation comme peut l’être une base de données SQLite, mais sans les inconvénients de celle-ci (ex: gestion du schéma de la base), alors Akavache n’est clairement pas la solution adaptée.

Il faudra alors plutôt se diriger vers d’autres solutions telles que les bases NoSQL, ce qui fera certainement l’objet d’un futur article sur ce blog.

Editer un XIB avec Xcode 8 peut avoir des conséquences inattendues sur le rendu de votre application Xamarin

Apple a rendu disponible la version finale de Xcode 8 courant Septembre et comme beaucoup de développeurs, vous avez probablement effectué la mise à jour si vous voulez pouvoir tirer parti des nouveautés d’iOS 10.

Ayant déjà essuyer les plâtres de ce type de mise à jour par le passé, notamment lors des différentes montées de version de Swift (de 1.0 à 3.0), je laisse désormais passer un peu de temps pour voir si d’autres personnes rencontrent des soucis ou non, même si j’avais déjà testé de manière superficielle cette version sur une 2ème machine de développement à sa sortie.

J’ai donc appliqué le même principe avec cette dernière mise à jour, et j’ai effectué des tests un plus poussés cette semaine pour voir si une application Xamarin.iOS sur laquelle travaille mon équipe, fonctionnait toujours correctement une fois la mise à jour effectuée.

Une interface graphique relativement simple basée sur des XIB

Quand on réalise une application iOS, on peut réaliser l’interface graphique sous 3 formes, en pouvant combiner celles-ci si nécessaire :

  • Par code => Il faut être motivé si les écrans sont complexes
  • A base de XIB => La solution historique utilisée sur iOS depuis ses débuts
  • A base de storyboards => La solution désormais préconisée par Apple

Nous ne débattrons pas ici des avantages et inconvénients de chaque solution et pour l’application sur laquelle nous travaillons, nous avons préféré retenir les XIB avec également la solution par code sur certains composants.

Ci-dessous, vous trouverez l’exemple d’un écran de l’application réalisé par XIB sous Xcode 7, avec l’utilisation de contraintes pour gérer facilement les différentes tailles/résolutions de périphériques. Cet écran contient également un composant entièrement réalisé par code (le calendrier sur l’onglet “Mois”).

Xcode8 - Application avant édition #1  Xcode8 - Application avant édition #2

Ouverture du XIB sous Xcode 8 sans apporter aucune modification…

Après avoir mis à jour les différents outils (Xamarin Studio, Xcode, etc…) sur une machine de développement, j’ai tout simplement ouvert le XIB de cet écran depuis Xcode 8 afin de pouvoir tirer parti des nouveautés de l’environnement Apple (ex: la nouvelle prévisualisation sur différents périphériques).

A l’ouverture du XIB, Xcode 8 fait un certain nombre de modifications dont vous n’avez pas nécessairement conscience, et enregistre automatiquement celles-ci. Le mécanisme de synchronisation entre Xamarin Studio et Xcode fait en sorte que ces modifications soient immédiatement répercutées côté Xamarin à la prochaine compilation de l’application.

… et là c’est le drame une fois l’application compilée et exécutée

Après être retourné sous Xamarin Studio et avoir compiler et exécuter l’application, je retourne sur l’écran en question et là surprise, celui-ci ne fonctionne plus comme auparavant.

Les différences sont subtiles (disparition du liseré rouge sous l’onglet courant, disparition de certaines lignes dans le contrôle calendrier…). Vous pouvez voir ci-dessous le résultat :

Xcode8 - Application après édition #1 Xcode8 - Application après édition #2

Mais que se passe-t-il vu que nous avons juste ouvert le XIB ?

Comme je le disais précédemment, lors de l’ouverture du XIB, Xcode 8 fait quelques modifications sans vous prévenir dans la structure du fichier, dont vous pouvez voir un extrait ci-dessous grâce au client Git que nous utilisons pour nos projets (Atlasssian SourceTree).

Xcode8 - Différences dans le fichier XIB

Comme on peut le voir, certaines lignes ont été ajoutées et/ou modifiées (en vert) et d’autres lignes ont purement et simplement été supprimées (en rouge).

Parmi les lignes supprimées, on retrouve tout ce qui concerne la définition du positionnement par défaut des éléments graphiques dans la vue (les lignes commençant par <rect key=”frame” x=”…” y=”…” width=”…” height=”…” />.

Vous vous demandez peut être pourquoi un tel impact sur le rendu de l’écran ?

Et bien parce que dans celui-ci, certains éléments sont construits par code (le liseré rouge ou le contrôle calendrier) avec du code ressemblant à quelque chose comme ceci :

public override void ViewDidLoad()
{
   base.ViewDidLoad();
   ...

   _selectedBottomLayer = new CALayer();
   _selectedBottomLayer.Frame = new CGRect(0, DayButton.Bounds.Size.Height - SELECTED_LAYER_THICKNESS, DayButton.Bounds.Width, SELECTED_LAYER_THICKNESS);
   View.Layer.AddSublayer(_selectedBottomLayer);

   ...
}

Rien que des choses assez classiques pour les personnes qui font de l’iOS, mais qui ne fonctionnent plus comme auparavant à cause de la modification opérée par Xcode 8.

La suppression de la position par défaut a un impact dans le cycle de vie de construction d’une vue

Le fait que Xcode 8 aie supprimé le positionnement par défaut des éléments n’a pas d’impact sur les éléments positionnés par contraintes, mais pour les éléments positionnés par code et sans contraintes (les contraintes ne peuvent pas toujours être utilisées), cette modification a un impact très important.

Dans l’exemple précédent, le positionnement du liseré rouge est calculé dans la méthode ViewDidLoad. Le problème étant qu’au moment où cette méthode est exécutée dans le cycle de vie iOS, les contraintes appliquées sur les composants graphiques n’ont pas encore été calculées.

Là où au préalable la bouton sur lequel étaient basés les calculs avait un cadre défini comme ceci (x=0, y=0, width=106.5, height=45), il n’a désormais plus de positionnement par défaut ce qui fait qu’au moment ou la méthode ViewDidLoad s’exécute, le bouton a les caractéristiques suivantes (x=0, y=0, width=1000, height=1000).

Donc le liseré rouge est bien construit mais avec des coordonnées totalement farfelues qui le positionnent en dehors de la zone visible de l’écran. Le problème est le même pour les lignes qui ont disparues du contrôle calendrier.

Quelle est la solution pour résoudre les soucis dans ce cas ?

La solution est relativement simplement à mettre en oeuvre mais peut nécessiter de revoir complètement le cycle de vie qui a été utilisé dans les différents écrans.

Puisque le calcul de positionnement ne peut être effectué correctement au moment du ViewDidLoad, il suffit de le faire à un moment plus opportun.

Il n’y a pas une réponse qui pourra répondre à tous les cas possibles que vous pourriez rencontrer mais iOS propose différentes méthodes à surcharger dans lesquelles vous pouvez effectuer les traitements nécessaires à vos besoins.

Parmi les méthodes classiques que l’on a l’habitude d’utiliser pour effectuer les traitements nécessaires à des calculs de positionnement, on retrouve celles-ci (liste non exhaustive) :

public override void UpdateViewConstraints()
{
}

public override void ViewDidAppear(bool animated)
{
}

public override void ViewDidLayoutSubviews()
{
}

public override void ViewWillAppear(bool animated)
{
}

public override void ViewWillLayoutSubviews()
{
}

En conclusion, même si dans la plupart des cas la montée de version de Xcode n’aura pas d’impact sur vos applications, il peut arriver que dans certains cas cela ne soit pas si transparent que cela.

Que Xcode 8 supprime des données du XIB est une chose qui parait assez logique, certainement pour privilégier le système de contraintes créé par Apple, mais le fait que cela change le comportement et l’affichage d’une application qui fonctionnait sans soucis jusque là, est plus dérangeant sur le fond.

On ne répètera jamais assez qu’avant de faire une montée de version quelle qu’elle soit (outils de développement, packages NuGet…), la mise en place de tests approfondis (unitaires, graphiques, etc…) pour vérifier tous les impacts est un élément crucial pour la qualité de vos applications.

React to changes of focus in your Xamarin.tvOS applications

The focus engine introduced in UIKit for tvOS 9.0 is one of the key concepts you should know if you want to develop applications for the Apple TV.

It allows you to know what is the control that is currently focused on the screen, and to be notified when the focus changes from a view to another view (e.g. from a textfield to a button, from a collection view cell to the next cell, etc…).

Today I have created my first and simple application that uses a UICollectionView with few cells as you can see on the screenshot below. I was proud of my work until I realized that when the application is running, there is no visual indication that gives a feedback to the user of which cell is currently selected (cell #0 or cell #1).

tvOS - CollectionView without focus

I then looked for solutions to surround the focused cell with a white border to give an instant feedback to the user. The focus engine gives you this capability through the DidUpdateFocus method.

This method exposes a context to retrieve which views are focused (the previous and the next), and an animation coordinator to change attributes of these views within an animation.

When you know this method, it is very simple to accomplish your goal. The example below shows you how to remove the border for the old focused view, and add a border for the new focused view.

public override void DidUpdateFocus(UIFocusUpdateContext context, UIFocusAnimationCoordinator coordinator)
{
  base.DidUpdateFocus(context, coordinator);
  coordinator.AddCoordinatedAnimations(() =>
  {
    if (context.PreviouslyFocusedView != null)
    {
      var previousCell = (UICollectionViewCell)context.PreviouslyFocusedView;
      previousCell.Layer.BorderColor = UIColor.Clear.CGColor;
      previousCell.Layer.BorderWidth = 0;
    }

    if (context.NextFocusedView != null)
    {
      var nextCell = (UICollectionViewCell)context.NextFocusedView;
      nextCell.Layer.BorderColor = UIColor.White.CGColor;
      nextCell.Layer.BorderWidth = 5;
    }
  }, null);
}

The visual feedback is now more understandable for the user and he knows that the currently focused view is the cell #1.

tvOS - CollectionView with border when focused

It is good but if I want to have the same feature that is standard on the Apple TV (the cells sizes increase/decrease when they are focused/unfocused), what I need to do ?

If your cell is only composed with an UIImageView, you can select the option called “Adjusts image when focused” on the properties of this view in Xcode.

tvOS - ImageView adjusts image when focused

But when your cells are more complex like the sample below (each cell is composed with an UIImageView, a translucent UIView with a UILabel above), then you probably want to increase the size all the items inside the cell at the same time.

tvOS - CollectionView with scale when focused

No problem to achieve this goal with the same method that was previously mentioned. We just need to apply a transformation to the cell (CGAffineTransform.MakeScale(…)) when it receive the focus, and revert it (CGAffineTransform.MakeIdentity()) when the focus is lost.

public override void DidUpdateFocus(UIFocusUpdateContext context, UIFocusAnimationCoordinator coordinator)
{
  base.DidUpdateFocus(context, coordinator);
  coordinator.AddCoordinatedAnimations(() =>
  {
    if (context.PreviouslyFocusedView != null)
    {
      var previousCell = (UICollectionViewCell)context.PreviouslyFocusedView;
      previousCell.Transform = CGAffineTransform.MakeIdentity();
    }

    if (context.NextFocusedView != null)
    {
      var nextCell = (UICollectionViewCell)context.NextFocusedView;
      nextCell.Transform = CGAffineTransform.MakeScale(1.25f, 1.25f);
    }
  }, null);
}

As we have seen in this article, it’s pretty easy to interact with the focus engine of tvOS to apply any modifications on views, depending on the focus state of each of them.

Issue in the Xamarin.iOS binding library for Google Ads

This week, I had to work about an issue around the integration of Google Ads into a Xamarin.iOS application.

To integrate Google Ads in your application, Xamarin has published a binding library above the official iOS SDK (written in Objective-C). This library is available as a NuGet package.

After adding the package in your project, it’s very easy to use and in just few lines of code, you can create a search request and retrieve ads matching your criteria. The sample code below shows you how to achieve that (it has been simplified for brevity).

var request = new SearchRequest();
request.Query = "Xamarin Mobile Development";

var banner = new SearchBannerView(...); 
banner.LoadRequest(request);

Unfortunately, with the actual version of the NuGet package, this code won’t compile. The error is on line with the LoadRequest statement.

After some investigations, I’ve discovered the source of the problem. If you look at the official iOS SDK, you can see declarations like this :

@interface GADRequest : NSObject<NSCopying>
@interface GADSearchRequest : GADRequest

The GADSearchRequest class (aka. interface in the Objective-C terminology) inherits from the GADRequest class.

But if you look the source code of the binding library available on GitHub since few weeks (Xamarin has announced at Evolve 2016 that a lot of components are now open-source), you can see these declarations :

[BaseType(typeof(NSObject), Name="GADRequest")]
interface Request : INSCopying

[BaseType(typeof(NSObject), Name="GADSearchRequest")]
interface SearchRequest

As you can see, there is a difference compared to the official SDK, because the BaseType of the SearchRequest class is equal to NSObject rather than Request.

I’ve downloaded, modified and recompiled the source code with the following modification.

[BaseType(typeof(NSObject), Name="GADRequest")]
interface Request : INSCopying

[BaseType(typeof(Request), Name="GADSearchRequest")]
interface SearchRequest

With this simple modification, the LoadRequest statement is good and now, I’m able to compile and test the application successfully.

This issue has been submitted on GitHub and the pull request is waiting for approval.

In conclusion, if you encounter some compilation errors like this one (in this library or any other), take a look to the official SDKs, and compare the declarations with the binding library to ensure that everything is good.

How to remove the IOHIDLibFactory error when you start your Xamarin.iOS application in the simulator

When you will develop Xamarin.iOS applications, you will use the iOS simulator to test your application without having to use a real device like an iPhone or an iPad.

In Xamarin Studio (like in Visual Studio), the output window gives you some useful information to help you to debug your application (e.g. when you use Console.WriteLine(…) to print values).

When you start your application in the iOS simulator, it might happen that you see a lot of errors like this :

Error loading /System/Library/Extensions/IOHIDFamily.kext/Contents/PlugIns/
IOHIDLib.plugin/Contents/MacOS/IOHIDLib: dlopen(/System/Library/Extensions/
IOHIDFamily.kext/Contents/PlugIns/IOHIDLib.plugin/Contents/MacOS/IOHIDLib, 262):
no suitable image found. Did find: /System/Library/Extensions/IOHIDFamily.kext/Contents/
PlugIns/IOHIDLib.plugin/Contents/MacOS/IOHIDLib: mach-o, but not built for iOS simulator

Or also :

Cannot find function pointer IOHIDLibFactory for factory 13AA9C44-6F1B-11D4-907C-0005028F18D5 in CFBundle/CFPlugIn 0x7be04110 </System/Library/Extensions/
IOHIDFamily.kext/Contents/PlugIns/IOHIDLib.plugin> (bundle, not loaded)

It’s not a problem for your application and has no impact on it, but it’s often annoying to have a lot of errors in the output window, each time you start your application.

If you want to remove these errors, you just have to edit your iOS build settings, and change the linker behavior value from “Don’t link” to “Link Frameworks SDKs Only“.

Xamarin Studio - iOS Build Settings

Note that activating this option will increase the compile time of your application because the linker will have more work to do to generate your package.

[French] Integrate and use HockeyApp into your Xamarin applications

At the end of 2014, Microsoft has announced the acquisition of HockeyApp, a class leading service for mobile crash analytics and app distribution for developers building apps on iOS, Android, Windows Phone, Windows, OS X, etc…

Last week at Evolve 2016, the annual event about Xamarin products and technologies, some announcements were made and one of them concerns the first improvements integrated in HockeyApp, after the acquisition of Xamarin by Microsoft, earlier in March 2016.

If you speak french and re interested to discover what’s HockeyApp and how to integrate the service into your Xamarin applications, take a look at my new video available on Channel 9.

Introducing the new HttpClient implementation in Xamarin.iOS

In the upcoming days/weeks, Xamarin will release a whole set of new versions for its products to develop cross-platform mobile applications.

One of the most important (from my perspective) is Xamarin Studio 6.0 which will include lots of new features. If you want more information about those features, you can find a complete list on this page.

Today, I would like to talk about one of them which allows you to choose which implementation you want to use for network communications for your application iOS.

When you want to write cross-platform mobile applications, you avoid to use platform-specific APIs such as NSURLSession in iOS. Instead, you will prefer to use APIs that are available on all platforms such as the HttpClient class available in the .NET framework.

While this solution has many benefits for reusing your code on all platforms, the performance is worse than using specific API to the platform.

Fortunately, Xamarin Studio 6.0 (available on the Beta channel at this time) will introduce an option to use the underlying native APIs (NSURLSession, CFNetwork…) above HttpClient.

To activate this option, you just have to edit your Xamarin.iOS project settings and to go to the iOS Build section. On the screenshot below, you can see that 2 new options are available :

  • HttpClient Implementation
  • SSL / TLS Implementation

By default, your project will use the .NET implementation but you can switch to native APIs.

Xamarin Studio 6.0 - Choose your HttpClient implementation

There are many choices available and if you want more details, leave your cursor on the “blue I” on the right side of the selection lists. Below, the details about choices available for the HttpClient implementation.

Xamarin Studio 6.0 - HttpClient implementation tooltip

As mentioned in this tooltip, the usage of native APIs will result of better performance, support for latest standards (e.g. TLS 1.2) and smaller executable size.

[French] Configuring Visual Studio 2015 to develop Xamarin.iOS applications

As Microsoft MVPs, we have opportunities to collaborate with Microsoft teams and we have access to a lot of resources and tools which are not available to the general audience.

Today, I’m proud to announce that my first screencast (and certainly not the last) was published on Channel 9, the official Microsoft video portal for developers.

If you speak French (yes it’s my native language) and are interested by Xamarin development, you can discover how to configure your Visual Studio 2015 development environment to develop iOS applications.