ScandiPWA
Create Magento AppCreate ScandiPWA AppUser ManualGitHub
  • Why Scandi
  • 🚀Quick-start Guide
  • 🗺️Roadmap
  • Introduction to the Stack
    • CMA, CSA, and ScandiPWA
    • Challenges
  • Setting up Scandi
    • Storefront Mode Setup
      • Proxying requests to server
    • Magento Mode Setup
    • Existing Magento 2 setup
    • Magento Commerce Cloud setup
    • Updating to new releases
      • Storefront mode upgrade
      • Magento mode upgrade
      • CMA upgrade
      • CSA upgrade
      • Custom ScandiPWA composer dependency update
      • Local ScandiPWA Composer Package Setup
    • Docker Setup [deprecated]
      • Legacy Docker setup
      • Migrating to CMA & CSA
  • Developing with Scandi
    • Override Mechanism
      • Overriding JavaScript
        • Overriding classes
        • Overriding non-classes
      • Overriding Styles
      • Overriding the HTML / PHP
      • Parent Themes
    • Extensions
      • Creating an extension
      • Installing an extension
      • Migrating from 3.x to 4.x
      • Publishing an extension
      • Extension Terminology
    • Working With Magento
      • Magento troubleshooting
      • Working with Magento modules
      • Working with GraphQL
      • GraphQL Security
      • Working with "granular cache"
    • Developer Tools
      • Debugging in VSCode
      • ScandiPWA CLI
      • Configuring ESLint
      • CSA Commands
    • Deploying Your App
      • Build & Deploy Android app
      • Build & Deploy iOS app
  • Structure
    • Directory Structure
    • Building Blocks
      • Components
        • Styling Components
      • Routes
      • Redux Stores
      • GraphQL Queries
      • Global Styles
      • The Util Directory
      • Type Checking
    • Application assets
    • Code Style
      • JavaScript Code Style
      • SCSS Code Style
  • Tutorials
    • Customizing Your Theme
      • Styling
        • Customizing the Global Styles
        • Adding a New Font
        • Overriding a Components Styles
        • Extending a Component's Styles
      • Customizing JavaScript
        • Customizing the Footer Copyright
        • Adding a New Page
        • Adding a Section in My Account
        • Adding a Tab on the Product Page
        • Creating a New Redux Store
    • Payment Method Integration
      • Setting Up for Development
      • Redirecting to the Payment Provider
      • Handling the Customer's Return
    • Creating a Custom Widget
      • Scandi CMS System Overview
      • Creating a Magento Widget
      • Implementing the Rendering
    • Video Tutorials
      • #1 Setting up and talking theory
      • #2 Templating in React
      • #3 Overriding a file
      • #4 Styling the application
      • #5 Patterns of ScandiPWA
    • Dark Mode Extension
    • Deploying Native Apps
    • Product 3D Model Extension
      • Part 1: Magento 3D Model Uploads
      • Part 2: GraphQL API
      • Part 3: Scandi Frontend
    • Social Share, Full Extension Development
      • STEP-1 and 2 Creating Magento 2 Module
      • STEP-3 Backend Configurations Settings
      • STEP-4 Simple GraphQl and Resolver
      • STEP-5 Creating Extension, Base Redux Store
      • STEP-6 Extension plugins
      • STEP-7 GraphQL types, Helpers
      • STEP-8 Query Field and FieldList
      • STEP-9 render Plugins and MSTP Plugin, Component creation
      • STEP-10 SocialShare Component Development
      • STEP-11 SocialShare for CategoryPage
      • TASK-1 Changing LinkedIn to Twitter
      • STEP-12 Comments for Admin Users
      • STEP-13 Final, bugfixes
    • Accessing Magento 2 Controllers
      • STEP-1 Creating Magento 2 Module
      • STEP-2 - Create Magento 2 Frontend Route and Basic Controller
      • STEP-3 Accessing Magento 2 Controller, Bypassing ScandiPWA frontend
      • STEP-4 Creating ScandiPWA Extension with additional dependencies
      • STEP-5 Creating Plugin and Axios request
  • About
    • Support
    • Release notes
    • Technical Information
    • Data Analytics
    • Contributing
      • Installation from Fork
      • Repository structure
      • Code contribution process
      • Submitting an Issue
      • Publishing ScandiPWA
Powered by GitBook
On this page

Was this helpful?

  1. Tutorials

Deploying Native Apps

