| 
									
										
										
										
											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. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-22 06:32:55 +00:00
										 |  |  | The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone) | 
					
						
							|  |  |  | can be parsed and evaluated in a JSC context. | 
					
						
							| 
									
										
										
										
											2023-02-13 04:07:25 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | :::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'});"); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-08 04:47:04 +00:00
										 |  |  | <details> | 
					
						
							|  |  |  |   <summary><b>Direct Read</b> (click to show)</summary> | 
					
						
							| 
									
										
										
										
											2023-05-25 01:36:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | `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
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-12 06:47:52 +00:00
										 |  |  | :::note pass | 
					
						
							| 
									
										
										
										
											2023-02-13 04:07:25 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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       | | 
					
						
							|  |  |  | |:-------------|:--------|:-----------| | 
					
						
							| 
									
										
										
										
											2024-04-05 02:07:37 +00:00
										 |  |  | | `darwin-x64` | `5.10`  | 2024-04-04 | | 
					
						
							| 
									
										
										
										
											2024-03-12 06:47:52 +00:00
										 |  |  | | `darwin-arm` | `5.9.2` | 2024-02-21 | | 
					
						
							| 
									
										
										
										
											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! | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ::: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-27 01:49:35 +00:00
										 |  |  | 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: | 
					
						
							| 
									
										
										
										
											2023-02-13 04:07:25 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | mkdir sheetjswift | 
					
						
							|  |  |  | cd sheetjswift | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-27 01:49:35 +00:00
										 |  |  | 2) Download the SheetJS Standalone script and the test file. Save both files in | 
					
						
							| 
									
										
										
										
											2023-09-22 06:32:55 +00:00
										 |  |  | the project directory: | 
					
						
							| 
									
										
										
										
											2023-02-13 04:07:25 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | <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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-27 01:49:35 +00:00
										 |  |  | 3) Download the Swift scripts for the demo | 
					
						
							| 
									
										
										
										
											2023-02-13 04:07:25 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | - [`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 | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-27 01:49:35 +00:00
										 |  |  | 4) Build the `SheetJSwift` program: | 
					
						
							| 
									
										
										
										
											2023-02-13 04:07:25 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | swiftc SheetJSCore.swift main.swift -o SheetJSwift | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-27 01:49:35 +00:00
										 |  |  | 5) Test the program: | 
					
						
							| 
									
										
										
										
											2023-02-13 04:07:25 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | ```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. |