Back to Blog Home
← all posts

Making a Today widget in iOS with NativeScript and UI for NativeScript

September 10, 2015 — by Nikolay Diyanov

Since iOS 8, Apple has made various aspects of the iOS available for 3rd-party developers to plug into. Those include plugging your own custom keyboard for your apps, or creating a widget for the iOS notification center.

Today, we will go through the steps needed to create a Today widget in the iOS notification center, but since we will be using the NativeScript framework to do so, it will be all in JavaScript, rather than in Swift/Objective-C.

“But what is NativeScript?”, you would ask. For those that do not know:

NativeScript is a framework for building cross-platform native mobile applications. You can write one source code and to reuse it on iOS and Android. But in order to be flexible and to be able to fully use the power of the native platform sometimes you need to add platform specific code to achieve a really shiny mobile application. In the light of our promise to have access to the entire native API and to support platform-specific code in NativeScript we want to show you how you can implement rich iOS specific notifications in your NativeScript app.

Our widget will use the native Chart component that ships with UI for NativeScript. The component will display static data (for simplicity) from the TIOBE index. Note that we will use the NativeScript CLI for this project.

001-Today-Widget-Simulator

At the end of this article you can see the running application.

> Note: Creating an iOS Today widget in NativeScript currently involves working in Xcode, but we're thinking through ways of allowing these types of extensions without the Xcode requirement. If you're interested, please vote for this feature and let us know your use cases in this issue on GitHub.

Assuming that you have installed NativeScript CLI, let’s dive in into the concrete steps of creating our project today.

Setting up the project

To create a project and add the necessary iOS platform to it, execute the following commands in the terminal:

tns create sample-ios-todaywidget
cd sample-ios-todaywidget
tns platform add ios
tns prepare ios

Installing UI for NativeScript

Before anything else, let’s make sure that we have the Chart library in place. Because of the considerations above, we will work directly with the native Chart framework rather than with the cross-platform JS wrapper. Still, because the beauty of NativeScript, we will be able to work with that library using JavaScript only.

UI for NativeScript is available as an npm package, so make sure you are at the main directory of your application (sample-ios-todaywidget) and execute the command below to install the product:

npm i nativescript-telerik-ui --save

This will install UI for NativeScript and will place the iOS library of interest (namely, the TelerikUI.framework) that contains the Chart component at sample-ios-todaywidget/node_modules/nativescript-telerik-ui/platforms/iOS/TelerikUI.framework and also at sample-ios-todaywidget/lib/iOS/TelerikUI.framework .

Note that we are using the flag --save. Thanks to it, npm records this dependency in your app's package.json. If you open your package.json, you should now see nativescript-telerik-ui in your app's "dependencies" array.

> Tip: By saving your app's npm dependencies in your package.json file, you can always regenerate your node_modules folder by running npm install. Because of this, it's a common practice to exclude the node_modules folder from source control. The sample-ios-todaywidget application uses git for source control, and as such includes node_modules/ in its .gitignore.

> Note: UI for NativeScript is a commercial product and the Chart component that we take from npm is a Trial version. The non-Trial version will be available for sale soon. The SideDrawer component included in UI for NativeScript is completely free though.

Setting up the main application target

Open the Xcode project at platforms/ios/sampleiostodaywidget.xcodeproj in Xcode.  

  1. Select the sampleiostodaywidget project file from the left pane, then the sampleiostodaywidget target, and finally the General section.002-NativeScript-Add

  2. In the General section, scroll down to the Embedded Binaries section. Click the “+” button and navigate to the platforms/ios/NativeScript.framework to add a reference to it. The NativeScript.framework provides the NativeScript runtime. 003-NativeScript-Add

    004-NativeScript-Add

The NativeScript.framework is a shared framework that will be used both by the app and the extension. At the end, the app with the widget will be smaller in size (about a megabyte), because of the single library shared between the two targets.

