Skip to content
This repository has been archived by the owner on Jun 15, 2023. It is now read-only.

Capacitor 4 plugin that provides full native splash screen functionality

License

Notifications You must be signed in to change notification settings

aparajita/capacitor-splash-screen

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

69 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

capacitor-splash-screen  npm version

❗️This package is no longer maintained. I don’t have the time to work on it, and now use the official splash screen plugin myself.

This Capacitor 4 plugin is a replacement for the official Splash Screen plugin and provides complete control over native splash screens, both automatically at launch and via code.

👉 NOTE: This plugin does not work with Capacitor < 4.

About
Features
Installation
Launch screen configuration
Usage
Custom animation
API

About

iOS and Android already provide launch screens, so why do we need this plugin?

System launch screens by default are removed as soon as the first frame is drawn by the application being launched. In the case of an Capacitor app, that occurs when the layout with the web view is drawn. But there is a finite (and often noticeable) delay between the time when the web view is drawn and the time when the app is fully mounted and drawn within the web view.

To prevent the user from seeing a flash of a blank screen after the system launch screen disappears, this plugin allows you to extend the time the system launch screen is displayed until after the app is fully mounted and drawn. In addition, you can modify the fade out duration or alternatively you can provide your own custom code to animate the launch screen using the hooks provided by this plugin.

Changes from v1.x

Whereas v1.x of this plugin supported multiple formats, this version only supports the formats supported by system launch screens. On iOS, that means storyboards; on Android that means vector drawables (or animated vector drawables on Android 12+). The reason for this is to provide an absolutely seamless transition from the system splash screen to your application.

Starting with Android 12, Android provides a backward-compatible API for configuring and presenting splash screens, and it always shows its own splash screen if you do not customize it. Such being the case, the best approach is to work with the splash screen API to customize the splash screen.

Features

  • Silky smooth, seamless transitions from the system splash screen to your app.
  • Full set of hooks for implementing custom animation. 🚀
  • Full support for localization. 🇺🇸🇧🇷
  • Full support for dark mode. 🌗
  • Full support for Android 12+ splash screens.
  • Splash screen workshop app lets you test splash screens and tweak timing parameters. 👀
  • Complete control over timing when testing splash screens: delay, fade in, duration, fade out. ⏱
  • Specify time units in seconds or milliseconds. ⌛

Installation

pnpm add @aparajita/capacitor-splash-screen @aparajita/capacitor-logger  # npm install, yarn add

Launch screen configuration

There are two types of splash screens: launch and programmatic. Configuration and usage differs depending on the type of splash screen and on the platform. When referring to Capacitor configuration, this document will assume you are using capacitor.config.ts.

There is a fair bit of config you have to get right to support all possible splash screen features (such as dark mode and localization) on both platforms. I have done my best to document it all below, but if you get confused, refer to the workshop app, which implements all of these features.

iOS

  • Create a launch screen storyboard and set it as the launch screen as you normally would.

  • In your app’s AppDelegate.swift, add a call to SplashScreen.initLaunchTime():

import AparajitaCapacitorSplashScreen

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  var window: UIWindow?

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // ==> Add this line
    SplashScreen.initLaunchTime()
    return true
  }

Android

For a launch splash screen in Android, do the following:

  • In Android Studio, create or import a vector drawable to be the splash screen.
  • Edit your app’s res/values/styles.xml file to use this template:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme -->
    <style name="AppTheme" parent="Theme.AppCompat.DayNight.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <!-- This style MUST appear with this name -->
    <style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
        <!-- This is the standard Ionic config -->
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        <item name="android:background">@null</item>

        <!-- OPTIONAL: Set the status and/or navigation bar color -->
        <item name="android:statusBarColor">@android:color/holo_blue_dark</item>
        <item name="android:navigationBarColor">@android:color/holo_green_dark</item>
    </style>

    <!-- This style MUST appear with this name -->
  	<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
        // Set this to the single background color of the splash screen.
        // If you want to change the background color in dark mode, create
        // a night version of the color.
        <item name="windowSplashScreenBackground">@color/systemBackground</item>

        // Set this to a VectorDrawable. On API 31+ it may animated,
        // in which case it will be animated during launch.
        // See https://developer.android.com/guide/topics/ui/splash-screen
        // for info on size limits.
        <item name="windowSplashScreenAnimatedIcon">@drawable/launchscreen</item>

        // Set this if the parent="Theme.SplashScreen.IconBackground".
        <item name="android:windowSplashScreenIconBackgroundColor" tools:targetApi="31">@null</item>

        // If you want a branding image to appear at the bottom, set it here.
        <item name="android:windowSplashScreenBrandingImage" tools:targetApi="31">@null</item>

        // Set the main app theme here (required).
        <item name="postSplashScreenTheme">@style/AppTheme.NoActionBar</item>
    </style>
