DEV Community

Matthias
Matthias

Posted on

Create your own iOS widget with JavaScript

What we will build

We will create our own iOS widget with pure JavaScript. To achieve this, we will use a great app called Scriptable. With Scriptable, you can create JavaScript scripts and run them on your iOS device. You can use these scripts to automate different things on your device and also display the script in an iOS home screen widget.
The main goal of this tutorial is to provide a starting guide for creating Scriptable widgets. Therefore we will build a simple widget to learn the basics and some useful tricks for building Scriptable widgets. The created widget will display the next scheduled launch date of SpaceX, as shown in the image below.
iOS home screen with widget

Prerequisites

Some basic knowledge of JavaScript will be helpful but is not necessary. As previously mentioned, we use the Scriptable app for creating our widget, so you have to install it on your device.
For easier development and testing on macOS, I recommend the Scriptable desktop app (beta).
An alternative is syncing your scripts using iCloud and edit the Scripts inside the iCloud folder on your desktop, which should also work on other operating systems. This allows you to edit the script in your favorite text editor and test it on your iOS device.

Creating the widget

Now we are all set up to start creating our widget.
First of all, create a new empty script and add the following snippet.

async function createWidget() {
  // Create new empty ListWidget instance
  let listwidget = new ListWidget();

  // Return the created widget
  return listwidget;
}

let widget = await createWidget();

// Check where the script is running
if (config.runsInWidget) {
  // Runs inside a widget so add it to the homescreen widget
  Script.setWidget(widget);
} else {
  // Show the medium widget inside the app
  widget.presentMedium();
}
Script.complete();
Enter fullscreen mode Exit fullscreen mode

This snippet adds a new function for creating and returning a new widget instance. In the next step, the widget will be created and set if the script is executed inside a home screen widget. If the script runs inside the Scriptable app, it will display a preview of the widget.
The preview is handy because the widgets don't refresh immediately. In contrast, the preview is always up to date when you run the script.
If you run the script inside the app, you should now see an empty white square. πŸŽ‰πŸŽ‰

Adding Content

Now that we can run a basic widget, let's add some content. The widget layout is based on stacks that hold the different displayed elements. The stacks can be nested into each other. By default, a ListWidget stacks its content vertically, which can be changed if you want it.

Tip #1
When you want to debug more complex layouts, add a colored border to your elements so you can easily detect its size and position.

First we will set the background color and after that we add some heading to our widget and give it some styling. Therefore paste the following snippet inside the createWidget() method after the new ListWidget();.

  // Set new background color
  listwidget.backgroundColor = new Color("#000000");

  // Add widget heading
  let heading = listwidget.addText("πŸš€NextπŸš€");
  heading.centerAlignText();
  heading.font = Font.lightSystemFont(25);
  heading.textColor = new Color("#ffffff");

  // Spacer between heading and launch date
  listwidget.addSpacer(15);
Enter fullscreen mode Exit fullscreen mode

To set the background color, we set the backgroundColor property of the created list widget instance. We assign a new color with the hex value #000000, which will give us a black background.
After that, we add a new text to our widget and save it in the heading variable. Using the variable, we can now access the added text and give it some styling. In our case, we will align the text center, change the default font and give it a white text color. If you are interested in the different options for the text and the other supported fonts, please visit the WidgetText and the Font documentation of scriptable.
Lastly, we add some spacing between our created heading and the next element, which will be our launch date.

Getting the data

To display the next scheduled launch date, we have to fetch our data from the API. Therefore we will use the following API Documentation spacexdata.com API.

⚠️ Sidenote ⚠️
If you use an API that requires an API key or secret, never share your script with the key inside. Every other person could use the API with your access if the key gets public, e.g. on GitHub. Always store the key in a variable at the beginning of your script to easily find and remove it before sharing. Even better, don't hard code the key and instead pass it as an argument to your script.

To fetch the data from the api we will add the following two new functions to our script.

async function getNextLaunch() {
  // Query url
  const url = "https://api.spacexdata.com/v4/launches/next";

  // Initialize new request
  const request = new Request(url);

  // Execute the request and parse the response as json
  const response = await request.loadJSON();

  // Return the returned launch data
  return response;
}

function getLaunchDateTime(launchData) {
  // Parse launch date to new date object
  const launchDateTime = new Date(launchData.date_utc);
  return launchDateTime;
}
Enter fullscreen mode Exit fullscreen mode

