Part 6: Multiple Widgets
At the end of the last part I was aware that although the widget worked in all the contexts I care about, it looked a little weird in the .accessoryCircular
family, which appears in certain watch complications and iPhone Lock Screen widgets.
In this part I'll add a second type of widget and talk a little about different widget families and contexts. As usual my focus here is on trying to share as much code as I can and have a single project that supports all the different options in obvious ways.
This section is a lot of moving code around, really, but again it sets us up going forward for more flexible widgets.
Divide and Conquer
First I'm going to pull apart the JustOneThingWidget
file. I've made a new ThingView.swift
file and pulled the ThingView
and the ThingEntryView
out to it, to leave just the Widget-specific stuff in JustOneThingWidget.swift
.
Lets also make a WidgetBundle.swift
file and pull the WidgetBundle
out to it.
That just leaves our JustOneThingWidget
struct.
Next we're going to rename the file and the struct to SystemThingWidget
(and you'll see I added some more descriptive text to the configuration display name and description. Also, crucially, I've added a .supportedFamilies
modifier to specify only the .systemXXX
families.
There's also a .systemExtraLarge
family but it's only on iPad, which I don't much care about. Having it there would mean more conditional checks so I just ignored it. Those three are available on iOS and macOS so all good.
The last thing you can see above is that I ended up just deleting ThingEntryView
and going straight to ThingView
. The EntryView wasn't really doing anything useful and I didn't need anything from the Entry but the Thing
.
Next we're going to add a new file and call it AccessoryWidget
and copy and paste the whole SystemWidget
into it.
I've renamed the struct and changed the configurationDisplayName
and description
just so we can tell it apart from the SystemWidget
. I've also set the supportedFamilies
to the .accessoryXXX
options.
Now we have two widget types we need to make sure they are both in the WidgetBundle
. This is where things get interesting.
Computer Says No
It turns out that (at time of writing) the system families are not supported on the Watch and the accessory families are not supported on the Mac, so we'll need to help Xcode build and package the right things for the right platforms.
First we'll see target memberships for the SystemWidget
and AccessoryWidget
files to only build them for the right platforms. SystemWidget
is NOT for the watch and AccessoryWidget
is NOT for the Mac.
Next, since the two widgets may or may not be available, we'll need to help WidgetBundle
out.
Once again, SystemWidget
is NOT for the watch, AccessoryWidget
is NOT for the Mac.
At this point everything. should build again and we have two distinct widgets for two different situations, though they both just show our ThingView
text.
Lets try them out in the various simulators.
What do we have?
On the iPhone, we can see the Home Screen is loading the SystemWidget
(the displayName
is Just One Thing). If we add a widget to the Lock Screen we can see its using the AccessoryWidget
(displayName
is One Tiny Thing).
The watch is interesting too. It's not immediately obvious we're getting the AccessoryWidget
because the watch doesn't show the displayName
or the description
. Instead its showing the text from the recommendations
method on the Provider ("My Intent Widget").
Here we can see the accessoryRectangular
, accessoryInline
and accessoryCircular
. Rectangular looks just like the SystemWidget
, and we could have tried to make the SystemWidget
support that family, but we'd have still had to deal with a more complicated situation around the availability of families on platforms. This way, there are two distinct widgets for different situations, but we know they are just simple wrappers round the ThingView
, so even though we don't share the Widget struct itself, there's a single place we can make changes and have them be reflected in both platforms.
I notice that they accessoryInline
view isn't using my fonts or colours, just simple text. Not a problem for now, but something to be aware of if we change ThingView
to be more complicated.
Lastly the accessoryCircular
is still just a tiny version of the ThingView
and we want to change that.
I'd like to quickly explain the recommendations
situation first though.
If we got back to the Provider code and add second recommendation...
Then go back to the watch simulator to add a widget...
You can see how the configurations work. We have one single AccessoryWidget
that (in this case) supports the accessoryInline
family, but we offer the user two different configurations.
If you remember back a few posts ago, I explained that the IntentConfiguration
stuff is to provide a way for a widget to be configured on the iPhone or the Mac. Perhaps we offer a way to change the number of Things
, or the colour.
That complex UI is not possible on the watch and so we offer some suggested configurations like "One Thing, Random Colour", "Two Things, Accent Colour" and the watch user can select which prebuilt configuration they want to add.
We'll ignore this for now and leave the existing single recommendation as we have no configuration system yet. Let's get back to making a slightly nicer circular complication.
Where the SystemWidget
just uses a ThingView
, the AccessoryWidget
now has its own wrapper view, which checks the environment for the widgetFamily
and returns something small and simple in the circular case.
When I hacked this together the first time I made the circular widget show a total number of Things
, but since I haven't built a real DataStore
for this yet, I just went with the SFSymbol I've been using as an icon. Its not winning any awards, and its just a basic launcher rather than glanceable information, but it looks slightly better than the tiny text.
That's a wrap for part 6. The commit is "Part 6: Separate Widgets"