Come creare un editor di testo collaborativo utilizzando Swift

Foto di rawpixel su Unsplash

Gli editor di testo sono sempre più popolari in questi giorni, sia che siano incorporati in un modulo di commento del sito Web o utilizzati come blocco note. Ci sono molti editor diversi tra cui scegliere. In questo post, impareremo non solo come creare una bella app mobile per l'editor di testi in iOS, ma anche come rendere possibile collaborare su una nota in tempo reale usando Pusher.

Si noti, tuttavia, che per semplificare l'applicazione, l'articolo non tratterà le modifiche simultanee. Pertanto, solo una persona può modificare contemporaneamente mentre gli altri guardano.

L'applicazione funzionerà attivando un evento quando viene inserito del testo. Questo evento verrà inviato a Pusher e quindi raccolto dal dispositivo del collaboratore e aggiornato automaticamente.

Per seguire in questo tutorial, avrai bisogno di quanto segue:

  1. Cocoapods: per installare, esegui gem install cocoapods sul tuo computer
  2. Xcode
  3. Un'applicazione Pusher: puoi creare un account e un'applicazione gratuiti qui
  4. Conoscenza della lingua Swift
  5. Node.js

Infine, per seguire questo tutorial è necessaria una conoscenza di base di Swift e Node.js.

Introduzione alla nostra applicazione iOS in Xcode

Avvia Xcode e crea un nuovo progetto. Chiamerò il mio Collabo. Dopo aver seguito la procedura guidata di configurazione e con lo spazio di lavoro aperto, chiudere Xcode e quindi cd nella radice del progetto ed eseguire il comando pod init. Questo dovrebbe generare un Podfile per te. Modifica i contenuti del Podfile:

# Rimuovi il commento dalla riga successiva per definire una piattaforma globale per il tuo progetto
    piattaforma: iOS, '9.0'
    target 'textcollabo' do
      # Commenta la riga successiva se non stai usando Swift e non vuoi usare framework dinamici
      use_frameworks!
      # Baccelli per anonchat
      pod 'Alamofire'
      pod 'PusherSwift'
    fine

Ora esegui l'installazione del pod comandi in modo che il gestore pacchetti Cocoapods possa estrarre le dipendenze necessarie. Al termine, chiudere Xcode (se aperto) e quindi aprire il file .xcworkspace che si trova nella radice della cartella del progetto.

Progettare le viste per la nostra applicazione iOS

Creeremo alcune visualizzazioni per la nostra applicazione iOS. Questi saranno la spina dorsale in cui agganceremo tutta la logica. Usando la storyboard di Xcode, rendi le tue visualizzazioni un po 'simili alle schermate qui sotto.

Questo è il file LaunchScreen.storyboard. Ho appena progettato qualcosa di semplice senza alcuna funzionalità.

Il prossimo storyboard che progetteremo è il Main.storyboard. Come suggerisce il nome, sarà quello principale. Qui è dove abbiamo tutti i punti di vista importanti collegati a qualche logica.

Qui abbiamo tre punti di vista.

La prima vista è progettata per assomigliare esattamente alla schermata di avvio, ad eccezione di un pulsante che abbiamo collegato per aprire la seconda vista.

La seconda vista è il controller di navigazione. È collegato a una terza vista che è un ViewController. Abbiamo impostato la terza vista come controller di root sul nostro controller di navigazione.

Nella terza vista, abbiamo un UITextView modificabile che viene inserito nella vista. C'è anche un'etichetta che dovrebbe essere un contatore di personaggi. Questo è il luogo in cui incrementeremo i caratteri mentre l'utente sta digitando il testo nella vista di testo.

Codifica dell'applicazione di editor di testo collaborativo iOS

Ora che abbiamo creato con successo le viste richieste per il caricamento dell'applicazione, la prossima cosa che faremo è iniziare a codificare la logica per l'applicazione.

Crea un nuovo file di classe cacao e chiamalo TextEditorViewController e collegalo alla terza vista nel file Main.storyboard. TextViewController dovrebbe inoltre adottare UITextViewDelegate. Ora puoi ctrl + trascinare UITextView e anche ctrl + trascinare UILabel nel file Main.storyboard nella classe TextEditorViewController.

Inoltre, è necessario importare le librerie PusherSwift e AlamoFire in TextViewController. Dovresti avere qualcosa di simile a questo dopo aver finito:

importare UIKit
    import PusherSwift
    import Alamofire
    class TextEditorViewController: UIViewController, UITextViewDelegate {
        @IBOutlet weak var textView: UITextView!
        @IBOutlet var var charactersLabel: UILabel!
    }

