Skip to content
 
 

Repository files navigation

3D Touch Cordova plugin

Why this fork exists

Forked from upstream because the plugin's native iOS code still used the deprecated UIWebView API, and our app only ships a WKWebView.

Published as @herdwatch/cordova-plugin-3dtouch.

Changes from upstream:

  • Replaced the deprecated UIWebView with WKWebView in the native iOS plugin (ThreeDeeTouch.h/.m), removing the old #if !WK_WEB_VIEW_ONLY conditional entirely
  • Renamed the JS callback API from onHomeIconPressed to registerQuickActionListener to match upstream's later API
  • Republished the package under the @herdwatch npm scope
  • Delivered quick actions under UIScene (see below), which cordova-ios 8 adopts

by Eddy Verbruggen

Quick actions under UIScene (cordova-ios 8)

From cordova-ios 8 the app declares UIApplicationSceneManifest, and UIKit then stops calling application:performActionForShortcutItem: — the only hook this plugin had. Quick actions arrive two other ways instead, and neither reached the plugin:

  • app already running: UIKit calls windowScene:performActionForShortcutItem: on the scene delegate;
  • app launched by the action: there is no callback at all. The item comes in as UISceneConnectionOptions.shortcutItem while the scene connects.

Both are now handled on CDVSceneDelegate. The launch case is a swizzle of scene:willConnectToSession:options: rather than a category method, because CDVSceneDelegate already implements that selector to forward URL contexts — a category would REPLACE it and take deep links down with it — so the original is called first. The item is then held until the plugin exists, since a plugin is instantiated after the scene connects; without that a cold start would deliver the action to nothing.

AppDelegate+threedeetouch.m keeps the pre-scene path for a build that has not adopted UIApplicationSceneManifest. Cordova iOS 9 removes AppDelegate extension points altogether, at which point that half can go.

Testing

Two suites, because they answer different questions and neither can answer the other's.

npm ci

npm run test:ios     # cordova-ios 8.1.1
npm run test:ios7    # cordova-ios 7.1.1
npm run test:e2e     # quick action actually arriving, on a booted simulator

test:ios* — cordova-paramedic. Builds a throwaway app around the plugin, runs the Jasmine specs in tests/ inside it, and reports the results. The assertions cover the JS surface and the native round trips; the real value is the build, because CDVSceneDelegate exists only from cordova-ios 8, and a scene symbol referenced unconditionally breaks 7.x at compile time — silently, for anyone still on it. That is why there are two commands rather than one.

cordova-paramedic is a git pin rather than a version: the newest release on npm is 0.5.0, from 2015, and the two fixes this needs — accepting a versioned platform spec such as ios@8.1.1, and passing plugin install arguments as separate argv entries — are only on master. Pinned by commit, not by branch, so a run cannot pick up unreleased changes on its own.

test:e2e — the quick action itself. A quick action can only be raised by long-pressing the app icon, which is SpringBoard's business and therefore outside the app; paramedic cannot reach it at all. tests/e2e is the smallest app that can be asked the question — one static shortcut and a page that records what arrives — driven through Appium: it long-presses the icon, taps the entry and checks the callback fired. It installs the plugin from this repository, so it tests these sources rather than a published version.

It needs Appium with the XCUITest driver (npm i -g appium && appium driver install xcuitest) and a server on port 4723, and it uses whichever simulator is booted unless IOS_UDID says otherwise.

Two things about driving the Home Screen cost hours to find, and both look like "the app has no quick action" from the outside:

  • mobile: backgroundApp breaks the menu. After it, a long press on the icon either does nothing or starts icon-rearrange mode. Leave the app with mobile: pressButton {name: 'home'} — what a person does — and the Home Screen stays responsive. Press it once; twice is the app switcher.
  • The accessibility tree covers every Home Screen page. So finding the icon by accessibility id succeeds for an icon nobody can see, and pressing at its rect lands on empty space on the visible page — which is itself how rearrange mode starts. Check hittable and swipe pages until it is true.

0. Index

  1. Description
  2. Screenshots
  3. Installation
  4. Usage
  5. Static Home Icon Actions
  6. Changelog

1. Description

Add 3D Touch capabilities to your Cordova app:

  • Quick Action for Home Screen icons. Static and Dynamic.
  • Enable Link preview for external links.

2. Screenshots

     

3. Installation

