Build & Deploy iOS app

You can package Scandi as a native iOS application

To deploy an iOS app, you need to have at least one native feature to be included in the app. We used a barcode scanner to search for items. You can add notifications management or anything else, but you have to add it as native code.

To deploy an iOS application you must have a macOS-powered device.

To display your site in iOS App, you first need to make sure it is deployed online. Then, you can use WebView to display the site inside of an iOS native app. To integrate native feature to the site, we suggest using the following approach:

  1. Create a Swift View Controller

  2. Display WebView inside of this controller

  3. Create a JavaScript file that would be injected into the app

  4. Use WebKit message handlers API to establish communication between WebView and native app

Creating an app (step-by-step)

1. Create a new XCode project

2. Select single view template

3. Go to ViewController.swift

4. Replace its content with the script bellow

import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate, WKScriptMessageHandler {
/// Assuming that the javascript sends message back, this function handles the message
/// - Parameters:
/// - userContentController: controller
/// - message: Message. Can be a String or [String:Any] to a single level.
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
print("something recived");
let messageBody = message.body as! [String: Any];
let action = messageBody["action"] as! String;
switch action {
case "barcodeClick":
print("barcode was clicked");
lazy var webView: WKWebView = {
let webCfg:WKWebViewConfiguration = WKWebViewConfiguration()
// Setup WKUserContentController instance for injecting user script
var userController:WKUserContentController = WKUserContentController()
var script:String?
// Get the contents of the file `inject.js`
if let filePath:String = Bundle.main.path(forResource: "inject", ofType:"js") {
script = try! String(contentsOfFile: filePath, encoding: .utf8)
let userScript:WKUserScript = WKUserScript(source: script!, injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: false)
// Add a script message handler for receiving "nativeProcess" event notifications posted from the JS document using window.webkit.messageHandlers.nativeProcess.postMessage script message
userController.add(self, name: "nativeProcess")
// Configure the WKWebViewConfiguration instance with the WKUserContentController
webCfg.userContentController = userController;
let webView = WKWebView(
frame: CGRect(
x: 0,
y: 0,
width: self.view.frame.width,
height: self.view.frame.height
configuration: webCfg
return webView
override func viewDidLoad() {
// Do any additional setup after loading the view.
self.navigationItem.title = "ScandiPWA"
let urlToLoad = URL(string: "")
// Do any additional setup after loading the view.
webView.load(URLRequest(url: urlToLoad!))

5. Create an inject.js file from empty template

6. Replace its content with the script bellow

function sendToNative(message) {
// Initiate the handle for Native process
const native = window.webkit.messageHandlers.nativeProcess
function onBarcodeClick() {
action: 'barcodeClick',
const BARCODE_SCANNER_ID = 'barcode-scanner';
const SEARCH_FIELD_ID = 'search-field';
function tryRenderingElement() {
setTimeout(() => {
if (document.getElementById(BARCODE_SCANNER_ID)) {
const searchElement = document.getElementById(SEARCH_FIELD_ID);
const barcodeButton = document.createElement('button'); = BARCODE_SCANNER_ID; = '30px'; = '30px'; = '1.4rem'; = `url("data:image/svg+xml,%3Csvg xmlns='' viewBox='0 0 480 480'%3E%3Cpath d='M80 48H16C7 48 0 55 0 64v64a16 16 0 0032 0V80h48a16 16 0 000-32zM464 336c-9 0-16 7-16 16v48h-48a16 16 0 000 32h64c9 0 16-7 16-16v-64c0-9-7-16-16-16zM464 48h-64a16 16 0 000 32h48v48a16 16 0 0032 0V64c0-9-7-16-16-16zM80 400H32v-48a16 16 0 00-32 0v64c0 9 7 16 16 16h64a16 16 0 000-32zM64 112h32v256H64zM128 112h32v192h-32zM192 112h32v192h-32zM256 112h32v256h-32zM320 112h32v192h-32zM384 112h32v256h-32zM128 336h32v32h-32zM192 336h32v32h-32zM320 336h32v32h-32z'/%3E%3C/svg%3E")`; = 'contain';
barcodeButton.onclick = onBarcodeClick;
const searchFieldWrapper = searchElement.parentNode.parentNode; = 'flex'; = 'center';
const searchField = searchElement.parentNode; = '1';
}, 0);
const pushState = window.history.pushState;
window.history.pushState = function () {
// Track page changes in React
pushState.apply(window.history, arguments);

7. Compile and test the application

As you can see injecting scripts into WebView is not that hard. When clicking on the barcode icon, the XCode console logs the barcode was clicked message. You can replace this logic with anything that matches your needs.

We will publish the full app code (including the barcode implementation) soon, so you can use it as a reference.

When the app is done, it's time to publish it!

Publishing the app

To publish an iOS app, you must be signed up for the Apple Developer Program. You can do this on the official site, here.

  1. Login to your App Store Connect

  2. Go to My Apps click +

  3. Enter app name, category, privacy, pricing

  4. Make screenshots of your application (size guide)

  5. Next, build your app with XCode

    1. For build platform select: Generic iOS Device

    2. Go to Product > Archive and build the app

    3. Select newly created archive, click Distribute App, select iOS AppStore

  6. Go back to your App Store Connect

  7. Find a version of your app, and click Submit for Review

For a more detailed guide, please see Chris's guide.

The process of review might take up to 10 days and result in refusal. ScandiPWA does not guarantee your app publishing. Please make sure the app you build complies with AppStore guidelines.