Advanced Widgets- Network Calls, Intent Configuration & Deeplinking
If you are coming from my previous article, Welcome back! If you are just starting with the widgets, I would recommend you to go through my previous article first. In this article we are going to modify our already created fake AdvancedWidget from the previous article and make it actually an advanced one! ;)
Network Calls:
In previous article we just displayed static data on our widget. But in real life you might need to fetch data from an api. Making network calls for widgets are as easy as you would expect. If you already have a web service you can simply expose it to your widget extension by selecting your widget target in Target Membership.
If you don’t have one, go ahead and create one for your extension. Don’t forget to select the your widget extension under Targets.
Create a model depicting your actual api request response. For the demo purpose I am making a simple model User with two properties Name and Profession.
Also we are going to mock api response instead of making actual api request. So I am creating demo response. Note that you shouldn’t use try!, catch your errors properly using do-catch.
In your WebService class write the method that communicates with the network. You can replace the commented code with your actual api request in the following code:
So, we are done with the our basic network set up. Now, coming back to our AdvancedProvider,. getTimeline is the method that provides entries to your widget that will be displayed on your widget view, feeling Deja Vu?? Go ahead and call your fetchUserData method you just created. Create an entry with your user data received from the method and pass it to the completion handler. Also, you should handle your errors gracefully. I am going to show static data in case my api call fails. Caching is also a way to go, You decide! Your getTimeline: method looks like this now:
Now run your application, you would be able to see the new text! Voila!!
Intent Configuration:
In previous article we worked with Static Intent configuration. Let’s create a dynamic intent. Choose File > New > File > SiriKit Intent Definition File.
Click next, provide name UserCategories. Check both the Targets.
Open your UserCategories definition file. Click on + at the bottom, create New Intent and provide name UserCategories.
For this Intent we need to fill out few fields here. Select Category as View. Provide meaningful Description. Select checkbox Intent is available for widgets.
Now we will create an Enum to supply data to our intent. Click on + icon, create New Enum. Provide name Users. You can change Display Name as you like. Add the cases you want.
Now select UserCategories again, under Parameters, click on + icon at the bottom, provide name users. Change type to the enum Users we just created.
Provide Summary and Description that makes sense unlike below:
Now our Intent Definition is complete! Easy right?! Now million dollars question is how will our widget use this Intent Definition, hmm..??
- You need to expose your intent to the application. Go to Project Targets, add your intent under Supported Intents.
Go back to your AdvancedWidget, change StaticConfiguration to IntentConfiguration, and Complier starts yelling, yet again!! It’s alright we’re gonna fix it one by one.
- It needs extra parameter, with the help of xcode, apply FixIt. It is asking for the intent, provide UserCategoriesIntent.self here.
- Now compiler complains that our AdvancedProvider does not conform to the IntentTimelineProvider, rightly so. Go ahead, change the conformance to IntentTimelineProvider from TimelineProvider.
- Now, IntentTimelineProvider demands few changes. First, add typealias for Intent i.e UserCategoriesIntent. Second, add configuration: UserCategoriesIntent parameter to your getSnapshot and getTimeline methods. Xcode auto-complete should help you here. Now your AdvancedProvider should look like this:
Now run your application again, long press your widget, you should see Edit Widget option now. Select Choose from the dialog box, you will see the list of users you provided through the enum. Voila!!!
So far so good, right!! Now I want to display whatever user selects from the options. We are going to write a simple method to map the selected indices to the strings we are going to display. Configuration parameter will be passed from the getTimeline method.
Call this method from the getTimeline method. Use returned string to display in your AdvancedEntry.
That’s it, we are done with the dynamic intent configuration. Go on experiment a bit.
Deeplinking:
Another important aspect of widgets is Deeplinking. When you tap on the widget it should take you to the specific part of your application. systemSmall widgets are one large tap area while systemMedium and systemLarge can use new SwiftUI link API to create tappable zones within the widget. So deeplinking can be done in two ways:
- For systemSmall widgets, you can add .widgetURL modifier onto your SwiftUI view. It works the same way as your URL schemes. You can handle it in openURL: method.
.widgetURL(URL(string: “WidgetsDemo://relativeText”))
2. For systemMedium and systemLarge widgets, you can use new SwiftUI link API
Link(destination: URL(string: “WidgetsDemo://relativeText”)!)
That’s it!! We started with a very simple widget, made a fake advanced widget to make an actual advanced one. Quite a journey right?!? Take a break, if you want to learn bit more, proceed ahead or come back later :). You can checkout the source code here.
Bonus:
- In this example we provided hard coded data to our widget configuration, but we may require to get the data via api call. Because our configuration is a SiriKit Intent, we can provide a dynamic list of options with an Intent Extension. Go to File > New > Target> Intents Extension.
Provide the product name WidgetsDemoIntentHandler, make sure the Starting point is None. Then Finish.
Make sure you expose your UserCategories.intentdefinition to this extension also.
Select WidgetsIntentHandler under Targets, enter UserCategoriesIntent in Supported Intents.
Coming back to UserCategories.intentdefinition file, instead of using that previously created enum, we are going to create a New Type. Click on + icon at the bottom and create New Type. Name it DynamicUser.
In your UserCategories, change type to DynamicType. And make sure you select the Dynamic Options checkbox.
Now, in IntentHandler.swift file, add UserCategoriesIntentHandling conformance to IntentHandler class. This protocol will provide the abstract Dynamic options methods. Type provide and xcode with help you with autocomplete. Implement provideUsersOptions method, to provide dynamic options for your widget. You can make a network call here to fetch options from an api. I am going to use static data for this demo, your IntentHandler class should look like this:
Pretty simple, right!! Go ahead and run your code, you will see new options now:
That’s it, we are done with the dynamic configuration.
2. Handling Dates: In order make your widgets lively, swiftUI provides special initializer on text that takes date and style to format it for you. While using this initializer the counter will count up or down from the specified date automatically as time passes.
3. Rounded Rectangles: Coordinate the corner radius of your content with the corner radius of the widget. If you want your view to be concentric with your widget you can use corner radius modifier but as the corner radius of widgets varies on different devices and platforms, you have to handle multiple cases for that. Instead of all this we can use ContainerRelativeShape. ContainerRelativeShape is a new Shape type that will take on the path of the nearest container specified by the parent.
Pretty simple!!
PS: This article is written for iOS 14 beta with Xcode 12.0 beta 4, minor syntax changes are expected in future.
I hope you found this article worth your time!!
Thanks,
Ritu