--- title: Swift + JavaScriptCore pagination_prev: demos/bigdata/index pagination_next: 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](/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 some experimental support through the Bun runtime, but apps intending to support Windows / Linux / Android should try to embed [V8](/docs/demos/engines/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: ```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'});"); ```
Direct Read (click to show) `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.
### 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 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! ::: 0) Ensure Swift is installed by running the following command in the terminal: ```bash swiftc --version ``` If the command is not found, install Xcode. 1) Create a folder for the project: ```bash mkdir sheetjswift cd sheetjswift ``` 2) Download the SheetJS Standalone script and the test file. Save both files in the project directory: {`\ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js curl -LO https://sheetjs.com/pres.numbers`} 3) 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 ``` 4) Build the `SheetJSwift` program: ```bash swiftc SheetJSCore.swift main.swift -o SheetJSwift ``` 5) 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.