# SumDoc view-SDK for iOS

Open and display `.docx` / `.docm` documents inside your own iOS app, rendered
by the same engine and the same page that ship in SumDoc on the App Store
(version 2026.3.33, build 9). The document is opened by a native
Rust core (`FastDocMobileCore.xcframework`); layout and drawing happen in a
`WKWebView` page bundled with the SDK. No server, no network.

iOS 16+, device (arm64) and simulator slices. The page opens in view mode; the
pencil switches to editing, which is the same editor SumDoc ships. The SDK
carries the same release number as the App Store build and is released
together with it.

## What is in the box

| Path | What |
|---|---|
| `FastDocMobileCore.xcframework/` | the document core (device + simulator), C header inside |
| `assets/a4/` | the viewer page: `index.html`, JS, CSS, fonts, icons |
| `assets/sample.docx` | a small document to prove the integration |
| `bridge/FastDocWebBridge.swift`, `bridge/BridgingHeader.h` | routes page requests to the core (`WKScriptMessageHandler` named `fastdoc`) |
| `example/EditorWebView.swift` | reference host view: the `sumdoc://` scheme handler, boot script, bridge wiring, load |
| `VERSION` | version, build, date, checksums |

## Steps 1-2-3

### 1. Add to your app

- Drag `FastDocMobileCore.xcframework` into the target (Frameworks, Libraries → *Embed & Sign* not required for a static framework; *Do Not Embed* is fine). Xcode picks up the C header from the xcframework's `Headers/` on its own — no `HEADER_SEARCH_PATHS` needed.
- Add `bridge/FastDocWebBridge.swift` to the target and set `bridge/BridgingHeader.h` as the target's Objective-C bridging header (it imports the core's C header).
- Add the `assets/a4` folder as a **folder reference** (blue folder), so the page keeps its `a4/…` paths inside the app bundle; add `assets/sample.docx` as a resource.
- Add `example/EditorWebView.swift` to the target. It is self-contained: the `sumdoc://` scheme handler, the `OpenDoc` value it takes (name, `.docx` bytes, source URL or `nil`), the boot script and the bridge wiring.
- The xcframework has arm64 slices only. On an Intel Mac, or if the simulator destination is "Rosetta", set `EXCLUDED_ARCHS[sdk=iphonesimulator*] = x86_64` on the target.

