Skip to main content

Native App integration guide

Objective

Learn how to integrate our puzzles with mobile apps

Embedding

A recommended method to incorporate puzzles into a native app is by opening the puzzle's web page within the app using a WebView. This approach offers the advantage of providing a unified app experience, and updates to PuzzleMe remain separate from updates to the app itself. It's essential to ensure that the WebView is configured with JavaScript enabled.

Any cache and local storage utilized by the PuzzleMe iframe are confined to the WebView instance, meaning they're inaccessible to other apps, including browsers installed on the device.

For examples of app implementations, please refer to the sections below.

The Washington Post

Landing page for puzzles
Puzzle picker
Crossword

The New Yorker

Puzzles section
Crossword WebView

L.A. Times

Landing page
Puzzle picker
Crossword

The printing feature within PuzzleMe relies on the browser print function, specifically window.print(), which isn't present by default in WebView. Consequently, native app integration doesn't inherently support puzzle printing unless a hook is incorporated into the WebView during instantiation.

To enable window.print() within the WebView, you can utilize JavascriptInterface on Android and webkit.messageHandlers on iOS. Below are sample code snippets demonstrating how to implement this functionality.

iOS Swift code snippet
// Copyright © 2024 Amuse Labs. All rights reserved.
// This is a sample code that can be used in the native app for integrating Amuse Labs puzzles print functionality

import UIKit
import WebKit

/**
ViewController that instantiates a webview with print functionality by assigning a native handler to window.print call in the webview
Loads the daily puzzles page on start
*/
class ViewController: UIViewController, WKUIDelegate, WKScriptMessageHandler, WKNavigationDelegate {

@IBOutlet var webView: WKWebView!
let url = "https://amuselabs.com/demo/reference.html";

// returns content controller with necessary scripts added
private func getWebviewUserContentControllerWithScripts() -> WKUserContentController {
let contentController = WKUserContentController()
contentController.add(self, name: "printPage");
return contentController
}

// init WKWebView with content controller script and assign it to view
override func loadView() {
let preferences = WKPreferences()
preferences.javaScriptCanOpenWindowsAutomatically = true

// Create a configuration for the preferences
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences

// add userContentController to configurattion
// IMP: this needs to be done at the time of instantiating WKWebView for webbkit.messageHandlers to be accessbile from the webview
configuration.userContentController = getWebviewUserContentControllerWithScripts()

self.webView = WKWebView(frame: .zero, configuration: configuration)
self.webView.uiDelegate = self
self.webView.navigationDelegate = self

view = webView
}


override func viewDidLoad() {
super.viewDidLoad()

// Do any additional setup after loading the view, typically from a nib.
webView.allowsBackForwardNavigationGestures = true

// hook window.print to printPage script by adding user script
// adding this once is sufficient, do not need to add this script on every page load. It is accessible in all the pages that are loaded in the webview
var js: String = ""
js += "window.print = function () {webkit.messageHandlers.printPage.postMessage({}); }"
let userScript = WKUserScript(source: js, injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: true)
self.webView.configuration.userContentController.addUserScript(userScript)

// load the daily puzzle picker page
let myURL = URL(string: url);
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
}


override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
webView.load(navigationAction.request)
}
return nil
}

/**
Content controller scripts: bridge between iOS native and webview JS
Scripts can be called using webkit.messageHandlers.[scriptToBeCalled].postMessage
*/
public func userContentController(_ userContentController: WKUserContentController, didReceive
message: WKScriptMessage) {

/* Prints the page show in the webview using native print widget */
func printPage() {
let printController = UIPrintInteractionController.shared
let printFormatter = self.webView.viewPrintFormatter()
printController.printFormatter = printFormatter

// go back after printing
let completionHandler: UIPrintInteractionController.CompletionHandler = { (printController, completed, error) in
if !completed {
if error != nil {
//self.log("[PRINT] Failed:(\(e.localizedDescription))", type: .error)
self.webView.goBack()
} else {
//self.log("[PRINT] Canceled", type: .info)
self.webView.goBack()
}
} else {
self.webView.goBack()
}
}
// present the print controller
printController.present(animated: true, completionHandler: completionHandler)
}

// user script calls come in as messages (via webkit.messageHandlers
if let _ = message.body as? [String: Any], let name = message.name as? String {
switch name {

// call printPage if the message is "printPage"
case "printPage":
printPage()
break

default:
break

}
}
}



}
Android Java code snippet
// Copyright © 2024 Amuse Labs. All rights reserved.
// This is a sample code that can be used in the native app for integrating Amuse Labs puzzles print functionality

package com.amuselabs.example;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintJob;
import android.print.PrintManager;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;

/**
* Activity that loads a webview with print functionality by assigning JavaScript interface method for window.print in the webview
* Loads the daily puzzles page on creation
*/
public class PuzzleWebViewActivity extends AppCompatActivity {

public WebView webView;
String puzzlePageUrl = "https://amuselabs.com/demo/reference.html";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

webView = (WebView) findViewById(R.id.web_view);

// enable JS, storage in webview
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDomStorageEnabled(true);
webView.setClickable(true);

// add JSInterface to webview
// accessible in webview as window.JSInterface
webView.addJavascriptInterface(new JSInterface(), "JSInterface");

webView.setWebViewClient(new WebViewClient() {
@Override
// add the print functionality to all the pages that load in the webview
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// hook the window print handler to JSInterface method
String js = "window.print = function () { window.JSInterface.printPage(); }";
view.evaluateJavascript(js, null);
}
});

// load the daily puzzle picker page
webView.loadUrl(puzzlePageUrl);

}

/**
* Prints the current page rendered in the webview
* refer to https://developer.android.com/training/printing/html-docs
*/
private void createWebPrintJob() {

// Get a PrintManager instance
final PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
final String jobName = "Puzzle print";

// post to webiew as it needs to run in the same thread
webView.post(new Runnable() {
@Override
public void run() {
// Get a print adapter instance
PrintDocumentAdapter printAdapter = webView.createPrintDocumentAdapter(jobName);
// Create a print job with name and adapter instance
PrintJob printJob = printManager.print(jobName, printAdapter,
new PrintAttributes.Builder().build());

}
});
}

/**
* JavaScript interface class - bridge between webview JS and Native backend
*/
class JSInterface {
@JavascriptInterface
/**
* calls createWebPrintJob which prints the page currently rendered in webview
*/
public void printPage() {
createWebPrintJob();
}

}
}