</resources>
  • Change windowSplashScreenAnimatedIcon to match the name of your splash screen drawable.
  • Change the other parameters according to your preferences.
  • In your app’s AndroidManifest.xml, set the android:theme attribute of the application activity to "@style/AppTheme.NoActionBarLaunch".
  • In your app’s MainActivity.java, add a call to SplashScreen.initLaunchTime():
import com.aparajita.capacitor.splashscreen.SplashScreen;
import com.getcapacitor.BridgeActivity;

public class MainActivity extends BridgeActivity {

  @Override
  public void onCreate(Bundle savedInstanceState) {
    SplashScreen.initLaunchTime();
    super.onCreate(savedInstanceState);
  }
}

All platforms

Because there is an indeterminate amount of time between when the system launch screen appears and your app is loaded and starts running, it is impossible to predict exactly how long a splash screen should remain visible in order to provide a smooth transition to your app’s UI. At the same time, you want to avoid showing the splash screen for too long, because the user may think the app has frozen.

This plugin addresses this problem in two ways:

  • In the native code of your app, you must call initLaunchTime as soon as the app is fully loaded, as shown above. This is potentially several hundred milliseconds before this plugin is loaded, which is a significant amount of time from a user perspective.

  • You may set a showDuration configuration option that specifies the minimum amount of time the launch screen should remain visible. When you call hide or animate, the plugin will wait until the minimum duration has elapsed before hiding or animating the splash screen. This ensures that if the app is loaded quickly, the splash screen won’t just flash and disappear (unless that is what you want!).

const config: CapacitorConfig = {
  plugins: {
    SplashScreen: {
      showDuration: 1 // 1 second
    }
  }
}

Logger configuration

This plugin uses a custom logger. You can set the logger level and whether or not to log to the system console on iOS. See the level and useSyslog options in the configuration section of @aparajita/capacitor-logger.

const config: CapacitorConfig = {
  plugins: {
    SplashScreen: {
      logger: {
        level: 'debug',
        useSyslog: true
      }
    }
  }
}

Localization

This plugin supports localization of the launch screen using standard system localization mechanisms. For an example of localized launch screens on iOS and Android, see the workshop app.

iOS

On iOS, Apple recommends against including text in your launch storyboard, since launch screens are cached by the system. You can, however, configure your app to use separate storyboards for different languages:

  • Create a new storyboard for each language you want to support.
  • Create a text file called InfoPlist.strings (capitalization is important) in the root of your app’s source code directory. This file should contain a single line:
// Set the name to the name of the base language launch storyboard.
"UILaunchStoryboardName" = "LaunchScreen.storyboard";
  • Add the file to your Xcode project, making sure to assign it to the app target.
  • Select the file in the navigator.
  • Open the Inspector panel and click the Localize… button.
  • Select the language this file is for and click Localize.
  • Select the project in the navigator, then the project in the main editor, and click the Info tab.
  • Add whatever languages you want to support to the Localizations list. For each language, check InfoPlist.strings in the dialog that appears.
  • In the InfoPlist.strings file created for each language, change the value of UILaunchStoryboardName to the name of the storyboard for that language.

Android

On Android, you can use the standard Android localization mechanisms to provide different launch screens for different languages. See the Android documentation for more information.

Usage

How you show and hide splash screens differs depending on how they are used.

Launch screens

You do not have to do anything to show launch screens, they are displayed automatically when the app is launched.

Once your app is fully mounted, call hide to fade out the launch screen, or animate to animate the launch screen. If you use animate, your custom animation code is responsible for hiding the splash screen.

Both hide and animate take a delay option. delay may be necessary because:

  1. Capacitor creates the web view.
  2. The web view loads your app.
  3. Your app is mounted in the DOM, but has not yet been rendered.
  4. You call hide() or animate().
  5. The web view renders the content generated by your app.

