Sådan oprettes en samarbejdende teksteditor ved hjælp af Swift

Foto af rawpixel på Unsplash

Tekstredaktører er i stigende grad populære i disse dage, uanset om de er integreret i en kommentarformular til webstedet eller brugt som et notesblok. Der er mange forskellige redaktører at vælge imellem. I dette indlæg lærer vi ikke kun, hvordan man bygger en smuk tekstredigerings-mobilapp i iOS, men også hvordan man gør det muligt at samarbejde om en note i realtid ved hjælp af Pusher.

Bemærk dog, at for at holde applikationen enkel, dækker artiklen ikke samtidige redigeringer. Derfor kan kun én person redigere på samme tid, mens andre ser på.

Programmet fungerer ved at udløse en begivenhed, når der indtastes noget tekst. Denne begivenhed sendes til Pusher og hentes derefter af samarbejdspartnerens enhed og opdateres automatisk.

For at følge med i denne tutorial skal du bruge følgende:

  1. Cocoapods: at installere, køre gem installere cocoapods på din maskine
  2. Xcode
  3. En Pusher-applikation: du kan oprette en gratis konto og applikation her
  4. Noget kendskab til det hurtige sprog
  5. node.js

Endelig er der brug for en grundlæggende forståelse af Swift og Node.js for at følge denne tutorial.

Kom godt i gang med vores iOS-applikation i Xcode

Start Xcode og opret et nyt projekt. Jeg kalder min Collabo. Efter at have fulgt opsætningsguiden og med arbejdsområdet åbent, skal du lukke Xcode og derefter cd til roden til dit projekt og køre kommandopoden init. Dette skal generere en Podfile til dig. Skift indholdet af Podfile:

# Fjern den næste linje for at definere en global platform for dit projekt
    platform: ios, '9.0'
    mål "textcollabo" do
      # Kommenter den næste linje, hvis du ikke bruger Swift og ikke ønsker at bruge dynamiske rammer
      use_frameworks!
      # Bælge til anonchat
      pod 'Alamofire'
      pod 'PusherSwift'
    ende

Kør nu kommandopoden, så Cocoapods-pakkehåndtereren kan trække de nødvendige afhængigheder ind. Når dette er afsluttet, skal du lukke Xcode (hvis åbent) og derefter åbne .xcworkspace-filen, der er i roden af ​​din projektmappe.

Designe visningerne til vores iOS-applikation

Vi vil oprette nogle visninger til vores iOS-applikation. Dette vil være rygraden, hvor vi vil koble al logikken ind. Brug Xcode-storyboardet til at få dine synspunkter til at ligne lidt på skærmbillederne nedenfor.

Dette er LaunchScreen.storyboard-filen. Jeg har lige designet noget enkelt uden funktionalitet overhovedet.

Det næste storyboard, vi designer, er Main.storyboard. Som navnet antyder, vil det være den vigtigste. Det er her vi har alle de vigtige synspunkter, der er knyttet til en vis logik.

Her har vi tre synspunkter.

Den første visning er designet til at ligne nøjagtigt som startskærmen med undtagelse af en knap, som vi har knyttet til, for at åbne den anden visning.

Den anden visning er navigationskontrollen. Det er knyttet til en tredje visning, som er en ViewController. Vi har indstillet den tredje visning som rodcontroller til vores Navigation Controller.

I den tredje visning har vi en UITextView, der kan redigeres, som er placeret i visningen. Der er også en etiket, der antages at være en tegntæller. Dette er det sted, hvor vi øger tegnene, når brugeren skriver tekst i tekstvisningen.

Kodning af iOS-samarbejdende tekstredigeringsprogram

Nu, hvor vi med succes har oprettet de visninger, der kræves for, at applikationen skal indlæses, er den næste ting, vi vil gøre, at starte kodningen af ​​applikationen.

Opret en ny kakaoklasse-fil og navngiv den TextEditorViewController, og link den til den tredje visning i Main.storyboard-filen. TextViewController skal også vedtage UITextViewDelegate. Nu kan du ctrl + trække UITextView og også ctrl + trække UIL-mærket i filen Main.storyboard til klassen TextEditorViewController.

Du skal også importere PusherSwift- og AlamoFire-bibliotekerne til TextViewController. Du skal have noget tæt på dette, når du er færdig:

importer UIKit
    import PusherSwift
    import Alamofire
    klasse TekstEditorViewController: UIViewController, UITextViewDelegate {
        @IBOutlet svag var textView: UITextView!
        @IBOutlet svage var tegn Etiket: UILabel!
    }

Nu skal vi tilføje nogle egenskaber, som vi har brug for et stykke tid senere i controlleren.

