Building a React-Native Today Widget in iOS

I am writing this tutorial after struggling days with “Unable to load” errors that were caused by memory limit of a Today widget that is 16 MB.

A lot of credit goes to David Skaarup His story really helped me getting started with this project. I also want to credit matejkriz for his examples.

1. Add Today Extension Target to your React Native App

Go to File > New > Target… and select Today Extension. and give it a name, I have called it AITodayWidget.

You can see the new widget in your project navigator and in your targets.(you will see only TodayViewController.h TodayViewController.m and plist.info and MainInterface.storyboard .

2. Modify the Today Widget Info.plist

First create App Transport Security Settingsand add Allow Arbitrary Loads
Then set TodayViewController as the NSExtensionPrincipalClass under NSExtension and remove NSExtensionMainStoryboard.

delete theMainInterface.storyboard frin “Copy Bundle Resources” phase.

3. Add Linker Flags

In the Build Settings of the Today Widget add -ObjC and -lc++ linker flags.

4. Create index.widget.js in the same location where index.js.

5. Add New Run Script Phase

Under Build Phases in the Today Widget add a new Run Script Phase.

Name it Bundle React Native code and images (this is the same as in your main app target). Then add the following, which is also defined in the main app target:

export NODE_BINARY=node
../node_modules/react-native/packager/react-native-xcode.sh index.widget.js

6. Add React Native Libraries

Now add all the React Native libraries to the widget target’s Build Phases in Link Binary with Libraries.

7. Modify the TodayViewController.m like this

//
//  TodayViewController.m
//  TodayWidgetExtension
//
//  Created by Matej Kriz on 21.05.17.
//  Copyright © 2017 Facebook. All rights reserved.
//
#import "TodayViewController.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <NotificationCenter/NotificationCenter.h>
#import "DisplayMode.h"
#import "Linking.h"
DisplayMode* displayMode;
Linking* linking;
@interface TodayViewController () <NCWidgetProviding>
@end
@implementation TodayViewController
- (void)loadView {
NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.widget" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"AITodayWidget"
initialProperties:nil
launchOptions:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1 green:1 blue:1 alpha:0];
self.view = rootView;
displayMode = [[DisplayMode alloc]initWithContext:self.extensionContext];
linking = [[Linking alloc]initWithContext:self.extensionContext];
}
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize{
if (activeDisplayMode == NCWidgetDisplayModeCompact){
self.preferredContentSize = maxSize;
}
else if (activeDisplayMode == NCWidgetDisplayModeExpanded){
self.preferredContentSize = CGSizeMake(maxSize.width, [DisplayMode getMaxHeight]);
}
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
// This is required in order for there to be space for
// the React Native stuff to show up
[self setPreferredContentSize:CGSizeMake(0, 200)];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler {
// Perform any setup necessary in order to update the view.
// If an error is encountered, use NCUpdateResultFailed
// If there's no update required, use NCUpdateResultNoData
// If there's an update, use NCUpdateResultNewData
completionHandler(NCUpdateResultNewData);
}
@end

8. Add DisplayMode.h DisplayMode.m Linking.h Linking.m from (right button to the target and then “Add files to…”)

https://github.com/matejkriz/react-native-today-widget/tree/f99eaabc3c2559e5f8c07bd27589240bfed403b8/ios/TodayWidgetExtension

9.And now for the most important part:

create index.widget.js and register your app.

import React from 'react'
import {AppRegistry, Platform, Text, View} from 'react-native'
const TodayWidget = () => (
<View style={{ flex: 1, justifyContent: 'center' }}>
<Text>
Hello Today Widget!
</Text>
</View>
)
if (Platform.OS === 'ios') {
AppRegistry.registerComponent('AITodayWidget', () => TodayWidget)
}

I have seen many examples where it is written to register the widget in index.js.

This is wrong, because of the memory limitation, if your app is big the widget won’t work.

so creating a different file index.widget.js and supplying it in the Bundle React Native code and image will bundle only the relevant files.

I hope this tutorial was helpful, Thank you for reading.

Like what you read? Give Maxim Toyberman a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.