When you call hide() or animate(), the web view may not have been fully rendered. If you hide too quickly, there will be a flash of the web view’s background color just before it renders. That’s why a small delay may be necessary.

Programmatic splash screens

This plugin also allows you to show splash screens programmatically. This is useful if you want to show a splash screen on app suspend/resume or to test and tweak the timing of a launch screen.

To show a programmatic splash screen, call show, passing SplashScreenShowOptions to configure the splash screen.

If you want to hide or animate the splash screen after some amount of time, you need to call hide or animate manually within setTimeout. For example:

await SplashScreen.show(showOptions)

setTimeout(() => {
  SplashScreen.hide(hideOptions).catch(showErrorAlert)
}, duration)

Showing a splash screen on app suspend/resume

You may wish to show a splash screen every time the app suspends for security purposes, or you may wish to show a splash screen on resume for branding purposes. This plugin provides a convenience function to make that easy to do.

👉 NOTE: Although this technique technically works on Android, I advise against it, as the suspend callback is not reliably called before the app becomes inactive on different Android versions, and you will not get the intended effect.

// Vue code
import { Capacitor } from '@capacitor/core'
import { onMounted, onUnmounted } from 'vue'

let listenerHandle: PluginListenerHandle

onMounted(async () => {
  if (Capacitor.getPlatform() === 'ios') {
    const onSuspend = (): void => {
      // We want to show the splash screen when we suspend
      // so it will already be there on resume.
      SplashScreen.show({
        source: 'LaunchScreen_hi',
        delay: 0,
        showDuration: 100
      }).catch(console.error)
    }

    const onResume = (): void => {
      SplashScreen.hide({
        delay: 300,
        fadeOutDuration: 300
      }).catch(console.error)
    }

    listenerHandle = await SplashScreen.listenToAppState({
      onSuspend,
      onResume
    })
  }
})

onUnmounted(async () => {
  if (listenerHandle) {
    await listenerHandle.remove()
  }
})

The trick we are playing here is to show the splash screen as the app is suspended, so that when the app is resumed, the splash screen is already visible, and then we just need to hide it. 😎

Custom animation

This plugin provides hooks for custom animation on both iOS and Android. The workshop app contains a complete example of custom animation on both platforms, which you can use as a template for your animation.

👉 NOTE: Your custom animation code is responsible for hiding the splash screen.

Once you have implemented your custom animation code, performing the animation is just a matter of calling animate() instead of hide().

Events

There are two possible animation events sent to your animation method. Of those, you are only required to respond to the animateLaunch event.

  • animateLaunch — Sent when the animate() method is called for a launch screen, after any delay specified in the animate options.

  • animate — Sent when the animate() method is called after calling show(), and after any delay specified in the animate options.

Event handler

In order to receive animation events (and thus perform animation), you need to create an animation handler method in your app’s native code. On iOS, a minimal event handler will look like this:

import AparajitaCapacitorSplashScreen

private let kDefaultDuration: Double = 0.7 // seconds

extension AppDelegate {
  @objc func onSplashScreenEvent(_ event: String, _ params: [AnyHashable: Any]) {
    animate(withParams: params)
  }

  private func animate(withParams params: [AnyHashable: Any]) {
    guard let view = params["splashView"] as? UIView,
          let options = params["options"] as? [AnyHashable: Any],
          let callbacks = params["callbacks"] as? SplashScreen.AnimationCallbacks else {
      return
    }

    let animationDuration = SplashScreen.toSeconds(Config.getDouble("animationDuration", inOptions: options) ?? kDefaultDuration)

    UIView.animateKeyframes(
      withDuration: animationDuration,
      delay: 0,
      options: [],
      animations: {
        self.performAnimation(forView: view)
      },
      completion: { _ in
        self.finishAnimation(forView: view, done: done)
      }
    )
  }

  private func performAnimation(forView view: UIView) {
    // Your animation code goes here
  }

  private func finishAnimation(
    forView view: UIView,
    callbacks: SplashScreen.AnimationCallbacks)
  {
    // Your cleanup code goes here

    // Make sure to call the done callback so control returns to the app
    callbacks.done()
  }
}