importer UIKit
    import PusherSwift
    import Alamofire
    klasse TekstEditorViewController: UIViewController, UITextViewDelegate {
        statisk let API_ENDPOINT = "http: // localhost: 4000";
        @IBOutlet svag var textView: UITextView!
        @IBOutlet svage var tegn Etiket: UILabel!
        var pusher: Pusher!
        var chillPill = sandt
        var placeHolderText = "Begynd at skrive ..."
        var randomUuid: String = ""
    }

Nu vil vi opdele logikken i tre dele:

  1. Vis og tastaturbegivenheder
  2. UITextViewDelegate metoder
  3. Håndtering af Pusher-begivenheder.

Vis og tastaturbegivenheder

Åbn TextEditorViewController, og opdater den med nedenstående metoder:

tilsidesætte func viewDidLoad () {
        super.viewDidLoad ()

        // Meddelelsesudløser
        NotificationCenter.default.addObserver (self, selector: #selector (keyboardWillShow), navn: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver (self, selector: #selector (keyboardWillHide), navn: NSNotification.Name.UIKeyboardWillHide, object: nil)

        // Gestusgenkender
        view.addGestureRecognizer (UITapGestureRecognizer (mål: self, action: #selector (tappedAwayFunction (_ :))))

        // Indstil controlleren som textView-delegat
        textView.delegate = self

        // Indstil enheds-ID
        randomUuid = UIDevice.current.identifierForVendor! .uuidString

        // Lyt for ændringer fra Pusher
        listenForChanges ()
    }

    tilsidesætte func-visningWillAppear (_ animeret: Bool) {
        super.viewWillAppear (animeret)

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

    func keyboardWillShow (anmeldelse: NSNotification) {
        hvis lad keyboardSize = (anmeldelse.userInfo? [UIKeyboardFrameBeginUserInfoKey] som? NSValue) ?. cgRectValue {
            hvis self.charactersLabel.frame.origin.y == 1.0 {
                self.charactersLabel.frame.origin.y - = keyboardSize.height
            }
        }
    }

    func keyboardWillHide (anmeldelse: NSNotification) {
        hvis lad keyboardSize = (anmeldelse.userInfo? [UIKeyboardFrameBeginUserInfoKey] som? NSValue) ?. cgRectValue {
            hvis self.view.frame.origin.y! = 1.0 {
                self.charactersLabel.frame.origin.y + = keyboardSize.height
            }
        }
    }

I viewDidLoad-metoden registrerede vi tastaturfunktionerne, så de vil svare på tastaturbegivenheder. Vi tilføjede også bevægelsesgenkendere, der afviser tastaturet, når du trykker uden for UITextView. Og vi indstiller textView-delegeret til selve controlleren. Til sidst kaldte vi en funktion til at lytte til nye opdateringer (vi opretter denne senere).

I viewWillAppear-metoden hackede vi simpelthen UITextView til at have en pladsholdertekst, fordi UITextView som standard ikke har denne funktion. Spekulerer på hvorfor, Apple ...

I keyboardWillShow og keyboardWillHide-funktionerne fik vi karaktertællemærkatet til at stige op med tastaturet og ned med det henholdsvis. Dette forhindrer, at tastaturet dækker etiketten, når det er aktivt.

UITextViewDelegate metoder

Opdater TextEditorViewController med følgende:

func textViewDidChange (_ textView: UITextView) {
        charactersLabel.text = String (format: "% i tegn", textView.text.characters.count)
        hvis textView.text.characters.count> = 2 {
            sendToPusher (tekst: textView.text)
        }
    }
    func textViewShouldBeginEditing (_ textView: UITextView) -> Bool {
        self.textView.textColor = UIColor.black
        hvis self.textView.text == placeHolderText {
            self.textView.text = ""
        }
        vende tilbage sandt
    }
    func textViewDidEndEditing (_ textView: UITextView) {
        hvis textView.text == "" {
            self.textView.text = placeHolderText
            self.textView.textColor = UIColor.lightGray
        }
    }
    func tappedAwayFunction (_ afsender: UITapGestureRecognizer) {
        textView.resignFirstResponder ()
    }

Metoden textViewDidChange opdaterer simpelthen tegnoptællingsetiketten og sender også ændringerne til Pusher ved hjælp af vores backend API (som vi vil oprette på et minut).

TekstenViewShouldBeginEditing er hentet fra UITextViewDelegate, og den udløses, når tekstvisningen er ved at blive redigeret. Herinde leger vi dybest set rundt med pladsholderen, det samme som metoden TextViewDidEndEditing.

Endelig definerer vi i den tappedeAwayFunktion begivenhedens tilbagekald for den gestus, vi registrerede i det foregående afsnit. I metoden afviser vi grundlæggende tastaturet.

Håndtering af Pusher-begivenheder

Opdater controlleren med følgende metoder:

func sendToPusher (tekst: streng) {
        let params: Parameters = ["text": text, "from": randomUuid]
        Alamofire.request (TextEditorViewController.API_ENDPOINT + "/ update_text", metode: .post, parametre: params). Validere (). ResponsJSON {svar i
            skift svar.resultat {
            sag. succes:
                udskrives ( "Lykkedes")
            case .failure (lad fejl):
                print (fejl)
            }
        }
    }
    func listenForChanges () {
        pusher = Pusher (nøgle: "PUSHER_KEY", indstillinger: PusherClientOptions (
            vært: .cluster ("PUSHER_CLUSTER")
        ))
        lad kanal = pusher.subscribe ("Collabo")
        lad _ = channel.bind (eventName: "text_update", tilbagekald: {(data: Enhver?) -> Annulleres
            hvis lad data = data som? [String: AnyObject] {
                let fromDeviceId = data ["deviceId"] som! Snor
                if fromDeviceId! = self.randomUuid {
                    lad tekst = data ["tekst"] som! Snor
                    self.textView.text = tekst
                    self.charactersLabel.text = String (format: "% i Characters", text.characters.count)
                }
            }
        })
        pusher.connect ()
    }

I sendToPusher-metoden sender vi nyttelasten til vores backend-applikation ved hjælp af AlamoFire, som igen vil sende den til Pusher.

I listenForChanges-metoden lytter vi derefter til ændringer i teksten, og hvis der er nogen, anvender vi ændringerne til tekstvisningen.

Husk at udskifte nøglen og klyngen med den aktuelle værdi, du har fået fra dit Pusher-instrumentbræt.

Hvis du har fulgt vejledningen nøje, skal din TextEditorViewController se sådan ud:

importer UIKit
    import PusherSwift
    import Alamofire
    klasse TekstEditorViewController: UIViewController, UITextViewDelegate {
        statisk let API_ENDPOINT = "http: // localhost: 4000";
        @IBOutlet svag var textView: UITextView!
        @IBOutlet svage var tegn Etiket: UILabel!
        var pusher: Pusher!
        var chillPill = sandt
        var placeHolderText = "Begynd at skrive ..."
        var randomUuid: String = ""
        tilsidesætte func viewDidLoad () {
            super.viewDidLoad ()
            // Meddelelsesudløser
            NotificationCenter.default.addObserver (self, selector: #selector (keyboardWillShow), navn: NSNotification.Name.UIKeyboardWillShow, object: nil)
            NotificationCenter.default.addObserver (self, selector: #selector (keyboardWillHide), navn: NSNotification.Name.UIKeyboardWillHide, object: nil)
            // Gestusgenkender
            view.addGestureRecognizer (UITapGestureRecognizer (mål: self, action: #selector (tappedAwayFunction (_ :))))
            // Indstil controlleren som textView-delegat
            textView.delegate = self
            // Indstil enheds-ID
            randomUuid = UIDevice.current.identifierForVendor! .uuidString
            // Lyt for ændringer fra Pusher
            listenForChanges ()
        }
        tilsidesætte func-visningWillAppear (_ animeret: Bool) {
            super.viewWillAppear (animeret)
            hvis self.textView.text == "" {
                self.textView.text = placeHolderText
                self.textView.textColor = UIColor.lightGray
            }
        }
        func keyboardWillShow (anmeldelse: NSNotification) {
            hvis lad keyboardSize = (anmeldelse.userInfo? [UIKeyboardFrameBeginUserInfoKey] som? NSValue) ?. cgRectValue {
                hvis self.charactersLabel.frame.origin.y == 1.0 {
                    self.charactersLabel.frame.origin.y - = keyboardSize.height
                }
            }
        }
        func keyboardWillHide (anmeldelse: NSNotification) {
            hvis lad keyboardSize = (anmeldelse.userInfo? [UIKeyboardFrameBeginUserInfoKey] som? NSValue) ?. cgRectValue {
                hvis self.view.frame.origin.y! = 1.0 {
                    self.charactersLabel.frame.origin.y + = keyboardSize.height
                }
            }
        }
        func textViewDidChange (_ textView: UITextView) {
            charactersLabel.text = String (format: "% i tegn", textView.text.characters.count)
            hvis textView.text.characters.count> = 2 {
                sendToPusher (tekst: textView.text)
            }
        }
        func textViewShouldBeginEditing (_ textView: UITextView) -> Bool {
            self.textView.textColor = UIColor.black
            hvis self.textView.text == placeHolderText {
                self.textView.text = ""
            }
            vende tilbage sandt
        }
        func textViewDidEndEditing (_ textView: UITextView) {
            hvis textView.text == "" {
                self.textView.text = placeHolderText
                self.textView.textColor = UIColor.lightGray
            }
        }
        func tappedAwayFunction (_ afsender: UITapGestureRecognizer) {
            textView.resignFirstResponder ()
        }
        func sendToPusher (tekst: streng) {
            let params: Parameters = ["text": text, "from": randomUuid]
            Alamofire.request (TextEditorViewController.API_ENDPOINT + "/ update_text", metode: .post, parametre: params). Validere (). ResponsJSON {svar i
                skift svar.resultat {
                sag. succes:
                    udskrives ( "Lykkedes")
                case .failure (lad fejl):
                    print (fejl)
                }
            }
        }
        func listenForChanges () {
            pusher = Pusher (nøgle: "PUSHER_KEY", indstillinger: PusherClientOptions (
                vært: .cluster ("PUSHER_CLUSTER")
            ))
            lad kanal = pusher.subscribe ("Collabo")
            lad _ = channel.bind (eventName: "text_update", tilbagekald: {(data: Enhver?) -> Annulleres
                hvis lad data = data som? [String: AnyObject] {
                    let fromDeviceId = data ["deviceId"] som! Snor
                    if fromDeviceId! = self.randomUuid {
                        lad tekst = data ["tekst"] som! Snor
                        self.textView.text = tekst
                        self.charactersLabel.text = String (format: "% i Characters", text.characters.count)
                    }
                }
            })
            pusher.connect ()
        }
    }

Store! Nu er vi nødt til at gøre backend af applikationen.

Bygning af backend Node-applikationen

Nu når vi er færdig med Swift-delen, kan vi fokusere på at oprette Node.js-backend til applikationen. Vi vil bruge Express, så vi hurtigt kan få noget i gang.

Opret et bibliotek til webapplikationen, og opret derefter nogle nye filer.

Filen index.js:

lad sti = kræve ('sti');
    lad Pusher = kræve ('pusher');
    lad udtrykke = kræve ('udtrykke');
    lad bodyParser = kræve ('body-parser');
    lad app = udtrykke ();
    let pusher = new Pusher (kræver ('./ config.js'));
    app.use (bodyParser.json ());
    app.use (bodyParser.urlencoded ({udvidet: falsk});
    app.post ('/ update_text', funktion (req, res) {
      var nyttelast = {tekst: req.body.text, deviceId: req.body.from}
      pusher.trigger ('Collabo', 'text_update', nyttelast)
      res.json ({succes: 200})
    });
    app.use (funktion (req, res, næste) {
        var err = new Error ('Not Found');
        err.status = 404;
        næste (err);
    });
    module.exports = app;
    app.listen (4000, funktion () {
      console.log ('App lytter på port 4000!');
    });

I JS-filen ovenfor bruger vi Express til at oprette en enkel applikation. I / update_text-ruten modtager vi simpelthen nyttelasten og videresender den til Pusher. Intet kompliceret der.

Opret en package.json-fil også:

{
      "main": "index.js",
      "afhængigheder": {
        "body-parser": "^ 1.17.2",
        "express": "^ 4.15.3",
        "sti": "^ 0.12.7",
        "pusher": "^ 1.5.1"
      }
    }

Pakken.json-filen definerer alle NPM-afhængigheder.

Den sidste fil, der skal oprettes, er en config.js-fil. Det er her vi definerer konfigurationsværdierne for vores Pusher-applikation:

module.exports = {
      appId: 'PUSHER_ID',
      nøgle: 'PUSHER_KEY',
      hemmelighed: 'PUSHER_SECRET',
      klynge: 'PUSHER_CLUSTER',
      krypteret: sandt
    };
Husk at udskifte nøglen og klyngen med den aktuelle værdi, du har fået fra dit Pusher-instrumentbræt.

Kør nu npm-installation i kataloget og derefter node index.js, når npm-installationen er afsluttet. Du skal se en app lytte på port 4000! besked.

Test af applikationen

Når du har din lokale node-webserver kørt, skal du foretage nogle ændringer, så din applikation kan tale med den lokale webserver. Foretag følgende ændringer i filen info.plist:

Med denne ændring kan du opbygge og køre din applikation, og den vil tale direkte med din lokale webapplikation.

Konklusion

I denne artikel har vi dækket, hvordan man bygger en realtime samarbejdende teksteditor på iOS ved hjælp af Pusher. Forhåbentlig har du lært en ting eller to fra at følge vejledningen. Til praksis kan du udvide statusene til at understøtte flere tilfælde.

Dette indlæg blev først offentliggjort til Pusher.