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.
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.
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
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.
Open the Xcode project at platforms/ios/sampleiostodaywidget.xcodeproj in Xcode.
Select the sampleiostodaywidget project file from the left pane, then the sampleiostodaywidget target, and finally the General section.
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.
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.
Select the sampleiostodaywidget project and add from the top menu, choose Editor >> Add Target.
From the dialog that will appear, choose Application Extension >> Today Extension
In the dialog that will appear select TodayWidget as a product name:
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.
From the sampleiostodaywidget target's Build Phases copy Generate Metadata to the TodayWidget target's Build Phases. Here is how to do that:
In order to add a reference to the iOS Chart component that stays behind the Chart wrapper of UI for NativeScript, do the following:
Navigate to the sampleiostodaywidget target's General tab.
Click the “+” button of Linked Frameworks and Libraries
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.framework .
Add the TelerikUI.framework folder to Framework Search Paths in Build Settings".
"$(SRCROOT)/../../node_modules/nativescript-telerik-ui/platforms/ios/"
Add the following flags to Build Settings -> Other Linker Flags:
-sectcreate __DATA __TNSMetadata "$(CONFIGURATION_BUILD_DIR)/metadata-$(CURRENT_ARCH).bin" $(inherited)
From the left navigation pane, navigate to TodayWidget/SupportingFiles and delete the files:
TodayViewController.h
TodayViewController.m
MainInterface.storyboard
We will implement them in pure JavaScript in a minute.
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:
Right-click the TodayWidget >> SupportingFiles and add a new Objective-C file naming it main.m.
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"];
}
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).
You now have to write some JavaScript and implement a column chart.
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]
});
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.
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!