With [xcodegen](https://github.com/yonaskolb/XcodeGen) the whole step is `example/project.yml` (copy the SDK next to it and run `xcodegen generate`); it is the spec this README was verified with.

### 2. Create the WebView

`file://` cannot load ES modules, so the page is served through a custom
scheme. `example/EditorWebView.swift` is the reference; the essentials:

```swift
let config = WKWebViewConfiguration()
config.setURLSchemeHandler(BundleSchemeHandler(), forURLScheme: "sumdoc")   // from the example
// boot script: UI language (else authored Russian), product name, "back" hook
config.userContentController.addUserScript(WKUserScript(source: """
  try{if(!localStorage.getItem('a4.ui-locale'))localStorage.setItem('a4.ui-locale','en');}catch(e){}
  window.__a4ProductBrand = "SumDoc";
  window.__fastdocMobileBack = function(){ webkit.messageHandlers.a4back.postMessage(""); };
  """, injectionTime: .atDocumentStart, forMainFrameOnly: true))
let webView = WKWebView(frame: .zero, configuration: config)
let bridge = FastDocWebBridge(webView: webView, docxBytes: bytes, documentPath: path,
                              displayName: name) { savedBytes in /* write; return the path */ }
webView.load(URLRequest(url: URL(string: "sumdoc://bundle/a4/index.html")!))
```

Keep the bridge alive as long as the web view lives (it owns the native
document and frees it in `deinit`). Create it **before** `load`. The
initializer is failable: `nil` means the core could not open the bytes (not a
`.docx`, or damaged) — tell the user instead of loading the page.

If you set `window.__fastdocMobileBack`, also register the handler it posts
to (`config.userContentController.add(handler, name: "a4back")`) — otherwise
the header "back" button does nothing. The example does both in its
`Coordinator`. Those three boot lines (language, product name, back hook) are
all the page needs from the host; everything about the document comes through
the bridge.

### 3. Open a document

The bridge takes the document as `Data`, so any source works: the bundled
sample (`Bundle.main.url(forResource: "sample", withExtension: "docx")`), a
file from the Files picker (`UIDocumentPickerViewController` / SwiftUI
`.fileImporter`, security-scoped URL → `Data`), or a file handed to your app via
"Open in…" (`onOpenURL`). "Save" from the page arrives in the closure you pass
to the bridge as the new `.docx` bytes.

The smallest host is one screen with a button:

```swift
struct RootView: View {
  @State private var doc: OpenDoc?
  var body: some View {
    if let doc {
      EditorWebView(doc: doc, onBack: { self.doc = nil }).ignoresSafeArea(edges: .bottom)
    } else {
      Button("Open sample") {
        let url = Bundle.main.url(forResource: "sample", withExtension: "docx")!
        doc = OpenDoc(name: "sample.docx", bytes: try! Data(contentsOf: url), source: nil)
      }
      .onOpenURL { url in                       // "Open in…" from Files, Mail, …
        let scoped = url.startAccessingSecurityScopedResource()
        defer { if scoped { url.stopAccessingSecurityScopedResource() } }
        if let bytes = try? Data(contentsOf: url) {
          doc = OpenDoc(name: url.lastPathComponent, bytes: bytes, source: url)
        }
      }
    }
  }
}
```

To be offered in "Open in…" at all, declare the type in `Info.plist`
(`CFBundleDocumentTypes` with `LSItemContentTypes` =
`org.openxmlformats.wordprocessingml.document`; `example/project.yml` has it).
Without the declaration `onOpenURL` never fires for `.docx`.

## Host contract (what the page expects from you)

- `webkit.messageHandlers.fastdoc` — installed by `FastDocWebBridge`; the page
  posts `{id, op, args}` and the bridge answers via
  `window.__fastdocNativeDeliver(json)`. You do not touch this.
- `localStorage['a4.ui-locale']` — UI language `en`/`ru`/`ar`; set it once in the
  boot script (the user's own choice made in the page survives).
- `window.__a4ProductBrand` — product name in the page's own texts.
- `window.__fastdocMobileBack()` — optional; the header "back" button.
- The page's `save` goes to the closure you pass to the bridge.

## Known limits

- iOS 16+; the xcframework carries arm64 device and simulator slices only.
- The core is large (hundreds of MB uncompressed in the xcframework; the linked
  Release binary grows by about 130 MB, the page adds 21 MB — a minimal host
  app is ~150 MB on disk).
- The page shows a blinking caret at the start of the document even in view
  mode (same as SumDoc on the App Store).
- The page is a full editor in view mode, not a lightweight viewer.

## Verified

2026-09-14, empty SwiftUI host built with xcodebuild from `example/project.yml`,
this xcframework, bridge, page and sample, no other code:

Time from handing the document to `EditorWebView` until the sample's text is in
the page's accessible document model (host-side probe, Release build):

| Where | First launch after install | Relaunch |
|---|---|---|
| iPhone 12, iOS 26.5.2 | 2.5 s | 2.1 s |
| iPhone 11 Pro Max, iOS 26.6.1 | 3.2 s | 1.8 s |
| iPhone 17 Pro Max simulator, iOS 26.5 | — | 1.7 s |

On the simulator, from the launch command to the rendered page on screen
(Debug build, screenshots): 3.4 s first launch, 2.0 s relaunch.

"Open in…" (`onOpenURL` with a `.docx` file URL) opened the file on both the
simulator and the device; the page's "back" returned to the host screen.

## Terms

Free to evaluate and to build integration prototypes. Licensing for
production use and support: contact us via sumoffice.com.