Ora dobbiamo aggiungere alcune proprietà di cui avremo bisogno in seguito nel controller.

importare UIKit
    import PusherSwift
    import Alamofire
    class TextEditorViewController: UIViewController, UITextViewDelegate {
        static let API_ENDPOINT = "http: // localhost: 4000";
        @IBOutlet weak var textView: UITextView!
        @IBOutlet var var charactersLabel: UILabel!
        var pusher: Pusher!
        var chillPill = true
        var placeHolderText = "Inizia a digitare ..."
        var randomUuid: String = ""
    }

Ora spezzeremo la logica in tre parti:

  1. Visualizza ed eventi tastiera
  2. Metodi UITextViewDelegate
  3. Gestione degli eventi Pusher.

Visualizza ed eventi tastiera

Apri TextEditorViewController e aggiornalo con i seguenti metodi:

override func viewDidLoad () {
        super.viewDidLoad ()

        // Trigger di notifica
        NotificationCenter.default.addObserver (self, selector: #selector (keyboardWillShow), nome: NSNotification.Name.UIKeyboardWillShow, oggetto: zero)
        NotificationCenter.default.addObserver (self, selector: #selector (keyboardWillHide), nome: NSNotification.Name.UIKeyboardWillHide, oggetto: nil)

        // Riconoscimento gestuale
        view.addGestureRecognizer (UITapGestureRecognizer (target: self, action: #selector (tappedAwayFunction (_ :))))

        // Imposta il controller come delegato textView
        textView.delegate = self

        // Imposta l'ID dispositivo
        randomUuid = UIDevice.current.identifierForVendor! .uuidString

        // Ascolta le modifiche da Pusher
        listenForChanges ()
    }

    override func viewWillAppear (_ animato: Bool) {
        super.viewWillAppear (animato)

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

    func keyboardWillShow (notifica: NSNotification) {
        if let keyboardSize = (notification.userInfo? [UIKeyboardFrameBeginUserInfoKey] come? NSValue) ?. cgRectValue {
            se self.charactersLabel.frame.origin.y == 1.0 {
                self.charactersLabel.frame.origin.y - = keyboardSize.height
            }
        }
    }

    funzion tastieraWillHide (notifica: NSNotification) {
        if let keyboardSize = (notification.userInfo? [UIKeyboardFrameBeginUserInfoKey] come? NSValue) ?. cgRectValue {
            if self.view.frame.origin.y! = 1.0 {
                self.charactersLabel.frame.origin.y + = keyboardSize.height
            }
        }
    }

Nel metodo viewDidLoad, abbiamo registrato le funzioni della tastiera in modo che rispondano agli eventi della tastiera. Abbiamo anche aggiunto i riconoscitori di gesti che elimineranno la tastiera quando tocchi all'esterno di UITextView. E impostiamo il delegato textView sul controller stesso. Infine, abbiamo chiamato una funzione per ascoltare i nuovi aggiornamenti (lo creeremo in seguito).

Nel metodo viewWillAppear, abbiamo semplicemente hackerato UITextView in modo da avere un testo segnaposto, perché, per impostazione predefinita, UITextView non ha quella funzione. Mi chiedo perché, Apple ...

Nelle funzioni keyboardWillShow e keyboardWillHide, abbiamo fatto in modo che l'etichetta del conteggio dei caratteri aumentasse con la tastiera e scendesse con essa, rispettivamente. Ciò impedirà alla tastiera di coprire l'etichetta quando è attiva.

Metodi UITextViewDelegate

Aggiorna TextEditorViewController con il seguente:

func textViewDidChange (_ textView: UITextView) {
        charactersLabel.text = String (formato: "% i Characters", textView.text.characters.count)
        se textView.text.characters.count> = 2 {
            sendToPusher (testo: textView.text)
        }
    }
    func textViewShouldBeginEditing (_ textView: UITextView) -> Bool {
        self.textView.textColor = UIColor.black
        if self.textView.text == placeHolderText {
            self.textView.text = ""
        }
        ritorna vero
    }
    func textViewDidEndEditing (_ textView: UITextView) {
        se textView.text == "" {
            self.textView.text = placeHolderText
            self.textView.textColor = UIColor.lightGray
        }
    }
    func tappedAwayFunction (_ mittente: UITapGestureRecognizer) {
        textView.resignFirstResponder ()
    }

Il metodo textViewDidChange aggiorna semplicemente l'etichetta del conteggio dei caratteri e invia anche le modifiche a Pusher usando la nostra API back-end (che creeremo tra un minuto).

TextViewShouldBeginEditing è ottenuto da UITextViewDelegate e viene attivato quando la vista di testo sta per essere modificata. Qui, in pratica, giochiamo con il segnaposto, lo stesso del metodo textViewDidEndEditing.

Infine, nella funzione tappedAway definiamo il callback dell'evento per il gesto che abbiamo registrato nella sezione precedente. Nel metodo, fondamentalmente ignoriamo la tastiera.

Gestione degli eventi Pusher

Aggiorna il controller con i seguenti metodi:

func sendToPusher (text: String) {
        let params: Parameters = ["text": text, "from": randomUuid]
        Alamofire.request (TextEditorViewController.API_ENDPOINT + "/ update_text", metodo: .post, parametri: params) .validate (). ResponseJSON {response in
            switch response.result {
            case .success:
                stampare ( "Riuscito")
            case .failure (let error):
                stampa (errore)
            }
        }
    }
    func hearForChanges () {
        pusher = Pusher (chiave: "PUSHER_KEY", opzioni: PusherClientOptions (
            host: .cluster ("PUSHER_CLUSTER")
        ))
        let channel = pusher.subscribe ("collabo")
        let _ = channel.bind (eventName: "text_update", callback: {(data: Any?) -> Annulla in
            if let data = data as? [String: AnyObject] {
                let fromDeviceId = data ["deviceId"] as! Stringa
                if fromDeviceId! = self.randomUuid {
                    let text = data ["text"] as! Stringa
                    self.textView.text = text
                    self.charactersLabel.text = String (formato: "% i Characters", text.characters.count)
                }
            }
        })
        pusher.connect ()
    }

Nel metodo sendToPusher, inviamo il payload alla nostra applicazione back-end utilizzando AlamoFire, che a sua volta lo invierà a Pusher.

Nel metodo hearForChanges, ascoltiamo quindi le modifiche al testo e, se ce ne sono, applichiamo le modifiche alla visualizzazione del testo.

Ricordare di sostituire la chiave e il cluster con il valore effettivo ottenuto dalla dashboard di Pusher.

Se hai seguito da vicino il tutorial, il tuo TextEditorViewController dovrebbe assomigliare a questo:

importare UIKit
    import PusherSwift
    import Alamofire
    class TextEditorViewController: UIViewController, UITextViewDelegate {
        static let API_ENDPOINT = "http: // localhost: 4000";
        @IBOutlet weak var textView: UITextView!
        @IBOutlet var var charactersLabel: UILabel!
        var pusher: Pusher!
        var chillPill = true
        var placeHolderText = "Inizia a digitare ..."
        var randomUuid: String = ""
        override func viewDidLoad () {
            super.viewDidLoad ()
            // Trigger di notifica
            NotificationCenter.default.addObserver (self, selector: #selector (keyboardWillShow), nome: NSNotification.Name.UIKeyboardWillShow, oggetto: zero)
            NotificationCenter.default.addObserver (self, selector: #selector (keyboardWillHide), nome: NSNotification.Name.UIKeyboardWillHide, oggetto: nil)
            // Riconoscimento gestuale
            view.addGestureRecognizer (UITapGestureRecognizer (target: self, action: #selector (tappedAwayFunction (_ :))))
            // Imposta il controller come delegato textView
            textView.delegate = self
            // Imposta l'ID dispositivo
            randomUuid = UIDevice.current.identifierForVendor! .uuidString
            // Ascolta le modifiche da Pusher
            listenForChanges ()
        }
        override func viewWillAppear (_ animato: Bool) {
            super.viewWillAppear (animato)
            if self.textView.text == "" {
                self.textView.text = placeHolderText
                self.textView.textColor = UIColor.lightGray
            }
        }
        func keyboardWillShow (notifica: NSNotification) {
            if let keyboardSize = (notification.userInfo? [UIKeyboardFrameBeginUserInfoKey] come? NSValue) ?. cgRectValue {
                se self.charactersLabel.frame.origin.y == 1.0 {
                    self.charactersLabel.frame.origin.y - = keyboardSize.height
                }
            }
        }
        funzion tastieraWillHide (notifica: NSNotification) {
            if let keyboardSize = (notification.userInfo? [UIKeyboardFrameBeginUserInfoKey] come? NSValue) ?. cgRectValue {
                if self.view.frame.origin.y! = 1.0 {
                    self.charactersLabel.frame.origin.y + = keyboardSize.height
                }
            }
        }
        func textViewDidChange (_ textView: UITextView) {
            charactersLabel.text = String (formato: "% i Characters", textView.text.characters.count)
            se textView.text.characters.count> = 2 {
                sendToPusher (testo: textView.text)
            }
        }
        func textViewShouldBeginEditing (_ textView: UITextView) -> Bool {
            self.textView.textColor = UIColor.black
            if self.textView.text == placeHolderText {
                self.textView.text = ""
            }
            ritorna vero
        }
        func textViewDidEndEditing (_ textView: UITextView) {
            se textView.text == "" {
                self.textView.text = placeHolderText
                self.textView.textColor = UIColor.lightGray
            }
        }
        func tappedAwayFunction (_ mittente: UITapGestureRecognizer) {
            textView.resignFirstResponder ()
        }
        func sendToPusher (text: String) {
            let params: Parameters = ["text": text, "from": randomUuid]
            Alamofire.request (TextEditorViewController.API_ENDPOINT + "/ update_text", metodo: .post, parametri: params) .validate (). ResponseJSON {response in
                switch response.result {
                case .success:
                    stampare ( "Riuscito")
                case .failure (let error):
                    stampa (errore)
                }
            }
        }
        func hearForChanges () {
            pusher = Pusher (chiave: "PUSHER_KEY", opzioni: PusherClientOptions (
                host: .cluster ("PUSHER_CLUSTER")
            ))
            let channel = pusher.subscribe ("collabo")
            let _ = channel.bind (eventName: "text_update", callback: {(data: Any?) -> Annulla in
                if let data = data as? [String: AnyObject] {
                    let fromDeviceId = data ["deviceId"] as! Stringa
                    if fromDeviceId! = self.randomUuid {
                        let text = data ["text"] as! Stringa
                        self.textView.text = text
                        self.charactersLabel.text = String (formato: "% i Characters", text.characters.count)
                    }
                }
            })
            pusher.connect ()
        }
    }

Grande! Ora dobbiamo creare il backend dell'applicazione.

Creazione dell'applicazione Node back-end

Ora che abbiamo finito con la parte Swift, possiamo concentrarci sulla creazione del backend Node.js per l'applicazione. Utilizzeremo Express per consentirci di eseguire rapidamente qualcosa.

Creare una directory per l'applicazione Web, quindi creare alcuni nuovi file.

Il file index.js:

let path = require ('percorso');
    let Pusher = request ('pusher');
    let express = require ('express');
    let bodyParser = require ('body-parser');
    let app = express ();
    let pusher = new Pusher (request ('./ config.js'));
    app.use (bodyParser.json ());
    app.use (bodyParser.urlencoded ({extended: false}));
    app.post ('/ update_text', funzione (req, res) {
      var payload = {text: req.body.text, deviceId: req.body.from}
      pusher.trigger ('collabo', 'text_update', payload)
      res.json ({success: 200})
    });
    app.use (funzione (req, res, next) {
        var err = new Error ('Not Found');
        err.status = 404;
        successivo (err);
    });
    module.exports = app;
    app.listen (4000, function () {
      console.log ('App in ascolto sulla porta 4000!');
    });

Nel file JS sopra, stiamo usando Express per creare una semplice applicazione. Nel percorso / update_text, riceviamo semplicemente il payload e lo passiamo a Pusher. Niente di complicato lì.

Crea anche un file package.json:

{
      "main": "index.js",
      "dipendenze": {
        "body-parser": "^ 1.17.2",
        "express": "^ 4.15.3",
        "percorso": "^ 0.12.7",
        "pusher": "^ 1.5.1"
      }
    }

Il file package.json è dove definiamo tutte le dipendenze NPM.

L'ultimo file da creare è un file config.js. Qui è dove definiremo i valori di configurazione per la nostra applicazione Pusher:

module.exports = {
      appId: 'PUSHER_ID',
      chiave: "PUSHER_KEY",
      segreto: "PUSHER_SECRET",
      cluster: "PUSHER_CLUSTER",
      crittografato: vero
    };
Ricordare di sostituire la chiave e il cluster con il valore effettivo ottenuto dalla dashboard di Pusher.

Ora esegui npm install nella directory e quindi nodo index.js una volta completata l'installazione di npm. Dovresti vedere un'app in ascolto sulla porta 4000! Messaggio.

Test dell'applicazione

Una volta che il server Web del nodo locale è in esecuzione, sarà necessario apportare alcune modifiche in modo che l'applicazione possa comunicare con il server Web locale. Nel file info.plist, apporta le seguenti modifiche:

Con questa modifica, puoi creare ed eseguire la tua applicazione e parlerà direttamente con la tua applicazione web locale.

Conclusione

In questo articolo, abbiamo spiegato come creare un editor di testo collaborativo in tempo reale su iOS usando Pusher. Spero che tu abbia imparato qualcosa o due seguendo il tutorial. Per fare pratica, è possibile espandere gli stati per supportare più istanze.

Questo post è stato pubblicato per la prima volta su Pusher.