On Android, a minimal event handler will look like this:

public class SplashScreenAnimator {

  static final long DEFAULT_ANIMATION_DURATION = 500;

  public void onSplashScreenEvent(
    SplashScreen.HookEventType event,
    HashMap<String, Object> params
  ) {
    animate(params);
  }

  private void animate(HashMap<String, Object> params) {
    AnimationCallbacks callbacks = null;
    View splashView = null;
    View iconView = null;
    long duration = DEFAULT_ANIMATION_DURATION;

    try {
      splashView = (View) params.get("splashView");
      iconView = (View) params.get("iconView");
      callbacks = (AnimationCallbacks) params.get("callbacks");
      JSObject options = (JSObject) params.get("options");
      Config config = (Config) params.get("config");

      if (options != null && config != null) {
        duration =
          SplashScreen.toMilliseconds(
            config.getDoubleOption(
              "animationDuration",
              options,
              DEFAULT_ANIMATION_DURATION
            )
          );
      }
    } catch (Exception ex) {
      // Handle it below
    }

    if (splashView == null || iconView == null) {
      if (callbacks != null) {
        callbacks.error(
          "Null splash or icon view",
          SplashScreen.ErrorType.NO_SPLASH
        );
      }

      return;
    }
    // Your animation code goes here. Be sure to call
    // callbacks.done() when the animation finishes or
    // is cancelled.
  }
}

To dispatch to the event handler on Android, your app’s MainActivity must implement onSplashScreenEvent():

public class MainActivity extends BridgeActivity {

  private final SplashScreenAnimator animator = new SplashScreenAnimator();

  @Override
  public void onCreate(Bundle savedInstanceState) {
    SplashScreen.initLaunchTime();
    super.onCreate(savedInstanceState);
  }

  public void onSplashScreenEvent(
    SplashScreen.HookEventType event,
    HashMap<String, Object> params
  ) {
    animator.onSplashScreenEvent(event, params);
  }
}

👉 IMPORTANT❗️ The onSplashScreenEvent method name and signature must be exactly as displayed above.

Event parameters

Each event receives parameters from the plugin with context that you may need in performing your animation.

Param Type (iOS / Android) Description
source String The source option passed to show(), defaults to "*"
splashView UIView
android.view.View
iOS: The storyboard’s main view controller view
Android: A full screen FrameLayout which contains the iconView.
iconView [unused]
View
The splash icon
plugin CAPPlugin
com.getcapacitor.Plugin
The SplashScreen instance which called the event handler
options [AnyHashable: Any]
com.getcapacitor.JSObject
Options passed to the animate() plugin method
callbacks SplashScreen.AnimationCallbacks
AnimationCallbacks
Contains done and error functions, one of which you must call when the animation is completely finished

As noted above, you must call either the done or error callback when the animation is completely done, otherwise control will not be returned to the JavaScript runtime. In most cases you will do this in the animation completion function.

Android-only event parameters

The following parameters are only passed on Android:

Param Type Description
iconView View The splash icon
config Config instance Use to get values from options
activity AppCompatActivity The current activity

Android-specific animation

When a splash screen is shown on Android, the status bar and navigation bar colors are smoothly animated from their current color to the background color of the splash screen, which effectively hides them. When your splash screen animation is fading out, you need to show the status and navigation bars (if they were visible in your app).

The callbacks parameter on Android contains two extra methods:

void showStatusBar(long delay, long duration, TimeInterpolator interpolator);

void showNavigationBar(
  long delay,
  long duration,
  TimeInterpolator interpolator
);

Calling these methods will smoothly transition the relevant background color from the splash screen background color to the bar’s previous color. If interpolator is null, it defaults to LinearInterpolator.

For an example of how to use these callbacks, see SplashScreenAnimator.java in the workshop app.

Why no immersive mode?

There are a few reasons why this plugin does not support immersive mode:

  • The new system splash screens in Android 12+ do not use immersive mode, they set the status and navigation bar backgrounds to the splash screen background. This plugin endeavors to be as consistent with the system splash screen as possible.
  • Immersive mode slides the status and navigation bars offscreen, but if the user taps the screen before the splash screen is hidden they will reappear.
  • On some versions of Android, when immersive mode is entered a system dialog appears advising the user to tap the screen to show the status and navigation bars. This is not something you want to happen during your app launch.

