swift 富文本编辑_如何使用Swift构建协作式文本编辑器

swift 富文本编辑

by Neo Ighodaro

由新Ighodaro

如何使用Swift构建协作式文本编辑器 (How to build a collaborative text editor using Swift)

Text editors are increasingly popular these days, whether they’re embedded in a website comment form or used as a notepad. There are many different editors to choose from. In this post, we are not only going to learn how to build a beautiful text editor mobile app in iOS, but also how to make it possible to collaborate on a note in realtime using Pusher.

如今,无论文本编辑器是嵌入网站评论表单还是用作记事本,文本编辑器都越来越受欢迎。 有许多不同的编辑器可供选择。 在本文中,我们不仅将学习如何在iOS中构建漂亮的文本编辑器移动应用程序,还将学习如何使用Pusher实时协作处理笔记。

Please note, however, that to keep the application simple, the article will not cover concurrent edits. Therefore, only one person can edit at the same time while others watch.

但是请注意,为使应用程序简单,本文将不涉及并发编辑。 因此,只有一个人可以同时编辑,而其他人则可以观看。

The application will work by triggering an event when some text is entered. This event will be sent to Pusher and then picked up by the collaborator’s device and updated automatically.

输入某些文本后,该应用程序将通过触发事件来工作。 该事件将发送到Pusher,然后由协作者的设备接收并自动更新。

To follow along in this tutorial, you will need the following:

要继续学习本教程,您将需要以下内容:

  1. Cocoapods: to install, run gem install cocoapods on your machine

    Cocoapods:在计算机上安装,运行gem install cocoapods

  2. Xcode

    Xcode

  3. A Pusher application: you can create a free account and application here

    Pusher应用程序:您可以在此处创建免费帐户和应用程序

  4. Some knowledge of the Swift language

    Swift语言的一些了解

  5. Node.js

    Node.js

Lastly, a basic understanding of Swift and Node.js is needed to follow this tutorial.

最后,需要对Swift和Node.js有基本的了解才能遵循本教程。

Xcode我们的iOS应用程序入门 (Getting started with our iOS application in Xcode)

Launch Xcode and create a new project. I’ll call mine Collabo. After following the set up wizard, and with the workspace open, close Xcode and then cd to the root of your project and run the command pod init. This should generate a Podfile for you. Change the contents of the Podfile:

启动Xcode并创建一个新项目。 我称我为Collabo 。 遵循设置向导并打开工作区后,关闭Xcode,然后cd到项目的根目录并运行命令pod init 。 这应该为您生成一个Podfile 。 更改Podfile的内容:

# Uncomment the next line to define a global platform for your project    platform :ios, '9.0'
target 'textcollabo' do      # Comment the next line if you're not using Swift and don't want to use dynamic frameworks      use_frameworks!
# Pods for anonchat      pod 'Alamofire'      pod 'PusherSwift'    end

Now run the command pod install so the Cocoapods package manager can pull in the necessary dependencies. When this is complete, close Xcode (if open) and then open the .xcworkspace file that is in the root of your project folder.

现在运行命令pod install以便Cocoapods软件包管理器可以提取必要的依赖项。 完成此操作后,关闭Xcode(如果已打开),然后打开项目文件夹根目录中的.xcworkspace文件。

设计我们的iOS应用程序的视图 (Designing the views for our iOS application)

We are going to create some views for our iOS application. These will be the backbone where we will hook all the logic into. Using the Xcode story board, make your views look a little like the screenshots below.

我们将为我们的iOS应用程序创建一些视图。 这些将是我们将所有逻辑连接到的骨干。 使用Xcode故事板,使您的视图看起来像下面的屏幕截图。

This is the LaunchScreen.storyboard file. I’ve just designed something simple with no functionality at all.

这是LaunchScreen.storyboard文件。 我刚刚设计了一些简单的东西,根本没有任何功能。

The next storyboard we will design is the Main.storyboard. As the name implies, it will be the main one. This is where we have all the important views that are attached to some logic.

我们将设计的下一个故事板是Main.storyboard。 顾名思义,它将是主要的。 在这里,我们拥有所有附加在某些逻辑上的重要视图。

Here we have three views.

在这里,我们有三种看法。

The first view is designed to look exactly like the launch screen, with the exception of a button that we have linked to open up the second view.

第一个视图的设计看起来与启动屏幕完全一样,只是我们已链接以打开第二个视图的按钮除外。