Setting up the Today extension Target

  1. Select the sampleiostodaywidget project and add from the top menu, choose Editor >> Add Target.
    006-Today-Target-Add

  2. From the dialog that will appear, choose Application Extension >> Today Extension
    007-Today-Extension

  3. In the dialog that will appear select TodayWidget as a product name:
    008-Today-Widget-Name

  4. Navigate to the TodayWidget target’s General section and in the Link Binary With Libraries add a reference to the the platforms/ios/NativeScript.framework.

  5. From the sampleiostodaywidget target's Build Phases copy Generate Metadata to the TodayWidget target's Build Phases. Here is how to do that:

  6. In order to add a reference to the iOS Chart component that stays behind the Chart wrapper of UI for NativeScript, do the following:

    1. Navigate to the sampleiostodaywidget target's General tab.

    2. Click the “+” button of Linked Frameworks and Libraries

    3. Select the TelerikUI.framework from sample-ios-todaywidget/node_modules/nativescript-telerik-ui/platforms/TelerikUI.framework or from the directory that was created to contain the native library of UI for NativeScript: sample-ios-todaywidget/lib/iOS/TelerikUI.framework011-TelerikUI

  7. Add the TelerikUI.framework folder to Framework Search Paths in Build Settings".screen_shot_2018-08-15_at_4.54.24_pm

    "$(SRCROOT)/../../node_modules/nativescript-telerik-ui/platforms/ios/"
  8. Add the following flags to Build Settings -> Other Linker Flags:other-linker-flags

    -sectcreate __DATA __TNSMetadata "$(CONFIGURATION_BUILD_DIR)/metadata-$(CURRENT_ARCH).bin" $(inherited)
  9. From the left navigation pane, navigate to TodayWidget/SupportingFiles and delete the files:

    TodayViewController.h
    TodayViewController.m
    MainInterface.storyboard

    012-Delete-Files

     
    We will implement them in pure JavaScript in a minute.

  10. In the TodayWidget/Supporting Files/Info.plist, replace the NSExtensionMainStoryboard property set to MainInterface, with NSExtensionPrincipalClass property set to TodayViewController. The result should look like:
    013-Info

  11. Right-click the TodayWidget >> SupportingFiles and add a new Objective-C file naming it main.m.
    014-Add-New-File-main

    015-Objective-C

    016-main.m

  12. Insert the following bootstrapping code there:

    #import <NativeScript/NativeScript.h>

    static TNSRuntime* runtime;

    __attribute__((constructor))
    void initialize() {
     
      extern char startOfMetadataSection __asm("section$start$__DATA$__TNSMetadata");
      [TNSRuntime initializeMetadata:&startOfMetadataSection];
     
      runtime = [[TNSRuntime alloc] initWithApplicationPath:[NSBundle mainBundle].bundlePath];
      TNSRuntimeInspector.logsToSystemConsole = YES;
      [runtime executeModule:@"./tiobe-widget"];
    }

    You can see that we are trying to load a tiobe-widget module. We’ll get to the implementation of that module in a second.

     

  13. Last but not least for this set of steps, select sampleiostodaywidget/app and from the right-hand pane file inspector where the Target Membership section resides, check the TodayWidget checkbox. This will copy the JavaScript files from the main bundle (sampleiostodaywidget) to the extension bundle (TodayWidget). 017-Target-Membership

You now have to write some JavaScript and implement a column chart.

Adding the JavaScript for the TodayWidget

Create a new file in your app directory and name it tiobe-widget.ios.js. During compilation for iOS only this file will be moved to platforms/ios/sampleiostodaywidget/app, with the 'ios' suffix stripped.

Basically, we are first creating a column chart filling it with some static data, and setting the appropriate axis styles. At the bottom, we define the principal class we set in the TodayWidget’s Info.plist:

var chart;

UIViewController.extend({
    viewDidLoad: function() {
        this.super.viewDidLoad();

        //Setting up the chart
        if (!chart) {
            chart = TKChart.alloc().initWithFrame(CGRectMake(0, 0, this.view.frame.size.width, 150));

            chart.plotView.backgroundColor = UIColor.clearColor;
            chart.backgroundColor = UIColor.clearColor;

            chart.gridStyle.horizontalFill = null;
            chart.gridStyle.horizontalAlternateFill = null;
        }
        this.view.addSubview(chart);

        this.preferredContentSize = CGSizeMake(0, 150);
    },
    //Removing any redundant margins
    widgetMarginInsetsForProposedMarginInsets: function(defaultMarginInsets) {
        return UIEdgeInsetsZero;
    },
    //Refilling chart with data when an update is needed
    widgetPerformUpdateWithCompletionHandler: function(completionHandler) {
        var dataPoints = [TKChartDataPoint.alloc().initWithXY("C++", 6.782),
                          TKChartDataPoint.alloc().initWithXY("JS", 2.342),
                          TKChartDataPoint.alloc().initWithXY("C#", 4.909),
                          TKChartDataPoint.alloc().initWithXY("Java", 19.565),
                          TKChartDataPoint.alloc().initWithXY("Python", 3.664),
                          TKChartDataPoint.alloc().initWithXY("PHP", 2.530),
                          TKChartDataPoint.alloc().initWithXY("C", 15.621)];

        var tiobeColumnSeries = TKChartColumnSeries.alloc().initWithItems(dataPoints);
        chart.addSeries(tiobeColumnSeries);

        var xAxis = chart.xAxis;
        xAxis.minorTickIntervalUnit = TKChartDateTimeAxisIntervalUnitDays;
        xAxis.style.labelStyle.textColor = UIColor.whiteColor;

        var yAxis = chart.yAxis;
        yAxis.majorTickInterval = 5;

        yAxis.style.labelStyle.textColor = UIColor.whiteColor;
        yAxis.style.labelStyle.firstLabelTextAlignment = TKChartAxisLabelAlignmentLeft | TKChartAxisLabelAlignmentTop;
        yAxis.style.labelStyle.firstLabelTextOffset = { horizontal: 4, vertical: 0 };

        chart.update();

        completionHandler(NCUpdateResultNewData);
    }
}, {
    name: "TodayViewController",
    protocols: [NCWidgetProviding]
});

Running the Extension

This is all for the implementation. Now run the following command at the root of your NativeScript project:

tns prepare ios

Open the Xcode project in platforms/ios. Select the TodayWidget target at the top left of the Xcode window and an appropriate simulator. Click the 'build and run' button. You will be asked which app to run, select Today, and click Run.

018-Run

Below you can see how our widget will look. Draw the Notification Center from the top, then click the Edit button and add our brand new TodayWidget to the list of the available widgets.
  


This is how you can create and add a widget with NativeScript and UI for NativeScript, all in JavaScript. You can find the complete project at GitHub.

Happy coding!