In order to publish a PWA as a stand alone native application in the App Store it’s required to add extra functionality which is not available by opening PWA in a web browser. Allowing to receive push notifications, scan QR codes, and access user contacts might be such features.

To achieve this goal it’s required to create a native iOS app containing WKWebView instance (references as webView in the following example code) and WKScriptMessageHandler protocol implementation (JSMessageHandler in example). PWA should provide a UI and send messages using Web Kit API. This API is injected by WKWebView itself and is accessed by calling a function in a following manner:

window.webkit.messageHandlers.<message_name>.postMessage("Hello, native world!");

Example project is configured to respond to following messages: exampleMessage, exampleMessageWithArgument

To invoke them in the example project it’s required to make following function calls on PWA side:

window.webkit.messageHandlers.exampleMessage.postMessage();
window.webkit.messageHandlers.exampleMessageWithArgument.postMessage("Hello, native world!");

Returning to the native code of the example project let’s take a look at the JSMessageHandler class. It consists of the two parts. First one is implementation details and it declares constants with message names and declares two closure properties which should be called when a message from the PWA side is received.

Second part is the userContentController function and this is the WKScriptMessageHandler protocol function. It’s called when a message to Web Kit API is sent from the web side. WKScriptMessage is passed as an argument and this object contains the actual message name and arguments passed to it. It’s a WKScriptMessageHandler responsibility to identify the message and convert the argument passed if needed. In the following example it’s achieved by using a switch operator and calling a corresponding closure if message is identified.

class JSMessageHandler: NSObject, WKScriptMessageHandler {
    struct MessageNames {
        static let exampleMessage = "exampleMessage"
        static let exampleMessageWithArgument = "exampleMessageWithArgument"
    }
    var exampleAction: (() -> Void)?
    var exampleActionWithArgument:((String) -> Void)?
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        switch message.name {
        case MessageNames.exampleMessage:
            exampleAction?()
        case MessageNames.exampleMessageWithArgument:
            if let argument = message.body as? String {
                exampleActionWithArgument?(argument)
            } else {
                print("Wrong argument type for message \(message.name) : \(message.body)")
            }
        default:
            print("Unknown message: \(message.name)")
        }
}

Second class of the example project is a ViewController class which creates a JSMessageHandler instance and configures WKWebView instance with it. Following function creates WKWebViewConfiguration instance:

func createWebViewConfiguration() -> WKWebViewConfiguration {
        let contentController = WKUserContentController()
        let handler = JSMessageHandler()
        handler.exampleAction = { [weak self] in
            print("exampleMessage was sent from PWA and is handled in native code")
            self?.webView.evaluateJavaScript("window.exampleActionCallback()", completionHandler: nil)
        }
        handler.exampleActionWithArgument = { [weak self] argument in
            print("exampleMessageWithArgument '\(argument)' was sent from PWA and is handled in native code")
            self?.webView.evaluateJavaScript("window.exampleActionWithArgumentCallback('\(argument + " Hello from native code!")')", completionHandler: nil)
        }
        contentController.add(handler, name: JSMessageHandler.MessageNames.exampleMessage)
        contentController.add(handler, name: JSMessageHandler.MessageNames.exampleMessageWithArgument)
        let config = WKWebViewConfiguration()
        config.userContentController = contentController
        return config
    }

First of all there is a WKUserContentController instance created. Afterwards a JSMessageHandler instance is created and its actions are populated. In this example these actions just report to the console and then call window.exampleActionCallback() or window.exampleActionWithArgumentCallback() correspondingly. It’s suggested that these functions are added by PWA and it will process the calls to them. In real world project this process most likely will be asynchronous and native code will call back to PWA after user interaction (getting QR code, accessing contacts etc)

Afterwards the content controller is instructed to pass the message handling for each expected message to create a JSMessageHandler instance. WKWebViewConfiguration is created, configured and returned afterwards.

lazy var webView: WKWebView = {
        let wv = WKWebView(frame: .zero, configuration: createWebViewConfiguration())
PreviousDark Mode ExtensionNextProduct 3D Model Extension

Last updated 4 years ago

Was this helpful?

WKWebView instance is created as follows and should open PWA by URL later. The rest of the code is a standard UIKit / WebKit and is skipped for clarity. See attached example project. It’s configured to open a non-existing "" site. You can change it to your own PWA URL.

https://your.pwa.com
32KB
TutotialPWA.zip
archive
TutorialPWA.zip