Skip to content

Commit

Permalink
Add more Swift/JavaScript bindings to generated Xcode project.
Browse files Browse the repository at this point in the history
  • Loading branch information
colorizenl committed Oct 5, 2024
1 parent 3a4da5c commit b36d488
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 193 deletions.
12 changes: 6 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ buildscript {
}

dependencies {
classpath "com.gradle.publish:plugin-publish-plugin:1.2.1"
classpath "com.gradle.publish:plugin-publish-plugin:1.3.0"
}
}

plugins {
id "io.freefair.lombok" version "8.6"
id "io.freefair.lombok" version "8.10"
id "com.github.ben-manes.versions" version "0.51.0"
}

Expand All @@ -20,7 +20,7 @@ apply plugin: "com.gradle.plugin-publish"
apply plugin: "jacoco"

group = "nl.colorize"
version = "2024.5"
version = "2024.6"
compileJava.options.encoding = "UTF-8"

java {
Expand All @@ -39,10 +39,10 @@ dependencies {
implementation gradleApi()
implementation localGroovy()
implementation files("lib/appbundler-1.0ea.jar")
implementation "org.jsoup:jsoup:1.17.2"
implementation "org.commonmark:commonmark:0.22.0"
implementation "org.jsoup:jsoup:1.18.1"
implementation "org.commonmark:commonmark:0.23.0"
implementation "org.nanohttpd:nanohttpd-webserver:2.3.1"
testImplementation "org.junit.jupiter:junit-jupiter:5.10.2"
testImplementation "org.junit.jupiter:junit-jupiter:5.11.1"
testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}

Expand Down
11 changes: 1 addition & 10 deletions example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ xcode {
appId = "Example"
bundleId = "com.example"
appName = "Example App"
appVersion = "0.1"
bundleVersion = "0.1"
icon = "../resources/icon.png"
resourcesDir = "${buildDir}/staticsite"
}
Expand Down Expand Up @@ -106,12 +106,3 @@ task fatJar(type: Jar) {
createApplicationBundle.dependsOn fatJar
generatePWA.dependsOn generateStaticSite
xcodeGen.dependsOn generateStaticSite

task allMac(dependsOn: ["signApplicationBundle", "xcodeGen", "packageEXE", "generateStaticSite", "generatePWA"]) {
}

task allWindows(dependsOn: ["packageMSI", "packageEXE", "generateStaticSite", "generatePWA"]) {
}

task allLinux(dependsOn: ["generateStaticSite", "generatePWA"]) {
}
59 changes: 59 additions & 0 deletions example/web/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//-----------------------------------------------------------------------------
// Gradle Application Plugin
// Copyright 2010-2024 Colorize
// Apache license (http://www.apache.org/licenses/LICENSE-2.0)
//-----------------------------------------------------------------------------

loadImageFromScript = function() {
const image = document.createElement("img");
image.addEventListener("load", () => {
const description = "Loaded image size: " + image.width + "x" + image.height;
document.getElementById("ajaxImage").innerText = description;
});
image.src = "images/icon.png";
};

window.shareText = function() {
const field = document.getElementById("shareText");

if (navigator.share) {
navigator.share({
title: "Share text",
text: field.innerText
});
}
};

window.openInNativeBrowser = function(url) {
if (window.clrz) {
window.clrz.openNativeBrowser(url);
} else {
window.location.href = url;
}
};

window.loadStorageText = function() {
const field = document.getElementById("storageField");

if (window.clrz) {
window.clrz.loadPreferences();
field.value = "Native: " + localStorage.getItem("test");
} else {
field.value = "Local: " + localStorage.getItem("test");
}
};

window.saveStorageText = function() {
const field = document.getElementById("storageField");
localStorage.setItem("test", field.value);
if (window.clrz) {
window.clrz.savePreferences("test", field.value);
}
};

document.addEventListener("DOMContentLoaded", event => {
loadImageFromScript();
if (!navigator.share) {
document.getElementById("shareText").placeholder = "Not available";
}
});
28 changes: 17 additions & 11 deletions example/web/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<title>Example</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width, user-scalable=no" />
<link rel="stylesheet" href="example.css" />
<script src="example.js"></script>
</head>

<body>
Expand All @@ -13,16 +14,21 @@
<div id="ajaxImage">
Loading image using JavaScript...
</div>

<script>
document.addEventListener("DOMContentLoaded", event => {
const image = document.createElement("img");
image.addEventListener("load", () => {
const description = "Loaded image size: " + image.width + "x" + image.height;
document.getElementById("ajaxImage").innerText = description;
});
image.src = "images/icon.png";
});
</script>

<form onsubmit="event.preventDefault()">
<a href="https://clrz.nl" target="_blank">External link</a>
<button onclick="window.openInNativeBrowser('https://clrz.nl')">Native browser</button>
</form>

<form onsubmit="event.preventDefault()">
<input id="shareText" type="text" />
<button onclick="window.shareText()">Share</button>
</form>

<form onsubmit="event.preventDefault()">
<input id="storageField" type="text" />
<button onclick="window.loadStorageText()">Load app storage</button>
<button onclick="window.saveStorageText()">Save app storage</button>
</form>
</body>
</html>
54 changes: 33 additions & 21 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ The plugin is available from the [Gradle plugin registry](https://plugins.gradle
use the plugin in your Gradle project by adding the following to `build.gradle`:

plugins {
id "nl.colorize.gradle.application" version "2024.5"
id "nl.colorize.gradle.application" version "2024.6"
}

Building native Mac application bundles
Expand Down Expand Up @@ -75,7 +75,6 @@ The following shows an example on how to define this configuration in Gradle:
mainClassName = "com.example.app.Main"
outputDir = "${buildDir}"
options = ["-Xmx2g"]
startOnFirstThread = false
}

The following configuration options are available:
Expand All @@ -99,7 +98,6 @@ The following configuration options are available:
| `options` | no | List of JVM command line options. |
| `args` | no | List of command line arguments provided to the main class. |
| `icon` | yes | Location of the `.icns` file. |
| `launcher` | no | Generated launcher type. Either "native" (default) or "shell". |
| `signNativeLibraries` | no | Signs native libraries embedded in the application's JAR files. |
| `outputDir` | no | Output directory path, defaults to `build/mac`. |

Expand Down Expand Up @@ -131,22 +129,22 @@ and all of its dependencies. The following example shows how to turn your projec
file into a fat JAR:

```
jar {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
exclude "**/module-info.class"
exclude "**/META-INF/INDEX.LIST"
exclude "**/META-INF/*.SF"
exclude "**/META-INF/*.DSA"
exclude "**/META-INF/*.RSA"
manifest {
attributes "Main-Class": "com.example.ExampleApp"
}
jar {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
exclude "**/module-info.class"
exclude "**/META-INF/INDEX.LIST"
exclude "**/META-INF/*.SF"
exclude "**/META-INF/*.DSA"
exclude "**/META-INF/*.RSA"
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
manifest {
attributes "Main-Class": "com.example.ExampleApp"
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
```

There are alternative ways to create a fat JAR, if you need to retain the project's "normal"
Expand Down Expand Up @@ -264,7 +262,7 @@ The following configuration options are available via the `xcode` section:
| `appId` | yes | App ID in the form MyApp. |
| `bundleId` | yes | Apple bundleID in the form com.example. |
| `appName` | yes | App display name in the form My App. |
| `appVersion` | yes | App version number in the form 1.2.3. |
| `bundleVersion` | yes | Version number in the form 1.2.3. |
| `icon` | yes | PNG file that will be used to generate the app icons. |
| `iconBackgroundColor` | no | Color used to replace the icon alpha channel, e.g. #000000. |
| `resourcesDir` | yes | Directory to copy into the app's resources. |
Expand All @@ -276,6 +274,22 @@ Like the Mac application bundle, the `buildversion` system property can be used
version during the build. If this system property is not present, the build version is the same
as the app version.

### Communication between native code and JavaScript

It is common for hybrid mobile apps to feature some kind of interaction between the native code
and the web application's JavaScript. The generated Xcode project therefore includes some bindings
for common tasks, which can be called from JavaScript:

- `clrz.openNativeBrowser(url)` opens a link in the native browser (i.e. Mobile Safari),
rather than in the web view. Note that external links will be opened in the native browser
by default.
- `clrz.loadPreferences()` will load the native app store into the web application's
[local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). This can
be used for persistent data, since iOS aggressively cleans up local storage for infrequently
used apps.
- `clrz.savePreferences(name, value)` can be used to save name/value pairs to the native app
storage.

Building PWAs
-------------

Expand Down Expand Up @@ -369,8 +383,6 @@ The plugin comes with an example application, that can be used to test the plugi
- Run `gradle xcodeGen` to generate a Xcode project for a hybrid iOS app.
- Run `gradle generateStaticSite` to generate a website from Markdown templates.
- Run `gradle generatePWA` to create a PWA version of the aforementioned website.
- The shorthand tasks `gradle allMac`, `gradle allWindows`, and `gradle allLinux` can be used
to build all application types that are supported on that platform.

Building the example application uses the same system requirements and environment variables as
the plugin itself. Refer to the documentation for each application type for details.
Expand Down
45 changes: 33 additions & 12 deletions resources/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct HybridWebView: UIViewRepresentable {
let config: WKWebViewConfiguration = WKWebViewConfiguration()
config.preferences = preferences
config.userContentController = scriptController
config.setValue(true, forKey: "_allowUniversalAccessFromFileURLs")
config.setValue(true, forKey: "allowUniversalAccessFromFileURLs")

let webView = WKWebView(frame: .zero, configuration: config)
webView.scrollView.contentInsetAdjustmentBehavior = .never
Expand All @@ -39,8 +39,10 @@ struct HybridWebView: UIViewRepresentable {
}

let scriptBridge = ScriptBridge(webView: webView)
scriptController.add(scriptBridge, name: "openNativeBrowser")
scriptController.add(scriptBridge, name: "loadPreferences")
scriptController.add(scriptBridge, name: "savePreferences")
webView.navigationDelegate = scriptBridge

return webView
}
Expand All @@ -56,36 +58,55 @@ struct HybridWebView: UIViewRepresentable {

func generateBridge() -> String {
return """
window.loadPreferences = function(appName) {
window.webkit.messageHandlers.loadPreferences.postMessage({appName});
};
window.savePreferences = function(appName, name, value) {
window.webkit.messageHandlers.savePreferences.postMessage({appName, name, value});
window.clrz = {
openNativeBrowser: function(url) {
window.webkit.messageHandlers.openNativeBrowser.postMessage({url});
},
loadPreferences: function() {
window.webkit.messageHandlers.loadPreferences.postMessage({});
},
savePreferences: function(name, value) {
window.webkit.messageHandlers.savePreferences.postMessage({name, value});
}
};
"""
}
}

class ScriptBridge: NSObject, WKScriptMessageHandler {
class ScriptBridge: NSObject, WKScriptMessageHandler, WKNavigationDelegate {
var webView: WKWebView

init(webView: WKWebView) {
self.webView = webView
}

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url {
if url.absoluteString.starts(with: "https://") {
UIApplication.shared.open(url)
decisionHandler(.cancel)
return
}
}

decisionHandler(.allow)
}

func userContentController(_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage) {
let body: NSDictionary = message.body as! NSDictionary
let appName: String = body["appName"] as! String;

if message.name == "loadPreferences" {

if message.name == "openNativeBrowser" {
let url: String = body["url"] as! String;
UIApplication.shared.open(URL(string: url)!)
} else if message.name == "loadPreferences" {
loadPreferences(body)
} else if message.name == "savePreferences" {
savePreferences(body)
}
}

func loadPreferences(_ body: NSDictionary) {
for key in UserDefaults.standard.dictionaryRepresentation().keys {
let value = UserDefaults.standard.string(forKey: key)
Expand Down
Loading

0 comments on commit b36d488

Please sign in to comment.