Original: Using JavaScript in Swift projects:building a Markdown to HTML Editor
GABRIEL Theodoropoulos
Translated by: Kmyhy
Always wanted to write an article about how Swift and Javascript can be combined to build a powerful App that supports rich text. This is not the first time we've heard that we're going to embed the Javacript code in IOS, but when you're done reading this, you'll feel that the process will become as simple as ever, like magic, and you just need to do very little work. The best of these is a framework called the JavaScriptCore framework.
You might think, why is it always someone who loves to use JavaScript, why not use Swift to realize all the features? In fact, that's what I want to ask, and here we have a few reasons:
- IOS developers who have written web apps and forgotten what Javascript has written about it, through the JavaScriptCore framework, have the opportunity to use the language they love again.
- For some tasks, there is a good chance that there are existing JavaScript repositories, and that the functionality that you want to use with Swift actually does not exist. Why not use it now?
- It is likely that some tasks will be easier to do with JavaScript.
- You may want to control the APP's line remotely. You can put JavaScript code in the server instead of the App bundle. This is done with caution because it is likely to cause a catastrophe.
- Make your App more resilient and powerful.
- You have a strong curiosity to use JavaScript in your IOS case.
Of course, in addition to that, you might think of a better reason to use JavaScript in IOS. Now, don't be too busy, so let's look at the necessary background knowledge. First, JavaScript has its own environment, or, more specifically, it needs to run in a virtual machine. In the JavaScriptCore framework, the jsvirtualmachine is used to represent a virtual machine, and of course you don't usually deal with it. You can run multiple virtual machines in one App, and they can't exchange numbers directly.
Second, the most you can use is actuallyJSContext. This is true for the actual environment (context) of the JavaScript foot. There can be multiple context in a virtual machine (jsvirtualmachine), and you can pass the numbers in the context. As you can see in the continued content, theJSContextSwift code is exposed to JavaScript and the JavaScript code is exposed to Swift. We'll use it heavily, but most of the usage is the same.
JSContextAll of the values in the Jsvalue are the same as thoseJSValueused to represent arbitrary types of JavaScript values. If you want to visit JavaScript variables or functions from Swift, you can use theJSValueimage. Of course there are ways to convert jsvalue into specific types. For example, converttoString()to a string and convert it into a dictionary using thetoDictionary()method (which you'll see later). Here is a complete list of methods.
I recommend that you read the official JavaScriptCore framework documents. The foregoing may have a general understanding of the tools you will use, and will help you to understand the content of the back.
Now, let's officially start. Let's take a look at what today's recipe is all about.
Demo Case Overview
We're going through a simple demonstration of how the JavaScriptCore framework is extremely characteristic, and this case demonstrates how to use JavaScript in Swift. We're going to use the classic "Hello World" example (the example I like most), which saves a string value to a JavaScript change. Our first concern is how to visit this variable from Swift, and we might as well print it out using the Xcode console. We'll continue to do a few simple examples to step through more features. Of course, we're not just going to learn how to value Swift from JavaScript, we have to study the reverse direction. Therefore, we need both to write the Swift code and to write the JavaScript code. But don't worry, JavaScript is not so difficult to deal with. It's not difficult! Note that all of the output starts here in the console, so we can focus on what really pays attention.
We've learned a lot about the basics, and we can look at how to use another language in one language, "he said."
To be more realistic, let's use a third-party JavaScript Library to try it out. In the second part of the case, we'll compile a markdown/html converter, or we'll go through a "converter library" to do this for us. Our work is simply to collect the user input MarkDown text from the edit box (a simple one), and then pass it on to theUITextViewJavaScript environment and display the HTML returned in the JavaScript environment to one of themUIWebView. Use a button to make a switch, and adjust the code. Take a look at the map:
In the third and final part, we will demonstrate how to pass on the customization of the components and methods to JavaScript Context. In addition, we will create an image and the value of its nature in JavaScript in accordance with this definition. We'll end up showing an iPhone's list of types (model names), as well as their earliest and latest OS versions, as well as their graphics. The numbers are stored in a CSV file, and we'll use a third-party repository for parsing. To obtain the parsed figures, we will use our custom Swift in JavaScript to render the custom image by this type, and then return the results to Swift. "--yes. We'll use a TableView to show this list. As shown in the following image:
The above describe in general the three distinct tasks, that would let us get to know the JavaScriptCore framework. As there is a lot of things wrapped up together in the package of one, we'll have a initial menu screens that we'll use T O Navigate to the proper part of the project:
To steal lazy, we provide a start-up, which you can download here. When you're done downloading, you can start your javascriptcore journey. In this article, we'll do a few things, but eventually we'll see that most of them are actually standard routines, and for the ultimate goal, we have to repeat these routines.
Let's start out!
Call JavaScript from Swift
As described in the introduction, the main role of JavaScriptCore is theJSContextcategory. AnJSContextimage is a bridge between the JavaScript environment and the native JavaScript foot. So we need to declare this in the beginningBasicsViewController. In theBasicsViewController.swiftfile, find the head of the category and add the following variables:
var jscontext:jscontext!
jsContextThe image must be of a kind, if you initialize it as a local variable in the method, then you cannot visit it when the method is finished.
Now we have to enter the JavaScriptCore framework and add this sentence to the file head:
Import JavaScriptCore
Next you want to initializejsContextthe image and then use it. But before we do that, we'll write a basic JavaScript code. We will compile them in a jssource.js file, and you can find this file in the professional navigation of the beginning of the case. We're going to announce a string of "Hello world" in there, and then there are a few simple functions that we'll visit through IOS. If you don't learn JavaScript, it's really too simple, and you can read it at a glance.
Openjssource.jsThe file and add this variable at the beginning:
var HelloWorld = "Hello world!"
Printing this change in the console is the first goal we've come up with!
Go back toBasicsViewController.swiftthe archives and create a way to accomplish 2 tasks:
- To initialize the nature of what we declared earlierjsContext.
- Download the Jssource.js file and pass it on to JavaScript so it can visit the code that was programmed in the file.
BasicsViewControllerCreate a new method in, initialize thejsContextvariables. The method is very simple:
func initializeJS() {
self.jsContext = JSContext()
}
The second task above is divided into several steps, but it is also very simple. Let's take a look at the source code, and then we'll discuss it:
Func initializeJS() {
...
/ / Specify the jssource.js file path
If let jsSourcePath = Bundle.main.path(forResource: "jssource", ofType: "js") {
Do {
/ / Load the contents of the file into a String
Let jsSourceContents = try String(contentsOfFile: jsSourcePath)
// Add the script contained in jsSourceContents to the Javascript runtime via the jsContext object
self.jsContext.evaluateScript(jsSourceContents)
}
Catch {
Print(error.localizedDescription)
}
}
}
The notes in the source code clearly explain what they mean. First, we specify the Jssource.js file path, and then add the file to thejsSourceContentsstring (currently, these are the content you previously wrote in the Jssource.js file). If it succeeds, then this is important: we use it tojsContext"calculate" These JavaScript codes, and in this way we can immediately transfer our JS code to JavaScript.
Then add a new approach:
func helloWorld() {
if let variableHelloWorld = self.jsContext.objectForKeyedSubscript("helloWorld") {
print(variableHelloWorld.toString())
}
}
This method is simple, but it does not work very small. The core part of this approach isobjectForKeyedSubscript(_:)a sentence that we pass through to visit the JavasscripthellowWorl. The first sentence returns a jsvalue (if no value is returned as nil), and then puts itvariableHelloWorldin the save. Simply put, this completes our first goal, because we write some JavaScript in Swift, and we can handle it in any way possible! How do we deal with this variable that holds the "Hello World" string? To get it out to the console.
Now, we'reviewDidAppear(_:)calling these two new methods in:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.initializeJS()
self.helloWorld()
}
To run the APP, click the first title for the Basics button. Open Xcode's console, and our "Hello World" word is javascriptcore to the console!
When using Swift and JavaScript in a mix, it's definitely not just about defining a few variables, but then printing their values. So let's create the first JavaScript function, let's see how we can use it.
I can't find other simple examples, so use this function to synthesize the full name of the first and last names. In the Jssource.js file, add:
function getFullname(firstname, lastname) {
return firstname + " " + lastname;
}
The surname and the name in a person's name are used as two parameters of a function. Save the file and returnBasicsViewController.swift.
There are two steps to calling a JavaScript function in Swift:
First, askjsContextfor the function name that you want to call, which returns a Jsvalue, which is the same as the HelloWorld change we visited. Then, call this function by means of the method name, and pass in the parameters it needs. You'll see in a minute that there's a new way to do this now:
func jsDemo1() {
let firstname = "Mickey"
let lastname = "Mouse"
if let functionFullname = self.jsContext.objectForKeyedSubscript("getFullname") {
}
}
Now, SwiftfunctionFullnamehas quoted thegetFullnameJS function. Then the second step is to call this JS function:
func jsDemo1() {
let firstname = "Mickey"
let lastname = "Mouse"
if let functionFullname = self.jsContext.objectForKeyedSubscript("getFullname") {
// Call the function that composes the fullname.
if let fullname = functionFullname.call(withArguments: [firstname, lastname]) {
print(fullname.toString())
}
}
}
call(withArguments:)method is used to call the Getfullname function, and to cause it to perform.callmethod receives only one of the parameters, which is an array of arbitrary types, and if the function does not have a reference, you can pass a nil. In our case, we have FirstName and LastName. The return value of this method is also a jsvalue, and we'll print it to the console. In the back, you'll see that the return value of the method is not necessarily intentional to us, so we will not use it.
Now, let's call thejsDemo1()method:
override func viewDidAppear(_ animated: Bool) {
...
self.jsDemo1()
}
Running items, you'll see the following output in the console:
This is not interesting, but you have to understand that what you see is the result of calling the JS function in Swift. At the same time, we pass through this part of the content can always end up this kind of a fixed process:
- Construct a jscontext image.
- Download the JavaScript code and Calculate (evaluate) its value (or pass it to JavaScript environment).
- ThroughJSContextthe method of access to theobjectForKeyedSubscript(_:)JS function.
- Call the JS function and handle the return value (optional).
Handle JavaScript in a common
In the development, it is always inevitable that the code is wrong, but it is necessary for the developers to see that they will resolve it. If you are mixing JS and Swift, how do you know where to try? Is Swift still JS? It's easy to make mistakes in Swift, but can we see the bugs that happen on the JS side?
Fortunately, the JavaScriptCore framework provides an abnormal way to capture the JS environment in Swift. Observation is often a standard procedure, and we will understand it later, but how to deal with it is obviously a very important thing.
Back to the code we just programmed, we'll revise theinitializeJS()method to catch JS. In this method, after Jscontext initialization, add the following sentence:
func initializeJS() {
self.jsContext = JSContext()
// Add an exception handler.
self.jsContext.exceptionHandler = { context, exception in
if let exc = exception {
print("JS Exception:", exc.toString())
}
}
...
}
See, Exceptionhandler is a closed packet, and every time a jscontext happens, it calls this closure. It has two parameters: the context in which the Chang is located (that isJSContext), and the difference itself. This exception is a jsvalue. Here we are simply going to print out the usual messages to the console.
Let's try to make a difference to test whether this method is going to work. To do this, we have to compile another JS function in the Jssource.js, which uses an integer array as the parameters (integer and negative), and returns a dictionary that contains the maximum, minimum, and average values in this array.
Open the Jssource.js file and add a function:
function maxMinAverage(values) {
var max = Math.max.apply(null, values);
var min = Math.min.apply(null, values);
var average = Math.average(values);
return {
"max": max,
"min": min,
"average": average
};
}
The mistake in the code is that there is noMathaverage function in the image, so this sentence is completely wrong:
var average = Math.average (values);
Pretend we don't know this situation, go backBasicsViewController.swiftand add a new method:
func jsDemo2() {
let values = [10, -5, 22, 14, -35, 101, -55, 16, 14]
if let functionMaxMinAverage = self.jsContext.objectForKeyedSubscript("maxMinAverage") {
if let results = functionMaxMinAverage.call(withArguments: [values]) {
if let resultsDict = results.toDictionary() {
for (key, value) in resultsDict {
print(key, value)
}
}
}
}
}
First, we've created an array of random numbers. We use it asmaxMinAveragea reference to the method of adjustment, which is quoted in Swift through thefunctionMaxMinAverageimage. When calling the call method, we pass this array as the only number of parameters. If all goes well, we'll follow the Dictionary (notetoDictionary()method) to return the results, print the value one by one to the console (the Maxminaverage method returns a dictionary, so we print both the key and value)
It is time to test, but we must call thisjsDemo2()method first:
override func viewDidAppear(_ animated: Bool) {
...
self.jsDemo2()
}
Running APP, we expect to print out the maximum, minimum, and average of the array.
But the last ugly and very straightforward thing we got from the JS run-time environment:
JS Exception:TypeError:Math.average is not a function. (In ' Math.average (values) ', ' math.average ' is undefined)
Before we resolve this intentional mistake, let's think about the meaning of this. If you can't catch the JS, you won't be able to figure out where the bug really is. In order to save our time, especially for large, complex apps, it's not something that we intentionally design, so it's really a pain to look at it in a bad way to find a bug.
So, after teaching, we should solve the problem. In the Jssource.js file, modify the Code>minmaxaverage function as:
function maxMinAverage(values) {
var max = Math.max.apply(null, values);
var min = Math.min.apply(null, values);
var average = null;
if (values.length > 0) {
var sum = 0;
for (var i=0; i
Using Javascript in the Swift case: Editing an editor that transforms Markdown into HTML