Latest stable version from npm:

$ cordova plugin add @herdwatch/cordova-plugin-3dtouch

Ionic:

$ npm i @awesome-cordova-plugins/three-dee-touch
$ ionic cordova plugin add @herdwatch/cordova-plugin-3dtouch

Bleeding edge version from Github:

$ cordova plugin add https://github.com/herdwatch-apps/cordova-plugin-3dtouch

ThreeDeeTouch.js is brought in automatically. It adds a global ThreeDeeTouch object which you can use to interact with the plugin.

4. Usage

There is a runnable app in demo/ covering everything below — cd demo && ./run.sh puts it on a booted simulator. Or read on for some copy-pasteable samples.

Make sure to wait for deviceready before using any of these functions.

Note that all these functions have optional callbacks, but mostly they're irrelevant, except for the first function here:

isAvailable

You need an iPhone 6S or some future tech to use the features of this plugin, so you can check at runtime if the user's device is supported.

  ThreeDeeTouch.isAvailable(function (avail) {
    // 'avail' is a boolean
    alert("avail? " + avail)
  });

watchForceTouches

You can get a notification when the user force touches the webview. The plugin defines a Force Touch when at least 75% of the maximum force is applied to the screen. Your app will receive the x and y coordinates, so you have to figure out which UI element was touched.

Useful for context menu's, zooming in on images, whatnot.

  ThreeDeeTouch.watchForceTouches(function(result) {
    console.log("force touch % " + result.force); // 84
    console.log("force touch timestamp " + result.timestamp); // 1449908744.706419
    console.log("force touch x coordinate " + result.x); // 213
    console.log("force touch y coordinate " + result.y); // 41
  });

You can also track in JS which was the last element that received an ontouchstart event, remember the timestamp when that happened and correlate that to the timestamp of the force touch. If those are very close to each other you can safely assume the force touch was on that element.

configureQuickActions

When your app starts you can add those fancy Quick Actions to the Home Screen icon. You can configure up to four icons and they are 'cached' until you pass in a new set of icons. So you don't need to do this every time your app loads, but it can't really hurt.

There are two types of icons supported currently iconType and iconTemplate.

iconType

A value from a (case insensitive) fixed list of icons which have been provided by Apple and look great:

  • iOS 9.0: Compose, Play, Pause, Add, Location, Search, Share
  • iOS 9.1 added these: Prohibit, Contact, Home, MarkLocation, Favorite, Love, Cloud, Invitation, Confirmation, Mail, Message, Date, Time, CapturePhoto, CaptureVideo, Task, TaskCompleted, Alarm, Bookmark, Shuffle, Audio, Update

Preview icons in Apple's gallery here

iconTemplate

Can be used to provide your own icon. It must be a valid name of an icon template in your Assets catalog.

The type param is the most convenient way to relate the icon to the event you'll receiver when the icon was used to launch your app. So make sure it's unique amongst your icons.

  ThreeDeeTouch.configureQuickActions([
    {
      type: 'checkin', // optional, but can be used in the registerQuickActionListener callback
      title: 'Check in', // mandatory
      subtitle: 'Quickly check in', // optional
      iconType: 'Compose' // optional
    },
    {
      type: 'share',
      title: 'Share',
      subtitle: 'Share like you care',
      iconType: 'Share'
    },
    {
      type: 'search',
      title: 'Search',
      iconType: 'Search'
    },
    {
      title: 'Show favorites',
      iconTemplate: 'HeartTemplate' // from Assets catalog
    }
  ]);

registerQuickActionListener

When a home icon is pressed, your app launches and this JS callback is invoked. I found it worked reliable when you use it like this (you should recognize the type params used previously):

  document.addEventListener('deviceready', function () {
    ThreeDeeTouch.registerQuickActionListener = function (payload) {
      console.log("Icon pressed. Type: " + payload.type + ". Title: " + payload.title + ".");
      if (payload.type == 'checkin') {
        document.location = 'checkin.html';
      } else if (payload.type == 'share') {
        document.location = 'share.html';
      } else {
        // hook up any other icons you may have and do something awesome (e.g. launch the Camera UI, then share the image to Twitter)
        console.log(JSON.stringify(payload));
      }
    }
  }, false);

enableLinkPreview

