6.0 KiB
title | pagination_prev | pagination_next |
---|---|---|
Swift + JavaScriptCore | demos/bigdata/index | solutions/input |
import current from '/version.js'; import CodeBlock from '@theme/CodeBlock';
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 SheetJS Standalone scripts can be parsed and evaluated in a JSC context.
:::danger Platform Limitations
JavaScriptCore is primarily deployed in MacOS and iOS applications. There is some experimental support through the Bun runtime, but apps intending to support Windows / Linux / Android should try to embed V8.
:::
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:
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:
let src = try String(contentsOfFile: "xlsx.full.min.js");
context.evaluateScript(src);
To confirm the library is loaded, XLSX.version
can be inspected:
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:
/* 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:
/* 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'});");
Direct Read (click to show)
Uint8Array
data can be passed directly, skipping string encoding and decoding:
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.
Writing Files
When writing to binary string in JavaScriptCore, the result should be stored in a variable and converted to string in 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:
/* 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 pass
This demo was tested in the following environments:
Architecture | Swift | Date |
---|---|---|
darwin-x64 |
5.10 |
2024-04-04 |
darwin-arm |
5.9.2 |
2024-02-21 |
:::
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!
:::
- Ensure Swift is installed by running the following command in the terminal:
swiftc --version
If the command is not found, install Xcode.
- Create a folder for the project:
mkdir sheetjswift
cd sheetjswift
- Download the SheetJS Standalone script and the test file. Save both files in the project directory:
- xlsx.full.min.js
- pres.numbers
{\ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js curl -LO https://sheetjs.com/pres.numbers
}
- Download the Swift scripts for the demo
SheetJSCore.swift
Wrapper librarymain.swift
Command-line script
curl -LO https://docs.sheetjs.com/swift/SheetJSCore.swift
curl -LO https://docs.sheetjs.com/swift/main.swift
- Build the
SheetJSwift
program:
swiftc SheetJSCore.swift main.swift -o SheetJSwift
- Test the program:
./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.