The first function sends a request to the specified URL and parses the response as JSON, which will then be returned. The second function is just a little helper that will extract the date string from the provided dataset and return it as a date object.
Now we can call the previously defined functions inside createWidtet() to fetch the data from the api and get the launch date. Therefore simply add the following lines inside the createWidtet() function after the listwidget.addSpacer()

  // Fetch next launch date
  let launch = await getNextLaunch();
  let launchDateTime = getLaunchDateTime(launch);
Enter fullscreen mode Exit fullscreen mode

Displaying the fetched data

Now that we have fetched our API data, we need to display it inside our widget. To achieve this, we create two new functions that will add our text to the widget and apply some basic formatting to it.

function addDateText(stack, text) {
  let dateText = stack.addText(text);
  dateText.centerAlignText();
  dateText.font = Font.semiboldSystemFont(20);
  dateText.textColor = new Color("#ffffff");
}

function displayLaunchDateTime(stack, launchDateTime, precision) {
  // Check if next launch date is precise enough and display different details based on the precision
  if (precision == "hour") {

    // Add launch date
    const dateOptions = { year: "numeric", month: "2-digit", day: "2-digit" };
    let datestring = launchDateTime.toLocaleDateString(undefined, dateOptions);
    addDateText(stack, datestring);

    // Add launch time
    const timeOptions = { hour: "numeric", minute: "numeric" };
    let timestring = launchDateTime.toLocaleTimeString(undefined, timeOptions);
    addDateText(stack, timestring);
  } else if (precision == "day") {

    // Add launch date
    const dateOptions = { year: "numeric", month: "2-digit", day: "2-digit" };
    let datestring = launchDateTime.toLocaleDateString(undefined, dateOptions);
    addDateText(stack, datestring);
  } else {
    addDateText(stack, "No day for next launch given");
  }
}
Enter fullscreen mode Exit fullscreen mode

The addDateText() function gets an instance to which the function should add the text and a string that holds the text which should be displayed. After that, the function adds the given text to the given stack and applies some styling to the element.

Tip #2
If you want to display some text with a variable length, you can add a minimumScaleFactor() between 0 and 1 to the text. This specifies the amount the text will scale down to fit inside the widget.

The displayLaunchDateTime() function is a bit more complex. It gets the instance to which the text should be added, the DateTime instance which should be displayed, and a precision parameter. The precision is sent with the API response of the next launch. It determines how precisely the launch time is known, which we will use to decide if we only display the day or also the time of the launch.
Inside the displayLaunchTime() function, we check the given precision. Based on that, we apply the correct format to our Date instance and add it to the stack by calling addDateText(). If the next launch date is not known precisely enough, we display a message that the next launch day isn't known yet.

Now inside our createWidget() function we can call the displayLaunchDateTime() function and pass our listwidget instance, the created date and the precision from the API response to display the data. The final createWidget function is shown below.

async function createWidget() {
  // Create new empty ListWidget instance
  let listwidget = new ListWidget();

  // Set new background color
  listwidget.backgroundColor = new Color("#000000");

  // Add widget heading
  let heading = listwidget.addText("πŸš€NextπŸš€");
  heading.centerAlignText();
  heading.font = Font.lightSystemFont(25);
  heading.textColor = new Color("#ffffff");

  // Spacer between heading and launch date
  listwidget.addSpacer(15);

  // Fetch next launch date
  let launch = await getNextLaunch();
  let launchDateTime = getLaunchDateTime(launch);

  // Add the launch time to the widget
  displayLaunchDateTime(listwidget, launchDateTime, launch.date_precision);

  // Return the created widget
  return listwidget;
}
Enter fullscreen mode Exit fullscreen mode

Add it to your home screen

To display the widget on your home screen, you need to create the script inside the Scriptable app (you can find the final script in the Gist below). After that, add a new small Scriptable widget to your home screen, which should give you an empty widget. Now you can long-press the widget, edit the widget and select your created script in the widget configuration.
That's it you should now see your created widget on your screen. πŸš€πŸš€

I hope you enjoyed this little tutorial. Feel free to write to me if you encounter any issues or want to suggest some additional topics about which I should make a tutorial.

Happy coding πŸ‘¨πŸΌβ€πŸ’»

Top comments (2)

Collapse
 
michael profile image
Michael Lee πŸ•

Glad to see more articles around Scriptable :) Thanks for sharing @matthri

Collapse
 
cdthomp1 profile image
Cameron Thompson

Awesome! Going to try this one!