Accessing animation options

This plugin provides a custom Config class that allows you to use different option values for each platform when passing options to show, hide and animate. For example, you may want a different animationDuration on each platform:

SplashScreen.animate({
  ios: {
    animationDuration: 500
  },
  android: {
    animationDuration: 700
  }
})

In your custom animation code, if you want to get values from the options passed to animate, use the custom config class to retrieve those values, as shown in the event handler examples above. In brief, to retrieve a value of <type> (String, Double, etc.) from the options passed in the animation params, do the following on iOS:

let value = Config.get<type>(<key>, inOptions: options) ?? <default>

and this in Android:

<type> value = config.get<type>Option(<key>, options, <default>);

So to retrieve the string value "foo" with a default of "bar", you would do this on iOS:

let foo = Config.getString("foo", inOptions: options) ?? "bar"

and this in Android:

String foo = config.getStringOption("foo", options, "bar");

API


show(...)

show(options?: SplashScreenShowOptions) => Promise<void>

Show the splash screen. Does not apply to launch screens.

NOTE: On Android, the size of the splash screen icon may not exactly match the actual launch screen icon size. I was not able to determine the algorithm used by Android to determine the size of the splash screen icon.

Param Type
options SplashScreenShowOptions

hide(...)

hide(options?: SplashScreenHideOptions) => Promise<void>

Hide the splash screen. You must call hide() or animate() to remove a launch screen, typically after your app is fully mounted.

Param Type
options SplashScreenHideOptions

animate(...)

animate(options?: SplashScreenAnimateOptions) => Promise<void>

Animate the splash screen. You must call hide() or animate() to remove a launch screen, typically after your app is fully mounted.

Param Type
options SplashScreenAnimateOptions

listenToAppState(...)

listenToAppState(options?: SplashScreenAppStateListeners) => Promise<PluginListenerHandle>

Listen to changes in the app state and execute the relevant code. This is a convenience to allow you to easily show a splash when your app resumes. Be sure to save the result of this call somewhere. When you no longer need to listen to the app state (e.g. when a component unmounts), be sure to call the remove() method on the returned PluginListenerHandle.

NOTE: This cannot be reliably used on Android, as the app may be paused before the listener is called.

Param Type
options SplashScreenAppStateListeners

Returns: Promise<PluginListenerHandle>


Interfaces

SplashScreenShowOptions

Show options may be specified separately for iOS and Android.

Prop Type
ios SplashScreenShowOpts
android SplashScreenShowOpts

SplashScreenShowOpts

Prop Type Description
source string The source of the splash screen. Does not apply to launch screens. On iOS, it may be a storyboard with the given name. On Android, it may be any vector drawable with the given name. If the name is "*", on iOS the configured LaunchScreen storyboard in the app's project will be used, on Android the value of the windowSplashScreenAnimatedIcon item in the launch theme will be used.
delay SplashScreenDuration How long to delay before showing the splash screen. Does not apply to launch screens.
fadeInDuration SplashScreenDuration How long to fade in. Does not apply to launch screens.
showDuration SplashScreenDuration Launch: The minimum time to show the splash screen. If animate() or hide() is called before this time elapses, the screen will remain until the duration is finished.

show(): How long to show the splash screen after fade in and before fade out.

SplashScreenHideOptions

Hide options may be specified separately for iOS and Android.

Prop Type
ios SplashScreenHideOpts
android SplashScreenHideOpts

SplashScreenHideOpts

Prop Type Description
delay SplashScreenDuration How long to delay before hiding.
fadeOutDuration SplashScreenDuration How long to fade out.

SplashScreenAnimateOptions

Animate options may be specified separately for iOS and Android.

Prop Type
ios SplashScreenAnimateOpts
android SplashScreenAnimateOpts

SplashScreenAnimateOpts

Prop Type Description
delay SplashScreenDuration How long to delay before starting the animation.
animationDuration SplashScreenDuration How long animation should take when calling animate.

PluginListenerHandle

Method Signature
remove () => Promise<void>

SplashScreenAppStateListeners

Type Aliases

SplashScreenDuration

In options objects, when you specify a duration, it can either be in seconds or milliseconds. Any value >= 10 will be considered milliseconds, any value < 10 will be considered seconds.

number