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.
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();
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);
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;
}
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);
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");
}
}
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 aminimumScaleFactor()
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;
}
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)
Glad to see more articles around Scriptable :) Thanks for sharing @matthri
Awesome! Going to try this one!