Part 4: A Thing
The three parts of the "Once and Only Once" reached a conclusion. You now have Apple's templates and targets all added and refactored so you can make changes in the fewest places. This article sort of follows along from there but I'd hesitate to really call it part 4. More like Book 2: Part 1 :-)
Now we've got our project structured how we (or rather I) like we can start adding something useful to it where required. I'll be taking fewer screenshots and taking bigger steps now as is this is really just me trying to document what I actually wanted to build, rather than a tutorial for you. I'm far from experienced here, will be learning as I go and no doubt making a bunch of mistakes. If you want a more in depth tutorial on Widgets or Intents, you'll find better options elsewhere :)
Anyhoo, if you recall, what I wanted to build was something to let me add some random, 2-3 word reminders and display them, one at a time, randomly on widgets across my phone, watch and Mac so I don t get overwhelmed with ALL THE THINGS and get gentle nudges about JUST ONE THING at a time.
Since I was making a Multiplatform app, it will work on an iPad (which I don't really use) and the AppleTV (which doesn't really have widgets). I'll also remind you that I have minimal interest in the "data entry" part of the app. It will be as rough and simple as possible. The two keys (for me) were: (1) I enjoyed messing with the code structure that got us this far and (2) I just want the strings appearing randomly on the widgets.
a Thing
The beating heart of the Widget system is the timeline provider. Its job is to supply one or more configured entries with a particular timestamp for the OS to display as close to this times as it can.
The ConfigurationIntent
part is essentially a supplied UI to configure each widget in a clear, constrained way. In my case I'll eventually use it to allow a user to configure the large widget to show one very big reminder, or two smaller reminders, but for now we'll ignore it and work just on the timeline.
Right now the template has supplied us with a ThingEntry:TimelineEntry
and that just contains the required properties of a Date
(for the timeline) and a configuration (as discussed above). What we don't yet have is a model object so the first thing I want to do is make a Thing
struct to represent the Things.
All it needs right now is a String to hold the text, but I'll also add a created date as I suspect I might want to twiddle with the randomness to maybe show older ones more often.
For now I'm not worrying about persistence, or sharing between the apps and the widgets. I just want a struct for the timeline to provide and the widget to display, so I'll put it in the WidgetShared folder. I also added a static placeholder I can use where required.
Next I'll go make some changes around the Timeline and the Entry to use this new struct. I'll rename Provider
to ThingProvider
and SimpleEntry
to ThingEntry
. This is mostly in the JustOneThingProvider
file but there are a couple of changed in the JustOneThingWidget
file. While I'm there, and just because its annoying me, I'll rename JustOneThingWidgetEntryView
(the templates default name) to ThingEntryView
, because that's what it displays.
Next we'll change the ThingEntry
to take a Thing
, alongside its date
and configuration
. Wherever the compiler needs me to supply a Thing
, I'll just use a Thing.placeholder
instance for now.
This seems a decent place to make a commit, its in the repo as "Part 4: Add a Thing struct
"
ALL THE THINGS
So next up I'm going to work on the timeline to make a few actual Things
. I'll split up a simple data store array of things and the timeline building which add them at hourly intervals.
Eventually the data store will need to be persistent and it will need to be shared between the apps and widgets, but again for now I'll just embed them in the Provider so I can keep working on the widgets.
First thing I did was make a new initialiser for the ThingEntry that provided defaults for the date and the configuration to make things a bit less verbose.
That lets me reduce some of the code in the placeholder and snapshot methods, and in the preview in the ThingWidget
file. I'm not yet sure what they do exactly, but less code is better and I will figure it out later. The main timeline method still needs all three arguments but at least the Thing
is first.
While I'm doing this I realise again that I am annoyed by ConfigurationIntent
. It's passed into something else called an IntentConfiguration
in the widget file and the names are irritating close together, and opaque. This is a classname I can change, but it's magically generated from the Intent Definition file. I'll fix it later, cause I'm doing something else, but it bugs me and I should find how how to change it.
Now back to the Data Store.
A simple ThingDataStore
that can give me back an array of Things, hardcoded for now. I've also added a way get them in random order. I'll add a ThingDataStore
instance to the provider to separate the "getting of things" from the "building of timeline entries".
In the Provider we'll change the timeline code to get all the shuffled things and return them as timeline entries 15 minutes apart.
While I was there I pulled the recommendations
method out to an extension as it didn't seem to belong with the rest of the "give me some entries" methods. Its only required on the watch so potentially I could move it to the WatchWidget folder/target in a separate file.
I didn't like having to the keep the separate line to increment the running time when what I really needed was a method on date which incremented and returned self. So I added one.
That let me simplify my timeline method a little
Next, I want to update the ThingView
to display the text of our Thing
.
I was tempted at this point to make it a ThingView and just pass the thing rather than the whole entry, but we may want to access the configuration at some point and so for now lets leave it taking the whole entry and live with the Demeter violation.
OK! If we switch to the MacWidgetExtension target and do a clean build, and then run, we'll load the widget up pin the WidgetKit simulator app where we can see the placeholders, snapshots etc, and most importantly our timeline!
The timestamp at the bottom of each widget is the timeline date from the entry and the text is the Thing.text. We can see as the timeline proceeds the widgets are displayed in a random order. W e're done, right ? SHIP IT.
Well, we'll at least commit. I've called it "Part 4: Show the Things".
I'm tempted to stop here for now. It's quite tiring doing a little programming and then stopping to show what I've done. Last thing I'll do for now is make a little icon for the apps and choose an accent colour to make things a little more interesting.
I went with the systemOrange
for a highlight colour and I used the excellent Bakery app to knock up a quick Icon. Bakery is pretty clear that it's just an SFSymbol on a background and that means you cant use them for App Store submissions due to copyright issues, but it's fine for development :-). It doesnt do the AppleTV as the icon there is layered and complicated, but it'll be nicer on the Mac and Phone and Watch (my primary use cases).
A last commit - "Part 4: Colour and Icon" for the above, and then one more for some super simple AppleTV assets I knocked up in Acorn ( "Part 4: Apple TV Assets" ) and we're done for now!