At this point, we have four test harness/demo apps prepared and ready to go. They currently operate using a very simple “mock” functionality that I took the liberty of adding to the SDK.
We also have the “skeleton” of the SDK ready. The SDK can be instantiated into two sets of functionality: Central (Bluetooth Central behavior), and Peripheral (Acting as a Bluetooth Peripheral).
The Central will “ask questions” of the Peripherals, which will act as “Magic 8-Balls.” In the Mac, iOS and TVOS variants of the apps, the user will be able to enter a question manually. In the Watch variant, due to the difficulty of entering arbitrary text, we will choose a random question from a pool of 20 questions.
Each Peripheral will have a pool of 20 answers. These will reflect the original answers from the Mattel Magic 8-Ball.
HOW THIS LESSON WILL GO
If you are used to the normal “Now add this code to this place” methodology of teaching, you may be either A) disappointed or B) pleased by how this will go.
I will make the changes in the code, then check it into the Git repo, with a tag for the end of the step I just did.
I will then link to a GitHub Compare between the previous step tag, and the end tag for the current step.
After that, we’ll walk through what I did. We are free to take the code from the beginning, and emulate the changes that I made to the code (which I’ll usually have listed).
I will be removing the comments in the entry examples (for brevity). The actual code checked into the GitHub repo will be heavily documented, but the examples in the entries will not be documented. I may also remove things like assertions.
THE COMPANION GIT REPO
Starting now, the accompanying GitHub repo should be cloned locally, and checked out at this tag (itcb-09).
Each step will be tagged, and we’ll be able to get a “clean” starting point at any time, by simply reverting the repo to the tag that accompanies each step (which I’ll link at the top of the page). We should not be afraid to “play around,” and “make a mess.” Git is a great way to make sure that we are always starting at a known state.
I’d suggest that we start each step by doing a “hard revert” to the master branch, at the tag linked at the beginning, so that we are all on the same page.
It’s quite possible to have a second, parallel, branch, with our own work, that is updated (and possibly synced with the master branch), on our local machines.
If we want to keep a second branch in a remote repo (like GitHub), we can use a technique like this one (what I do, as I develop the lesson).
This prevents the repo from becoming very “noisy.” I do many, many commits as I work, and it can leave a long and rather boring history.
MAJOR CAVEAT
Apple doesn’t support Peripheral Mode for the Watch or TV platforms. That means that we can only run as Central for those platforms.
This doesn’t seem to be explicitly documented anywhere, but if you look at the SDK documentation for
CBMutableService
andCBMutableCharacteristic
, the classes, themselves, are supported, but their initializers are not.That means that the docs are…“factually-challenged.” The classes are not, in fact, supported, as their only available initializers are not supported on those platforms.
Because of this, the TV and Watch apps will only run in Central mode.
This was covered at the WWDC in 2017, but doesn’t seem to have made it into the docs…
THE PROJECT STRUCTURE
If you check out the itcb-09 tag, you will see a directory that looks like this:
There will be an Xcode workspace file (ITCB.workspace
), and two main directories (Apps-src
and SDK-src
). The other files are basically “project meta” files that won’t be used in the lesson.
We’ll be using the workspace file for all our work, so there won’t really be that much of a need to know the file structure, but I’ll review it anyway, so we are all aware of what is where.
The Apps-src Directory
This will definitely be the largest directory. I have already done the work, here, so we won’t need to do anything with the contents of this directory.
It contains four (4) complete, ship-quality (but absolutely tiny) applications. One for each platform that we’ll be supporting.
I should note that the WatchOS app is one of the new “independent” Watch apps, and does not require a companion iOS app. This means that it must run on devices that have a minimum of WatchOS 6.0.
Each app is localized, and has some (rather primitive) graphical assets. The localization is shared between all the apps (this is something that I like to do, if possible. It ensures a consistent localized user experience, and makes translations easier and cheaper).
The user experience is quite similar between the app variants, with some basic platform differences.
In the following discussion, I will use screengrabs from the Mac version of the app. The Watch version is a lot simpler, because it doesn’t have the ability to enter text, or select questions. It just has a “Send Random Question” button.
The apps will not change at all during the lesson. They are complete. All changes will happen with the SDK.
The First Screen
The iOS and MacOS apps begin with a screen that allows us to select the operating mode of the app:
The 8-ball on the left (with the Bluetooth logo) will run the app as a “Central,” where we select a Peripheral, then “ask it a question,” to which it will respond with an answer.
The 8-ball on the right (with the “8”) will run the app as a Peripheral, where the app will wait for a “question” to be sent, and will respond with one of the twenty possible answers; either directly selected, or randomly selected.
Running As A Central
If we select the left icon, we will be shown a screen similar to this:
It will be a list of “discovered” Peripheral devices (8-balls). We select one by clicking/tapping on that row, which will give us a screen that may be similar to this (Mac, iOS and TVOS):
We can enter text for a question, then hit the “Send Question to 8-Ball” button.
The resulting screen would look like this:
It should be noted that this behavior is completely mocked, at this point. Nothing is sent anywhere. A random answer is generated and returned immediately from the SDK.
Error Simulation
We also emulate errors, as random responses. If that happens, we will get an alert that may look like this:
These are generated completely randomly. It’s possible that we may not see one, or get several in a row. If we get one, we can simply go again.
Running As A Peripheral
The Peripheral screen is a lot simpler. If we select the right icon in the Mode Selection Screen, we will be presented with a screen like this:
The question sent from the Central device is displayed at the top, just under the Central’s name. We also have a “Send A Random Answer” button, which will randomly select one of the answers listed below it.
Alternatively, we can also tap/click on an answer to send that answer explicitly.
After “sending” the answer, we get an alert that looks like this:
Again, it should be noted that this is all completely mocked. Nothing is actually getting received or sent anywhere. The questions that we are being “asked,” are generated randomly, using a timer (in this file).
The SDK-src Directory
In this directory, we can see that there is no platform-specific code. The same code is retargeted for each platform.
There are “hacky” ways to make a fully cross-platform SDK (I’ve used them, in the past), but I don’t really like using something that isn’t supported by Apple for production code. As a result, I produce four different framework targets from the code. The target directories simply contain separate info.plist
files.
The actual code is exactly the same between the variants. We will only be working on one set of files.
We will end up with four separate frameworks, which should be embedded in the same app as their name implies (it is also the module name):
That Weird “Project-Meta” Directory
That directory is one that I tend to include in all of my projects. I run a REALLY tight ship, so I use SwiftLint to LINT the code (I usually use CocoaPods to install it, but I include it as an executable, in order to ensure that this project has no dependencies), and I have an xcodeproj file that runs the target with a lot of warnings “turned to 11.”
The files that we will be playing with are in the “src
” directory.
After this, we’ll be starting to get technical, with a lot of Xcode-specific discussion. If Xcode is not our IDE of choice, I suspect that we may be adept at casting Xcode directions to our IDE.