2023-02-13 04:07:25 +00:00
|
|
|
---
|
|
|
|
title: Swift + JavaScriptCore
|
2023-02-28 11:40:44 +00:00
|
|
|
pagination_prev: demos/bigdata/index
|
|
|
|
pagination_next: solutions/input
|
2023-02-13 04:07:25 +00:00
|
|
|
---
|
|
|
|
|
2023-04-27 09:12:19 +00:00
|
|
|
import current from '/version.js';
|
2023-05-07 13:58:36 +00:00
|
|
|
import CodeBlock from '@theme/CodeBlock';
|
2023-04-27 09:12:19 +00:00
|
|
|
|
2023-02-13 04:07:25 +00:00
|
|
|
iOS and MacOS ship with the JavaScriptCore framework for running JS code from
|
|
|
|
Swift and Objective-C. Hybrid function invocation is tricky, but explicit data
|
|
|
|
passing is straightforward. The demo shows a standalone Swift sample for MacOS.
|
|
|
|
|
|
|
|
The [Standalone scripts](/docs/getting-started/installation/standalone) can be
|
|
|
|
parsed and evaluated in a JSC context.
|
|
|
|
|
|
|
|
:::warning Platform Limitations
|
|
|
|
|
|
|
|
JavaScriptCore is primarily deployed in MacOS and iOS applications. There is
|
2023-05-25 01:36:15 +00:00
|
|
|
some experimental support through the Bun runtime, but apps intending to support
|
|
|
|
Windows / Linux / Android should try to embed [V8](/docs/demos/engines/v8).
|
2023-02-13 04:07:25 +00:00
|
|
|
|
|
|
|
:::
|
|
|
|
|
|
|
|
## Integration Details
|
|
|
|
|
|
|
|
Binary strings can be passed back and forth using `String.Encoding.isoLatin1`.
|
|
|
|
|
|
|
|
_Initialize JavaScriptCore_
|
|
|
|
|
|
|
|
JSC does not provide a `global` variable. It can be created in one line:
|
|
|
|
|
|
|
|
```swift
|
|
|
|
var context: JSContext!
|
|
|
|
do {
|
|
|
|
context = JSContext();
|
|
|
|
context.exceptionHandler = { _, X in if let e = X { print(e.toString()!); }; };
|
|
|
|
// highlight-next-line
|
|
|
|
context.evaluateScript("var global = (function(){ return this; }).call(null);");
|
|
|
|
} catch { print(error.localizedDescription); }
|
|
|
|
```
|
|
|
|
|
|
|
|
_Load SheetJS Scripts_
|
|
|
|
|
|
|
|
The main library can be loaded by reading the scripts from the file system and
|
|
|
|
evaluating in the JSC context:
|
|
|
|
|
|
|
|
```swift
|
|
|
|
let src = try String(contentsOfFile: "xlsx.full.min.js");
|
|
|
|
context.evaluateScript(src);
|
|
|
|
```
|
|
|
|
|
|
|
|
To confirm the library is loaded, `XLSX.version` can be inspected:
|
|
|
|
|
|
|
|
```swift
|
|
|
|
let XLSX: JSValue! = context.objectForKeyedSubscript("XLSX");
|
|
|
|
if let ver = XLSX.objectForKeyedSubscript("version") { print(ver.toString()); }
|
|
|
|
```
|
|
|
|
|
|
|
|
### Reading Files
|
|
|
|
|
|
|
|
`String(contentsOf:encoding:)` reads from a path and returns an encoded string:
|
|
|
|
|
|
|
|
```swift
|
|
|
|
/* read sheetjs.xls as Base64 string */
|
|
|
|
let file_path = shared_dir.appendingPathComponent("sheetjs.xls");
|
|
|
|
let data: String! = try String(contentsOf: file_path, encoding: String.Encoding.isoLatin1);
|
|
|
|
```
|
|
|
|
|
|
|
|
This string can be loaded into the JS engine and processed:
|
|
|
|
|
|
|
|
```swift
|
|
|
|
/* load data in JSC */
|
|
|
|
context.setObject(data, forKeyedSubscript: "payload" as (NSCopying & NSObjectProtocol));
|
|
|
|
|
|
|
|
/* `payload` (the "forKeyedSubscript" parameter) is a binary string */
|
|
|
|
context.evaluateScript("var wb = XLSX.read(payload, {type:'binary'});");
|
|
|
|
```
|
|
|
|
|
2023-05-25 01:36:15 +00:00
|
|
|
<details><summary><b>Direct Read</b> (click to show)</summary>
|
|
|
|
|
|
|
|
`Uint8Array` data can be passed directly, skipping string encoding and decoding:
|
|
|
|
|
|
|
|
```swift
|
|
|
|
let url = URL(fileURLWithPath: file)
|
|
|
|
var data: Data! = try Data(contentsOf: url);
|
|
|
|
let count = data.count;
|
|
|
|
/* Note: the operations must be performed in the closure! */
|
|
|
|
let wb: JSValue! = data.withUnsafeMutableBytes { (dataPtr: UnsafeMutableRawBufferPointer) in
|
|
|
|
// highlight-next-line
|
|
|
|
let ab: JSValue! = JSValue(jsValueRef: JSObjectMakeTypedArrayWithBytesNoCopy(context.jsGlobalContextRef, kJSTypedArrayTypeUint8Array, dataPtr.baseAddress, count, nil, nil, nil), in: context)
|
|
|
|
/* prepare options argument */
|
|
|
|
context.evaluateScript(String(format: "var readopts = {type:'array', dense:true}"));
|
|
|
|
let readopts: JSValue = context.objectForKeyedSubscript("readopts");
|
|
|
|
/* call XLSX.read */
|
|
|
|
let XLSX: JSValue! = context.objectForKeyedSubscript("XLSX");
|
|
|
|
let readfunc: JSValue = XLSX.objectForKeyedSubscript("read");
|
|
|
|
return readfunc.call(withArguments: [ab, readopts]);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
For broad compatibility with Swift versions, the demo uses the String method.
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
2023-02-13 04:07:25 +00:00
|
|
|
### Writing Files
|
|
|
|
|
|
|
|
When writing to binary string in JavaScriptCore, the result should be stored in
|
|
|
|
a variable and converted to string in Swift:
|
|
|
|
|
|
|
|
```swift
|
|
|
|
/* write to binary string */
|
|
|
|
context.evaluateScript("var out = XLSX.write(wb, {type:'binary', bookType:'xlsx'})");
|
|
|
|
|
|
|
|
/* `out` from the script is a binary string that can be stringified in Swift */
|
|
|
|
let outvalue: JSValue! = context.objectForKeyedSubscript("out");
|
|
|
|
var out: String! = outvalue.toString();
|
|
|
|
```
|
|
|
|
|
|
|
|
`String#write(to:atomically:encoding)` writes the string to the specified path:
|
|
|
|
|
|
|
|
```swift
|
|
|
|
/* write to sheetjsw.xlsx */
|
|
|
|
let out_path = shared_dir.appendingPathComponent("sheetjsw.xlsx");
|
|
|
|
try? out.write(to: out_path, atomically: false, encoding: String.Encoding.isoLatin1);
|
|
|
|
```
|
|
|
|
|
|
|
|
## Complete Example
|
|
|
|
|
|
|
|
:::note
|
|
|
|
|
2023-06-05 20:12:53 +00:00
|
|
|
This demo was tested in the following environments:
|
2023-02-13 04:07:25 +00:00
|
|
|
|
2023-06-05 20:12:53 +00:00
|
|
|
| Architecture | Swift | Date |
|
|
|
|
|:-------------|:--------|:-----------|
|
|
|
|
| `darwin-x64` | `5.7.2` | 2023-02-12 |
|
|
|
|
| `darwin-arm` | `5.8.1` | 2023-06-05 |
|
2023-02-13 04:07:25 +00:00
|
|
|
|
|
|
|
:::
|
|
|
|
|
|
|
|
The demo includes a sample `SheetJSCore` Wrapper class to simplify operations.
|
|
|
|
|
|
|
|
:::caution This demo only runs on MacOS
|
|
|
|
|
|
|
|
This example requires MacOS + Swift and will not work on Windows or Linux!
|
|
|
|
|
|
|
|
:::
|
|
|
|
|
|
|
|
0) Ensure Xcode is installed. Create a folder for the project:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
mkdir sheetjswift
|
|
|
|
cd sheetjswift
|
|
|
|
```
|
|
|
|
|
|
|
|
1) Download the standalone script and the test file:
|
|
|
|
|
|
|
|
<ul>
|
2023-04-27 09:12:19 +00:00
|
|
|
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>xlsx.full.min.js</a></li>
|
2023-02-13 04:07:25 +00:00
|
|
|
<li><a href="https://sheetjs.com/pres.numbers">pres.numbers</a></li>
|
|
|
|
</ul>
|
|
|
|
|
2023-05-07 13:58:36 +00:00
|
|
|
<CodeBlock language="bash">{`\
|
2023-04-27 09:12:19 +00:00
|
|
|
curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
|
|
|
|
curl -LO https://sheetjs.com/pres.numbers`}
|
2023-05-07 13:58:36 +00:00
|
|
|
</CodeBlock>
|
2023-02-13 04:07:25 +00:00
|
|
|
|
|
|
|
2) Download the Swift scripts for the demo
|
|
|
|
|
|
|
|
- [`SheetJSCore.swift`](pathname:///swift/SheetJSCore.swift) Wrapper library
|
|
|
|
- [`main.swift`](pathname:///swift/main.swift) Command-line script
|
|
|
|
|
|
|
|
```bash
|
|
|
|
curl -LO https://docs.sheetjs.com/swift/SheetJSCore.swift
|
|
|
|
curl -LO https://docs.sheetjs.com/swift/main.swift
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
3) Build the `SheetJSwift` binary:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
swiftc SheetJSCore.swift main.swift -o SheetJSwift
|
|
|
|
```
|
|
|
|
|
|
|
|
4) Test the program:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
./SheetJSwift pres.numbers
|
|
|
|
```
|
|
|
|
|
|
|
|
If successful, a CSV will be printed to console. The script also tries to write
|
|
|
|
to `SheetJSwift.xlsx`. That file can be verified by opening in Excel / Numbers.
|