UIWebView and WKWebView (the webviews powering Cordova apps) don't allow the fancy new link preview feature of iOS9. If you have a 3D Touch enabled device though, you sometimes are allowed to force press a link and a preview pops up (see the screenshot above). If you want to enable this feature, do:

  ThreeDeeTouch.enableLinkPreview();

disableLinkPreview

To disable the link preview feature again, do:

  ThreeDeeTouch.disableLinkPreview();

5. Static Home Icon Actions

The configureQuickActions function above can add dynamic icon actions to your app, but what if you want to have actions immediately after installation from the AppStore, before opening your app?

That's where static icons come in, which need to be configured in your app's .plist file. Let's say you want these actions:

Then add this anywhere in the .plist:

	<key>UIApplicationShortcutItems</key>
	<array>
		<dict>
			<key>UIApplicationShortcutItemIconFile</key>
			<string>Eye</string>
			<key>UIApplicationShortcutItemTitle</key>
			<string>Eye from plist</string>
			<key>UIApplicationShortcutItemSubtitle</key>
			<string>Awesome subtitle</string>
			<key>UIApplicationShortcutItemType</key>
			<string>eyefromplist</string>
		</dict>
		<dict>
			<key>UIApplicationShortcutItemIconType</key>
			<string>UIApplicationShortcutIconTypeCompose</string>
			<key>UIApplicationShortcutItemTitle</key>
			<string>Compose</string>
			<key>UIApplicationShortcutItemType</key>
			<string>compose</string>
		</dict>
	</array>

UIApplicationShortcutItemIconFile

The second action uses the built-in UIApplicationShortcutIconTypeCompose icon (which is the same as the Compose icon you'd get when using the configureQuickActions), but the first one uses a custom icon: Eye. This expects an Eye.png file in your app's bundle. According to Apple's docs this needs to be a single color square 35x35 icon, but that will look pixelated on retina devices, so go ahead and use a 70x70 or 105x105 icon if you please.

In Xcode just drag the icon to the Resources folder. If you're using Telerik Platform you can add it to the App_Resources/iOS folder. That's where the .plist is stored as well.

UIApplicationShortcutItemTitle / UIApplicationShortcutItemSubtitle

You can guess what those do by looking at the screenshot, right?

Note that you can localize these by opening Xcode, and adding a InfoPlist.strings file to the Resources folder. Then mark it as Localizable in the Utilities window and add translations for the appropriate languages. Details here.

If you're using Telerik Platform you can manually upload this plugin and tweak the plugin's contents. See the commented section in plugin.xml about static icon localization.

UIApplicationShortcutItemType

This is the same as the type param of configureQuickActions, so it's what you'll receive in your registerQuickActionListener as payload.type. Just do something cool with that info.

6. Changelog

  • 1.4.1 Replace UIWebView with WKWebView
  • 1.4.0 Update documentation
  • 1.3.9 Refactor replace onHomeIconPressed with registerQuickActionListener, thanks dzNavitski!
  • 1.3.8 Support WKWebViewOnly build settings, thanks #45!
  • 1.3.7 Ionic 4 compat, thanks #43!
  • 1.3.6 Get back the subtitle when a home icon was pressed, thanks #27!
  • 1.3.5 Home icons are now WKWebView compatible. Previously your app would crash. See #12.
  • 1.3.4 Increased the wait time for onHomeIconPressed from 5 to 15 secs. See #4.
  • 1.3.3 Compatibility of 'home icon cold-starts' with Meteor, see #4.
  • 1.3.2 Compatibility with Cordova-iOS 4.
  • 1.3.1 Added timestamp to the response of watchForceTouches.
  • 1.3.0 You can now receive notifications when the user applies a 'Force Touch' on the webview.
  • 1.2.2 Documentation on how to localize the title and subtitle of your static icons.
  • 1.2.1 Documentation on how to add static icons to your app.
  • 1.2.0 iOS 9.1 added a lot of new iconTypes to choose from. Thanks #2!
  • 1.1.0 Found a solid way to deal with timing when to call into onHomeIconPressed. Should always work now, even on coldstart.
  • 1.0.1 Increased the timeouts a bit, so there is a better chance onHomeIconPressed gets called on coldstart. Thanks #1.
  • 1.0.0 Initial release (untagged)

About

Home Screen quick actions and link preview for Cordova on iOS. Fork: WKWebView instead of UIWebView, and quick actions delivered under UIScene on cordova-ios 8.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages