Comment créer un éditeur de texte collaboratif à l'aide de Swift

Photo de rawpixel sur Unsplash

Les éditeurs de texte sont de plus en plus populaires de nos jours, qu'ils soient intégrés dans un formulaire de commentaire de site Web ou utilisés comme bloc-notes. Il existe de nombreux éditeurs différents parmi lesquels choisir. Dans cet article, nous allons non seulement apprendre à créer une belle application mobile d'éditeur de texte dans iOS, mais aussi à permettre de collaborer sur une note en temps réel à l'aide de Pusher.

Veuillez noter, cependant, que pour simplifier l'application, l'article ne couvrira pas les modifications simultanées. Par conséquent, une seule personne peut éditer en même temps pendant que d'autres regardent.

L'application fonctionnera en déclenchant un événement lorsqu'un texte est entré. Cet événement sera envoyé à Pusher, puis récupéré par l'appareil du collaborateur et mis à jour automatiquement.

Pour suivre ce didacticiel, vous aurez besoin des éléments suivants:

  1. Cocoapods: pour installer, exécutez gem install cocoapods sur votre machine
  2. Xcode
  3. Une application Pusher: vous pouvez créer un compte et une application gratuits ici
  4. Connaissance de la langue Swift
  5. Node.js

Enfin, une compréhension de base de Swift et Node.js est nécessaire pour suivre ce tutoriel.

Premiers pas avec notre application iOS dans Xcode

Lancez Xcode et créez un nouveau projet. Je vais appeler le mien Collabo. Après avoir suivi l'assistant de configuration, et avec l'espace de travail ouvert, fermez Xcode puis cd à la racine de votre projet et exécutez le pod de commande init. Cela devrait générer un Podfile pour vous. Modifiez le contenu du Podfile:

# Décommentez la ligne suivante pour définir une plateforme globale pour votre projet
    plateforme: ios, '9.0'
    cible 'textcollabo' faire
      # Commentez la ligne suivante si vous n'utilisez pas Swift et ne souhaitez pas utiliser de frameworks dynamiques
      use_frameworks!
      # Pods pour anonchat
      pod 'Alamofire'
      pod 'PusherSwift'
    fin

Exécutez maintenant la commande pod install afin que le gestionnaire de packages Cocoapods puisse extraire les dépendances nécessaires. Une fois cette opération terminée, fermez Xcode (s'il est ouvert), puis ouvrez le fichier .xcworkspace qui se trouve à la racine de votre dossier de projet.

Conception des vues pour notre application iOS

Nous allons créer des vues pour notre application iOS. Ce sera l'épine dorsale dans laquelle nous accrocherons toute la logique. En utilisant le storyboard Xcode, faites en sorte que vos vues ressemblent un peu aux captures d'écran ci-dessous.

Il s'agit du fichier LaunchScreen.storyboard. Je viens de concevoir quelque chose de simple sans aucune fonctionnalité.

Le prochain storyboard que nous allons concevoir est le Main.storyboard. Comme son nom l'indique, ce sera le principal. C'est là que nous avons tous les points de vue importants qui sont attachés à une logique.

Ici, nous avons trois vues.

La première vue est conçue pour ressembler exactement à l'écran de lancement, à l'exception d'un bouton que nous avons lié pour ouvrir la deuxième vue.

La deuxième vue est le contrôleur de navigation. Il est attaché à une troisième vue qui est un ViewController. Nous avons défini la troisième vue en tant que contrôleur racine pour notre contrôleur de navigation.

Dans la troisième vue, nous avons une UITextView modifiable qui est placée dans la vue. Il y a aussi une étiquette qui est censée être un compteur de caractères. C'est l'endroit où nous allons incrémenter les caractères lorsque l'utilisateur tape du texte dans la vue texte.

Codage de l'application d'éditeur de texte collaboratif iOS

Maintenant que nous avons réussi à créer les vues nécessaires au chargement de l'application, la prochaine chose que nous allons faire est de commencer à coder la logique de l'application.

Créez un nouveau fichier de classe cacao et nommez-le TextEditorViewController et liez-le à la troisième vue du fichier Main.storyboard. Le TextViewController doit également adopter l'UITextViewDelegate. Maintenant, vous pouvez ctrl + faire glisser l'UITextView et également ctrl + faire glisser l'UILabel dans le fichier Main.storyboard vers la classe TextEditorViewController.

De plus, vous devez importer les bibliothèques PusherSwift et AlamoFire dans TextViewController. Vous devriez avoir quelque chose près de cela après avoir terminé:

importer UIKit
    importer PusherSwift
    importer Alamofire
    classe TextEditorViewController: UIViewController, UITextViewDelegate {
        @IBOutlet var faible textView: UITextView!
        @IBOutlet caractères var faiblesLabel: UILabel!
    }

Maintenant, nous devons ajouter quelques propriétés dont nous aurons besoin un peu plus tard dans le contrôleur.

importer UIKit
    importer PusherSwift
    importer Alamofire
    classe TextEditorViewController: UIViewController, UITextViewDelegate {
        static let API_ENDPOINT = "http: // localhost: 4000";
        @IBOutlet var faible textView: UITextView!
        @IBOutlet caractères var faiblesLabel: UILabel!
        var pusher: Pusher!
        var chillPill = true
        var placeHolderText = "Commencez à taper ..."
        var randomUuid: String = ""
    }

Nous allons maintenant diviser la logique en trois parties:

  1. Afficher et événements du clavier
  2. Méthodes UITextViewDelegate
  3. Gestion des événements Pusher.

Afficher et événements du clavier

Ouvrez TextEditorViewController et mettez-le à jour avec les méthodes ci-dessous:

remplacer la fonction func viewDidLoad () {
        super.viewDidLoad ()

        // Déclencheur de notification
        NotificationCenter.default.addObserver (auto, sélecteur: #selector (keyboardWillShow), nom: NSNotification.Name.UIKeyboardWillShow, objet: nil)
        NotificationCenter.default.addObserver (auto, sélecteur: #selector (keyboardWillHide), nom: NSNotification.Name.UIKeyboardWillHide, objet: nil)

        // Reconnaissance de gestes
        view.addGestureRecognizer (UITapGestureRecognizer (cible: auto, action: #selector (tappedAwayFunction (_ :))))

        // Définir le contrôleur comme délégué textView
        textView.delegate = self

        // Définir l'ID de l'appareil
        randomUuid = UIDevice.current.identifierForVendor! .uuidString

        // Écoutez les changements de Pusher
        listenForChanges ()
    }

    remplacer func viewWillAppear (_ animated: Bool) {
        super.viewWillAppear (animé)

        si self.textView.text == "" {
            self.textView.text = placeHolderText
            self.textView.textColor = UIColor.lightGray
        }
    }

    func keyboardWillShow (notification: NSNotification) {
        si laissez keyboardSize = (notification.userInfo? [UIKeyboardFrameBeginUserInfoKey] as? NSValue) ?. cgRectValue {
            si self.charactersLabel.frame.origin.y == 1.0 {
                self.charactersLabel.frame.origin.y - = keyboardSize.height
            }
        }
    }

    func keyboardWillHide (notification: NSNotification) {
        si laissez keyboardSize = (notification.userInfo? [UIKeyboardFrameBeginUserInfoKey] as? NSValue) ?. cgRectValue {
            si self.view.frame.origin.y! = 1.0 {
                self.charactersLabel.frame.origin.y + = keyboardSize.height
            }
        }
    }

Dans la méthode viewDidLoad, nous avons enregistré les fonctions du clavier afin qu'elles répondent aux événements du clavier. Nous avons également ajouté des reconnaisseurs de gestes qui fermeront le clavier lorsque vous appuyez en dehors de UITextView. Et nous avons défini le délégué textView sur le contrôleur lui-même. Enfin, nous avons appelé une fonction pour écouter les nouvelles mises à jour (nous le créerons plus tard).

Dans la méthode viewWillAppear, nous avons simplement piraté l'UITextView pour avoir un texte d'espace réservé, parce que, par défaut, l'UITextView n'a pas cette fonctionnalité. Je me demande pourquoi, Apple…

Dans les fonctions keyboardWillShow et keyboardWillHide, nous avons fait monter le libellé du nombre de caractères avec le clavier et le descendre avec, respectivement. Cela empêchera le clavier de couvrir l'étiquette lorsqu'il est actif.

Méthodes UITextViewDelegate

Mettez à jour TextEditorViewController avec les éléments suivants:

func textViewDidChange (_ textView: UITextView) {
        charactersLabel.text = String (format: "% i Characters", textView.text.characters.count)
        si textView.text.characters.count> = 2 {
            sendToPusher (texte: textView.text)
        }
    }
    func textViewShouldBeginEditing (_ textView: UITextView) -> Bool {
        self.textView.textColor = UIColor.black
        si self.textView.text == placeHolderText {
            self.textView.text = ""
        }
        retourner vrai
    }
    func textViewDidEndEditing (_ textView: UITextView) {
        si textView.text == "" {
            self.textView.text = placeHolderText
            self.textView.textColor = UIColor.lightGray
        }
    }
    func tappedAwayFunction (_ sender: UITapGestureRecognizer) {
        textView.resignFirstResponder ()
    }

La méthode textViewDidChange met simplement à jour le libellé du nombre de caractères et envoie également les modifications à Pusher à l'aide de notre API backend (que nous créerons dans une minute).

TextViewShouldBeginEditing est obtenu à partir de UITextViewDelegate et il est déclenché lorsque la vue de texte est sur le point d'être modifiée. Ici, nous jouons essentiellement avec l'espace réservé, comme la méthode textViewDidEndEditing.

Enfin, dans la fonction tappedAwayFunction, nous définissons le rappel d'événement pour le geste que nous avons enregistré dans la section précédente. Dans la méthode, nous rejetons essentiellement le clavier.

Gestion des événements Pusher

Mettez à jour le contrôleur avec les méthodes suivantes:

func sendToPusher (texte: chaîne) {
        let params: Parameters = ["text": text, "from": randomUuid]
        Alamofire.request (TextEditorViewController.API_ENDPOINT + "/ update_text", méthode: .post, paramètres: params) .validate (). ResponseJSON {réponse dans
            changer response.result {
            cas. succès:
                print ("Réussi")
            cas .failure (laissez l'erreur):
                imprimer (erreur)
            }
        }
    }
    func listenForChanges () {
        pusher = Pusher (clé: "PUSHER_KEY", options: PusherClientOptions (
            hôte: .cluster ("PUSHER_CLUSTER")
        ))
        let channel = pusher.subscribe ("collabo")
        let _ = channel.bind (eventName: "text_update", callback: {(data: Any?) -> Void in
            si let data = data as? [Chaîne: AnyObject] {
                laissez fromDeviceId = data ["deviceId"] as! Chaîne
                si fromDeviceId! = self.randomUuid {
                    laissez text = data ["text"] as! Chaîne
                    self.textView.text = text
                    self.charactersLabel.text = String (format: "% i Characters", text.characters.count)
                }
            }
        })
        pusher.connect ()
    }

Dans la méthode sendToPusher, nous envoyons la charge utile à notre application principale à l'aide d'AlamoFire, qui, à son tour, l'enverra à Pusher.

Dans la méthode listenForChanges, nous écoutons ensuite les modifications apportées au texte et, le cas échéant, nous appliquons les modifications à la vue texte.

N'oubliez pas de remplacer la clé et le cluster par la valeur réelle que vous avez obtenue de votre tableau de bord Pusher.

Si vous avez suivi de près le didacticiel, votre TextEditorViewController devrait ressembler à ceci:

importer UIKit
    importer PusherSwift
    importer Alamofire
    classe TextEditorViewController: UIViewController, UITextViewDelegate {
        static let API_ENDPOINT = "http: // localhost: 4000";
        @IBOutlet var faible textView: UITextView!
        @IBOutlet caractères var faiblesLabel: UILabel!
        var pusher: Pusher!
        var chillPill = true
        var placeHolderText = "Commencez à taper ..."
        var randomUuid: String = ""
        remplacer la fonction func viewDidLoad () {
            super.viewDidLoad ()
            // Déclencheur de notification
            NotificationCenter.default.addObserver (auto, sélecteur: #selector (keyboardWillShow), nom: NSNotification.Name.UIKeyboardWillShow, objet: nil)
            NotificationCenter.default.addObserver (auto, sélecteur: #selector (keyboardWillHide), nom: NSNotification.Name.UIKeyboardWillHide, objet: nil)
            // Reconnaissance de gestes
            view.addGestureRecognizer (UITapGestureRecognizer (cible: auto, action: #selector (tappedAwayFunction (_ :))))
            // Définir le contrôleur comme délégué textView
            textView.delegate = self
            // Définir l'ID de l'appareil
            randomUuid = UIDevice.current.identifierForVendor! .uuidString
            // Écoutez les changements de Pusher
            listenForChanges ()
        }
        remplacer func viewWillAppear (_ animated: Bool) {
            super.viewWillAppear (animé)
            si self.textView.text == "" {
                self.textView.text = placeHolderText
                self.textView.textColor = UIColor.lightGray
            }
        }
        func keyboardWillShow (notification: NSNotification) {
            si laissez keyboardSize = (notification.userInfo? [UIKeyboardFrameBeginUserInfoKey] as? NSValue) ?. cgRectValue {
                si self.charactersLabel.frame.origin.y == 1.0 {
                    self.charactersLabel.frame.origin.y - = keyboardSize.height
                }
            }
        }
        func keyboardWillHide (notification: NSNotification) {
            si laissez keyboardSize = (notification.userInfo? [UIKeyboardFrameBeginUserInfoKey] as? NSValue) ?. cgRectValue {
                si self.view.frame.origin.y! = 1.0 {
                    self.charactersLabel.frame.origin.y + = keyboardSize.height
                }
            }
        }
        func textViewDidChange (_ textView: UITextView) {
            charactersLabel.text = String (format: "% i Characters", textView.text.characters.count)
            si textView.text.characters.count> = 2 {
                sendToPusher (texte: textView.text)
            }
        }
        func textViewShouldBeginEditing (_ textView: UITextView) -> Bool {
            self.textView.textColor = UIColor.black
            si self.textView.text == placeHolderText {
                self.textView.text = ""
            }
            retourner vrai
        }
        func textViewDidEndEditing (_ textView: UITextView) {
            si textView.text == "" {
                self.textView.text = placeHolderText
                self.textView.textColor = UIColor.lightGray
            }
        }
        func tappedAwayFunction (_ sender: UITapGestureRecognizer) {
            textView.resignFirstResponder ()
        }
        func sendToPusher (texte: chaîne) {
            let params: Parameters = ["text": text, "from": randomUuid]
            Alamofire.request (TextEditorViewController.API_ENDPOINT + "/ update_text", méthode: .post, paramètres: params) .validate (). ResponseJSON {réponse dans
                changer response.result {
                cas. succès:
                    print ("Réussi")
                cas .failure (laissez l'erreur):
                    imprimer (erreur)
                }
            }
        }
        func listenForChanges () {
            pusher = Pusher (clé: "PUSHER_KEY", options: PusherClientOptions (
                hôte: .cluster ("PUSHER_CLUSTER")
            ))
            let channel = pusher.subscribe ("collabo")
            let _ = channel.bind (eventName: "text_update", callback: {(data: Any?) -> Void in
                si let data = data as? [Chaîne: AnyObject] {
                    laissez fromDeviceId = data ["deviceId"] as! Chaîne
                    si fromDeviceId! = self.randomUuid {
                        laissez text = data ["text"] as! Chaîne
                        self.textView.text = text
                        self.charactersLabel.text = String (format: "% i Characters", text.characters.count)
                    }
                }
            })
            pusher.connect ()
        }
    }

Génial! Maintenant, nous devons faire le backend de l'application.

Création de l'application de nœud principal

Maintenant que nous avons terminé avec la partie Swift, nous pouvons nous concentrer sur la création du backend Node.js pour l'application. Nous allons utiliser Express pour pouvoir rapidement lancer quelque chose.

Créez un répertoire pour l'application Web, puis créez de nouveaux fichiers.

Le fichier index.js:

laissez path = require ('path');
    laissez Pusher = require ('pousseur');
    let express = require ('express');
    laissez bodyParser = require ('body-parser');
    laissez app = express ();
    let pusher = new Pusher (require ('./ config.js'));
    app.use (bodyParser.json ());
    app.use (bodyParser.urlencoded ({extended: false}));
    app.post ('/ update_text', fonction (req, res) {
      var payload = {text: req.body.text, deviceId: req.body.from}
      pusher.trigger ('collabo', 'text_update', charge utile)
      res.json ({succès: 200})
    });
    app.use (fonction (req, res, next) {
        var err = new Error ('Not Found');
        err.status = 404;
        suivant (err);
    });
    module.exports = app;
    app.listen (4000, fonction () {
      console.log ('App écoute sur le port 4000!');
    });

Dans le fichier JS ci-dessus, nous utilisons Express pour créer une application simple. Dans la route / update_text, nous recevons simplement la charge utile et la transmettons à Pusher. Rien de compliqué là-bas.

Créez également un fichier package.json:

{
      "main": "index.js",
      "dépendances": {
        "body-parser": "^ 1.17.2",
        "express": "^ 4.15.3",
        "chemin": "^ 0.12.7",
        "pousseur": "^ 1.5.1"
      }
    }

Le fichier package.json est l'endroit où nous définissons toutes les dépendances NPM.

Le dernier fichier à créer est un fichier config.js. C'est là que nous définirons les valeurs de configuration pour notre application Pusher:

module.exports = {
      appId: 'PUSHER_ID',
      clé: 'PUSHER_KEY',
      secret: 'PUSHER_SECRET',
      cluster: 'PUSHER_CLUSTER',
      chiffré: vrai
    };
N'oubliez pas de remplacer la clé et le cluster par la valeur réelle que vous avez obtenue de votre tableau de bord Pusher.

Exécutez maintenant npm install sur le répertoire puis le noeud index.js une fois l'installation de npm terminée. Vous devriez voir une appli écouter sur le port 4000! message.

Test de l'application

Une fois que votre serveur Web de noeud local est en cours d'exécution, vous devrez apporter quelques modifications pour que votre application puisse parler au serveur Web local. Dans le fichier info.plist, apportez les modifications suivantes:

Avec cette modification, vous pouvez créer et exécuter votre application et elle parlera directement avec votre application Web locale.

Conclusion

Dans cet article, nous avons expliqué comment créer un éditeur de texte collaboratif en temps réel sur iOS à l'aide de Pusher. J'espère que vous avez appris une ou deux choses en suivant le didacticiel. Pour vous entraîner, vous pouvez étendre les statuts pour prendre en charge plus d'instances.

Ce message a été publié pour la première fois sur Pusher.