The second view is the Navigation controller. It is attached to a third view which is a ViewController. We have set the third view as the root controller to our Navigation Controller.

第二个视图是导航控制器。 它附加到第三个视图,即ViewController 。 我们将第三个视图设置为导航控制器的根控制器。

In the third view, we have a UITextView that is editable which is placed in the view. There’s also a label that is supposed to be a character counter. This is the place where we will increment the characters as the user is typing text into the text view.

在第三个视图中,我们有一个可编辑的UITextView ,它位于视图中。 还有一个标签应该是字符计数器。 这是我们在用户在文本视图中键入文本时增加字符的地方。

编码iOS协作文本编辑器应用程序 (Coding the iOS collaborative text editor application)

Now that we have successfully created the views required for the application to load, the next thing we will do is start coding the logic for the application.

现在我们已经成功创建了应用程序加载所需的视图,接下来我们要做的是开始为应用程序逻辑编码。

Create a new cocoa class file and name it TextEditorViewController and link it to the third view in the Main.storyboard file. The TextViewController should also adopt the UITextViewDelegate. Now, you can ctrl+drag the UITextView and also ctrl+drag the UILabel in the Main.storyboard file to the TextEditorViewController class.

创建一个新的可可类文件,并将其命名为TextEditorViewController并将其链接到Main.storyboard文件中的第三个视图。 TextViewController还应该采用UITextViewDelegate 。 现在,您可以ctrl+drag UITextView ,也可以ctrl+drag Main.storyboard文件中的UILabelTextEditorViewController类。

Also, you should import the PusherSwift and AlamoFire libraries to the TextViewController. You should have something close to this after you are done:

另外,您应该将PusherSwiftAlamoFire库导入TextViewController 。 完成后,您应该对此有一些了解:

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

Now we need to add some properties that we will be needing sometime later in the controller.

现在,我们需要添加一些稍后将需要在控制器中使用的属性。

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

Now we will break up the logic into three parts:

现在,我们将逻辑分为三个部分:

  1. View and Keyboard events

    查看和键盘事件
  2. UITextViewDelegate methods

    UITextViewDelegate方法
  3. Handling Pusher events.

    处理Pusher事件。
查看和键盘事件 (View and Keyboard events)

Open the TextEditorViewController and update it with the methods below:

打开TextEditorViewController并使用以下方法对其进行更新:

override func viewDidLoad() {        super.viewDidLoad()        // Notification trigger        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)        // Gesture recognizer        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tappedAwayFunction(_:))))        // Set the controller as the textView delegate        textView.delegate = self        // Set the device ID        randomUuid = UIDevice.current.identifierForVendor!.uuidString        // Listen for changes from Pusher        listenForChanges()    }    override func viewWillAppear(_ animated: Bool) {        super.viewWillAppear(animated)        if self.textView.text == "" {            self.textView.text = placeHolderText            self.textView.textColor = UIColor.lightGray        }    }    func keyboardWillShow(notification: NSNotification) {        if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {            if self.charactersLabel.frame.origin.y == 1.0 {                self.charactersLabel.frame.origin.y -= keyboardSize.height            }        }    }    func keyboardWillHide(notification: NSNotification) {        if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {            if self.view.frame.origin.y != 1.0 {                self.charactersLabel.frame.origin.y += keyboardSize.height            }        }    }

In the viewDidLoad method, we registered the keyboard functions so they will respond to keyboard events. We also added gesture recognizers that will dismiss the keyboard when you tap outside the UITextView. And we set the textView delegate to the controller itself. Finally, we called a function to listen for new updates (we will create this later).

viewDidLoad方法中,我们注册了键盘功能,因此它们将响应键盘事件。 我们还添加了手势识别器,当您在UITextView外部点击时,这些手势识别器将关闭键盘。 然后我们将textView委托设置为控制器本身。 最后,我们调用了一个函数来侦听新的更新(我们将在以后创建)。

In the viewWillAppear method, we simply hacked the UITextView into having a placeholder text, because, by default, the UITextView does not have that feature. Wonder why, Apple…

viewWillAppear方法中,我们只是将UITextView为具有占位符文本,因为默认情况下, UITextView不具有该功能。 不知道为什么,苹果…

In the keyboardWillShow and keyboardWillHide functions, we made the character count label rise up with the keyboard and descend with it, respectively. This will prevent the Keyboard from covering the label when it is active.

keyboardWillShowkeyboardWillHide函数中,我们使字符计数标签分别随键盘上升和下降。 这样可以防止键盘在激活时覆盖标签。

UITextViewDelegate方法 (UITextViewDelegate methods)

Update the TextEditorViewController with the following:

使用以下命令更新TextEditorViewController

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

The textViewDidChange method simply updates the character count label and also sends the changes to Pusher using our backend API (which we will create in a minute).

textViewDidChange方法仅更新字符计数标签,并使用我们的后端API(将在几分钟内创建)将更改发送到Pusher。

The textViewShouldBeginEditing is gotten from the UITextViewDelegate and it is triggered when the text view is about to be edited. In here, we basically play around with the placeholder, same as the textViewDidEndEditing method.

textViewShouldBeginEditing是从UITextViewDelegate获得的,当要编辑文本视图时将触发它。 在这里,我们基本上使用占位符,与textViewDidEndEditing方法相同。

Finally, in the tappedAwayFunction we define the event callback for the gesture we registered in the previous section. In the method, we basically dismiss the keyboard.

最后,在tappedAwayFunction中,为上一节中注册的手势定义事件回调。 在该方法中,我们基本上关闭了键盘。

处理推杆事件 (Handling Pusher events)

Update the controller with the following methods:

使用以下方法更新控制器:

func sendToPusher(text: String) {        let params: Parameters = ["text": text, "from": randomUuid]
Alamofire.request(TextEditorViewController.API_ENDPOINT + "/update_text", method: .post, parameters: params).validate().responseJSON { response in            switch response.result {
case .success:                print("Succeeded")            case .failure(let error):                print(error)            }        }    }
func listenForChanges() {        pusher = Pusher(key: "PUSHER_KEY", options: PusherClientOptions(            host: .cluster("PUSHER_CLUSTER")        ))
let channel = pusher.subscribe("collabo")        let _ = channel.bind(eventName: "text_update", callback: { (data: Any?) -> Void in
if let data = data as? [String: AnyObject] {                let fromDeviceId = data["deviceId"] as! String
if fromDeviceId != self.randomUuid {                    let text = data["text"] as! String                    self.textView.text = text                    self.charactersLabel.text = String(format: "%i Characters", text.characters.count)                }            }        })
pusher.connect()    }

In the sendToPusher method, we send the payload to our backend application using AlamoFire, which will, in turn, send it to Pusher.

sendToPusher方法中,我们使用AlamoFire将有效负载发送到后端应用程序,然后将其发送到Pusher。

In the listenForChanges method, we then listen for changes to the text and, if there are any, we apply the changes to the text view.

然后,在listenForChanges方法中,我们侦听文本更改,如果有更改,则将更改应用于文本视图。

? Remember to replace the key and cluster with the actual value you have gotten from your Pusher dashboard.

请记住用您从Pusher仪表板获得的实际值替换密钥和群集。

If you have followed the tutorial closely, then your TextEditorViewController should look something like this:

如果您已经仔细阅读了本教程,那么您的TextEditorViewController应该看起来像这样:

import UIKit    import PusherSwift    import Alamofire
class TextEditorViewController: UIViewController, UITextViewDelegate {        static let API_ENDPOINT = "http://localhost:4000";
@IBOutlet weak var textView: UITextView!
@IBOutlet weak var charactersLabel: UILabel!
var pusher : Pusher!
var chillPill = true
var placeHolderText = "Start typing..."
var randomUuid : String = ""
override func viewDidLoad() {            super.viewDidLoad()
// Notification trigger            NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)            NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
// Gesture recognizer            view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tappedAwayFunction(_:))))
// Set the controller as the textView delegate            textView.delegate = self
// Set the device ID            randomUuid = UIDevice.current.identifierForVendor!.uuidString
// Listen for changes from Pusher            listenForChanges()        }
override func viewWillAppear(_ animated: Bool) {            super.viewWillAppear(animated)
if self.textView.text == "" {                self.textView.text = placeHolderText                self.textView.textColor = UIColor.lightGray            }        }
func keyboardWillShow(notification: NSNotification) {            if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {                if self.charactersLabel.frame.origin.y == 1.0 {                    self.charactersLabel.frame.origin.y -= keyboardSize.height                }            }        }
func keyboardWillHide(notification: NSNotification) {            if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {                if 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)
if textView.text.characters.count >= 2 {                sendToPusher(text: textView.text)            }        }
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {            self.textView.textColor = UIColor.black
if self.textView.text == placeHolderText {                self.textView.text = ""            }
return true        }
func textViewDidEndEditing(_ textView: UITextView) {            if textView.text == "" {                self.textView.text = placeHolderText                self.textView.textColor = UIColor.lightGray            }        }
func tappedAwayFunction(_ sender: UITapGestureRecognizer) {            textView.resignFirstResponder()        }
func sendToPusher(text: String) {            let params: Parameters = ["text": text, "from": randomUuid]
Alamofire.request(TextEditorViewController.API_ENDPOINT + "/update_text", method: .post, parameters: params).validate().responseJSON { response in                switch response.result {
case .success:                    print("Succeeded")                case .failure(let error):                    print(error)                }            }        }
func listenForChanges() {            pusher = Pusher(key: "PUSHER_KEY", options: PusherClientOptions(                host: .cluster("PUSHER_CLUSTER")            ))
let channel = pusher.subscribe("collabo")            let _ = channel.bind(eventName: "text_update", callback: { (data: Any?) -> Void in
if let data = data as? [String: AnyObject] {                    let fromDeviceId = data["deviceId"] as! String
if fromDeviceId != self.randomUuid {                        let text = data["text"] as! String                        self.textView.text = text                        self.charactersLabel.text = String(format: "%i Characters", text.characters.count)                    }                }            })
pusher.connect()        }    }

Great! Now we need to make the backend of the application.

大! 现在我们需要制作应用程序的后端。

构建后端节点应用程序 (Building the backend Node application)

Now that we are done with the Swift part, we can focus on creating the Node.js backend for the application. We are going to be using Express so that we can quickly get something running.

现在我们完成了Swift部分,我们可以集中精力为应用程序创建Node.js后端。 我们将使用Express,以便我们可以快速运行某些东西。

Create a directory for the web application and then create some new files.

为Web应用程序创建目录,然后创建一些新文件。

The index.js file:

index.js文件:

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

In the JS file above, we are using Express to create a simple application. In the /update_text route, we simply receive the payload and pass it on to Pusher. Nothing complicated there.

在上面的JS文件中,我们使用Express创建一个简单的应用程序。 在/update_text路由中,我们仅接收有效负载并将其传递给Pusher。 那里没什么复杂的。

Create a package.json file also:

还创建一个package.json文件:

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

The package.json file is where we define all the NPM dependencies.

package.json文件中,我们定义了所有NPM依赖项。

The last file to create is a config.js file. This is where we will define the configuration values for our Pusher application:

最后创建的文件是config.js文件。 在此处,我们将为Pusher应用程序定义配置值:

module.exports = {      appId: 'PUSHER_ID',      key: 'PUSHER_KEY',      secret: 'PUSHER_SECRET',      cluster: 'PUSHER_CLUSTER',      encrypted: true    };

? Remember to replace the key and cluster with the actual value you have gotten from your Pusher dashboard.

请记住用您从Pusher仪表板获得的实际值替换密钥和群集。

Now run npm install on the directory and then node index.js once the npm installation is complete. You should see an App listening on port 4000! message.

现在,在目录上运行npm install ,然后在npm安装完成后运行node index.js 。 您应该看到一个监听端口4000应用程序! 信息。

测试应用程序 (Testing the application)

Once you have your local node web server running, you will need to make some changes so your application can talk to the local web server. In the info.plist file, make the following changes:

一旦运行了本地节点Web服务器,就需要进行一些更改,以便您的应用程序可以与本地Web服务器通信。 在info.plist文件中,进行以下更改:

With this change, you can build and run your application and it will talk directly with your local web application.

进行此更改后,您可以构建和运行您的应用程序,它将直接与本地Web应用程序通信。

结论 (Conclusion)

In this article, we have covered how to build a realtime collaborative text editor on iOS using Pusher. Hopefully, you have learned a thing or two from following the tutorial. For practice, you can expand the statuses to support more instances.

在本文中,我们介绍了如何使用Pusher在iOS上构建实时协作文本编辑器。 希望您从本教程中学到了一两件事。 作为练习,您可以扩展状态以支持更多实例。

This post was first published to Pusher.

该帖子最初发布给Pusher

翻译自: https://www.freecodecamp.org/news/how-to-build-a-collaborative-text-editor-using-swift-df7402c82510/

swift 富文本编辑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值