OAuth is, to borrow a phrase, a bag of hurt. Not just any bag, but the kind that is thrown over your head right before you’re thrown into a van and driven down to Tijuana and ransomed for $50,000. I don’t like OAuth.

Sometimes, though you need to use it. I’m working on an internal tool at work and want to make sure that only co-workers can use it. I could do the username/password thing, but the world has enough of those. We use Google docs for the office, so everyone has a Google account that could, theoretically, be used to sign in. Thus, OAuth.

The tool itself is a web app that uses Ember on the front-end and Hapi and MongoDB as the API and database. It’s my first time using Hapi, and I like it quite a bit, seems like a really good tool for API building.

Since this is a completely separate client and server, it adds a bit of a wrinkle to the OAuth dance. There are plenty of “Sign in with X” libraries out there, but they mostly seem to be written with the intent that the user will only be interacting with that service. In other words, if I was building a Google Docs client app, my life would be easier, but no, I am only authenticating a user’s account and then treating that as the key to my own system… which makes it more manual. It also doesn’t help that Google’s OAuth documentation is lousy. There are many different ways to authenticate, and multiple libraries to support the different ways, and they’ve changed over time, so searching for help is difficult.

Here’s how it’s supposed to work:

  1. The client (Ember) “signs in with Google”. If this is successful it receives an authorizationCode. This code by itself is useless, it just means the user signed in ok.
  2. The server (Hapi) needs to validate this code and turn it into an accessToken.
  3. If that is successful, we can query the Google API (from the server still) for the account information, verify that they are from Tallwave, and then send credentials back down to the client.
  4. The client receives the credentials, stores them locally, and includes them for subsequent API requests.

Errors need to be handled at all the steps along the way too.

Step 1 — Client Side

Torii is a pretty good (though a wee bit out of date) library for Ember that handles a good amount of the Google sign in process. Follow its installation instructions and setup the configuration and the proper adapter. Here’s my environment.js entry:

1
2
3
4
5
6
7
8
9
10
11
/* The following properties are in ENV */
torii: {
  sessionServiceName: 'session',
  providers: {
    'google-oauth2': {
      redirectUri: signinURL,
      apiKey: googleOAuthAPIKey,
      scope: 'profile email'
    }
  }
}

The scope property is important. That allows you to query for the user’s account later on. The only other point with Torii worth mentioning is how I sent the authorization code to my API. The open method on the torii-adapters/application.js is called after Google auth finishes, and that is where you create the “session” for authentication.

By default Ember uses the JSON API for the API layer, and I tend to go with the flow. But in this case, since we’re sending a single value to the server and will throw it away immediately after… Setting up a model seems overkill for that. We can drop down to regular old jQuery for this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// This is in torii-adapters/application.js
open(authentication) {
  return new Ember.RSVP.Promise((resolve, reject) => {
    Ember.$.ajax({
      method: 'POST',
      data: { authorizationCode: authentication.authorizationCode },
      url: '', // this is the authentication URL in your API
      success: Ember.run.bind(null, resolve), // forward success/error to the Promise
      error: Ember.run.bind(null, reject)
    });
  }).then((account) => {
    this.get('sessionStore').save(account);
    return { currentUser: account };
  });
}

Worth pointing out is that we are expecting our API to return some JSON that will be:

  1. Stored locally (sessionStore is a service, which we’ll define later), and will be retrieved later.
  2. Returning the currentUser object will merge it into the underlying session, so that it can be retrieved in routes, components, templates etc. This isn’t permanent storage though, if the user refreshes the page, it’ll be gone, hence sessionStore.

Step 2 — On the Server

This is where the fun begins. If you’re playing at home, I used Node 5 and Hapi 13.x.x. First, you’ll need to get your credentials from the Google Developer Console. I’m not going to walk through those steps, but you’ll need an OAuth client ID for a Web Application. Specifically, you’ll need the client id, client secret, and redirect URL (yes, even though you won’t be redirecting to anything at this point).

Then, install the googleapis dependency.

1
npm install googleapis --save

Now, let’s build a service that sends Google an authorizationCode and gets an accessToken back. You’ll call this service from your route handler.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
const Google = require('googleapis');
const OAuth2 = Google.auth.OAuth2;
const profile = Google.oauth2('v2');

// pull these from an ENV variable or something else
const oauthClient = new OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URL);
// Omitting the REDIRECT_URL will throw an error

module.exports = {
  // callback is a function whose signature is (error, tokens)
  getTokens(authorizationCode, callback) {
    oauthClient.getToken(authorizationCode, (err, tokens) => {
      if (err) { // the authorization code was bad
        callback(err);
        return;
      }
      // At this point, we have an access token.
      oauthClient.credentials = tokens;

      // Each Google API method takes a params object, 
      // which is where you provide your auth credentials
      const params = {
        auth: oauthClient
      };

      // Retrieve the user's account
      profile.userinfo.v2.me.get(params, (err, response) => {
        if (err) {
          callback(err);
          return;
        }
        // The response object contains all of the account info

        // This is where you validate if the user is allowed or not.

        // If the user is valid, create a new session for them and save it.
        // It's up to you where you want to save this session. Could be 
        // a redis cache, http session, whatever your needs are.

        // After that, we're going to send our session token and user 
        // account back.


        const sessionJSON = {
          token: someSessionToken,
          account: response
        };
        callback(null, newSession);
      }
    });
  }
};

That’s kind of a mouthful and could definitely stand a good refactoring, but it goes through the steps needed to get some information from Google. You can see more APIs you can call, but the documentation on a lot of them is rather sparse. For example, adding the oauthClient to params is not apparent, but necessary for each call.

We’ve now determined if the user should actually have access to what they just signed in to and sent the response back to the client. That’s pretty good! Refresh your beverage, take a deep breath and then let’s get handle that response and get our client in order to keep moving.

Step 3 — Back on the Client

You might have noticed again that we did not send the session information back in JSON API format either. This was intentional since we aren’t going to be storing this in the standard Ember store, and therefore don’t need the additional benefit of using something that standardized. You could if you wanted to, but for this occasion, I did not want to set up a model (or in this case models to support the nested structure).

Back up in our Ember.RSVP.Promise handler there was this line:

1
2
// In torii-adapters/application.js
this.get('sessionStore').save(account);

Once you receive a response, you’ll need to store it. You could use Ember Data, but that felt overkill to me; I needed to access the tokens within the response for subsequent API calls, and nothing else. I ended up building an Ember service that serialized the content and stored it in localStorage. You can go your own way too.

Step 4 — Making authenticated requests

We’re authorized (or have displayed a fancy error message) and are ready to make actual requests to get actual data. I like to authenticate my API requests by including my authorization token in the Authorization header on requests. Ember allows for this by overriding the headers computed property in your Adapter:

1
2
3
4
5
6
7
8
9
10
// adapters/application.js
  headers: Ember.computed(function() {
    const account = this.get('sessionStore').fetch();
    if (account) {
      return {
        'Authorization': account._id
      };
    }
    return {};
  }).volatile()

Step 5 — Verify authentication on the server

There are may ways to authenticate requests with Hapi. Hapi calls these “Schemes”. A Scheme is made up of multiple Strategies but since we only have one, we’ll just refer to it as a Scheme.

You define a Scheme as a function that takes a server and some options as arguments and go from there. Here’s api-auth.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module.exports = function (server, options) {

  return {
    // These are the standard Hapi request/reply arguments
    authenticate: function (request, reply) {

      // Get the session token from the HTTP Headers of the request
      const req = request.raw.req;
      const authorization = req.headers.authorization;
      // Fail quickly if it isn't present.
      if (!authorization) {
        return reply(Boom.unauthorized(null, 'Basic'));
      }

      // Pull the credentials from the session store that are 
      // associated with the session token. Fail if it's not valid.
      if (!tokenIsValid) {
        return reply(Boom.unauthorized('Authenication failed', 'Basic'), null);
      }

      // Authenticated, so send the credentials along the chain in 
      // case they need to be used later.
      return reply.continue({ credentials: credentials });
    }
  };
};

Now that we’ve defined our Scheme, we need to register it with Hapi. Wherever the server is set up add this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Import our Scheme
const apiScheme = require('./config/api-scheme');

exports.register = function (server, options, next) {

  // Auth
  // Schemes have a name which you define here.
  server.auth.scheme('auth-scheme', apiScheme);

  // Strategies also have a name. This is what you'll refer 
  // to when setting schemes for routes.
  server.auth.strategy('basic-strategy', 'auth-scheme');

  // You can declare all routes to use a one Strategy by default.
  server.auth.default('basic-strategy');

  // Routes might go here
};

If you do use a default auth strategy, you can override that on individual routes in their handler config:

1
2
3
4
5
6
7
8
{
  method: 'GET',
  path: '/',
  handler: function(req, reply) { reply(); },
  config: {
    auth: 'another-strategy'
  }
}

Or use the boolean false to skip auth altogether for a route, such as your session creation API.

And we’re done! You now have an authenticated API. There are plenty of other points that could be expanded on, but it’s getting a little long in the tooth now.

Wait, why not use Bell?

The Hapi organization maintains a project called Bell that is a “Third-party login plugin for hapi”, including OAuth services. It’s great, but the catch is that since half of our authentication takes place in the browser, we can’t quite use it the way it is intended to be used.

“We’re going to Maine.”

“Oh… do you have family or friends out there?”

That’s usually how the conversation went when I informed people of The Williams Family 2016 Vacation plans. It was kind of weird. I mean Maine’s not exactly the hotspot for getaways. There are a bunch of more tropical places. Hawaii, Florida, The Caribbean, Idaho. What’re you gonna do in Maine? Visit Stephen King?

Well, here’s what we did.

Beach time

We rented a place for the week in Old Orchard Beach. It was a nice little beach condo about 100 feet from the ocean. You wouldn’t normally think of Maine as being a beach destination, but it was better than a lot of quite of few of the beaches in southern California I’ve been to. Not too rocky, no giant riptide, water was warm enough (mostly). The waves weren’t quite as nice, but fine enough to swim through and throw small children into.

Beach
The path to the beach
Beach
Partly cloudy
Beach
The girls getting in the Atlantic
Beach
Catching waves
Beach
At night
Beach
From the pier
Beach
Sometimes the moon looks like the sun

Weather

The temperature was usually in the low 80’s during the day. The last day was warm, topping 90, which apparently didn’t happen at all last year. Other than that, it was great. It rained a few of the days, but that was a nice change of pace.

Beach
Storm clouds
Beach
Fog. I haven’t played Silent Hill, but this can’t be good right?
Beach
Temperate
Beach
Bliss

Food

I’m on record as loving seafood. If it lives in the ocean I’ll probably like eating it, and lots of things swim in the ocean near Maine. We had lobster, lobster rolls, lobster mac ‘n cheese, crab rolls, crab cakes, and shrimp. Most of it was ridiculously cheap. The best places were little dive type places that smelled of delicious fish. I didn’t take too many pictures because I’m not the biggest fan of taking pictures of food. Perhaps my favorite was a lunch that consisted of two full lobsters, a lobster roll, and a shrimp cocktail. All of that was under $40, which is usually the price for a single lobster at a fancy restaurant out here. Not bad.

Nature

Maine’s one of the most beautiful places on Earth. The beaches are just a few miles from some amazing forests. I could gush, but I’ll let the pictures sell it some more.

Beach
Flowers by the bay
Beach
House at the end of the lane
Beach
Gateway
Beach
Forest
Beach
Shortcut to Mushrooms
Beach
Hay
Beach
Waterfall
Beach
Forest moon of Endor
Beach

And the rest

Maine also has Lighthouses. This one is called the Portland Head Light.

Beach

There were fireworks on the beach one night.

Beach

We visited a farm and picked berries. Then ate them.

Beach

We rented kayaks and paddled up the Saco river.

Beach
Beach

And Finally

We had an early checkout time on Saturday, but our flight back wasn’t until the evening. We took the scenic route through New Hampshire and stopped at Cathedral Ledge for a little hiking and sightseeing. It was a good stop.

Beach
Rock climber (definitely not me)
Beach
The view

Apps on my home screen that I use most often and my opinion if they’d be appropriate for the new subscription service Apple is rolling out.

I wonder how many, if any, will consider switching an existing subscription service to one built into Apple’s systems. I wonder how many, if any, companies will rush into subscription without really thinking it through and angering all their users. There are a whole lot of economic factors at play to consider before doing that.

I received my Apple Watch just over a year ago. I bought it because it felt like it was going to be the next big platform, and I didn’t want to miss out. That didn’t turn out to be the case, but what did suck me in were its fitness tracking capabilities. I’d been concerned about my overall health and weight since I hit my 30s; my family history that includes such fun things as diabetes and heart disease, and I wanted to avoid those. The watch finally served as the catalyst to really try to do something about it.

A year later, I’ve lost 50 pounds. I can run 8 miles without stopping and will be running a half marathon in early 2017. This is how I did that and how I plan to lose a little more, and keep it off. Also note: this worked for me and may not be replicable. People are different and there are so many other factors with regards to health and weight. I don’t write this to brag (ok, well maybe just a smidge), or to instruct, but to log.

This was not my first rodeo. A few years ago I was traveling back forth from Phoenix to Dallas for work. Since this was going to last for a few months, so I decided to take advantage of the “free” time and get serious about working out. I signed up for a gym membership, mapped out a plan with a trainer, and started to lift weights and do the various gym things you do. I also had a per diem for eating expenses from the company and ate far better than I normally would have if left to my own devices1. It worked pretty well. I went from about 235 down to 195. I had all the intention to keep it off, but then I didn’t.

When that project was over I was back at home. I kept up the gym work, but it got harder to fit that in my schedule. In Dallas I didn’t have anything else to do but work, go to the gym, and watch bad TV in my hotel room. At home I had a family and friends to, y’know, do things with. I was back to thinking about my own food again, and the laziness and lack of discipline crept back in. Working in an office with lots of free snacks and soda did not help either.

Eventually the gym attendence faded too. The process of putting on the gym clothes, finding my bag, driving to the gym, doing the locker room thing, then going to lift weights, all on a relatively strict schedule started to be too many hurdles for my lazy butt. I would go to the gym but then put in a low effort on a treadmill for 20 minutes. Then I just didn’t even go anymore.

Time went on and I found myself fatter than I had been before, somewhere north of 2402. It crept up on me and I didn’t think I had gotten back to where I was before, and then some.

After some thought I realized that my failure with the Dallas plan wasn’t sustainable at home. Everything I had done was based on the notion of being away from home and having the time and money to do all the right things. I needed to come up with a plan that would work with my irregular schedule. I also didn’t want to go on a special diet, or any kind of diet really. I’ve seen so many people lose weight from dieting and then gain it right back. The evidence supports that notion. I knew that for something permanent to happen, it would have to last me the rest of my life. I came up with three rules to improve my health:

  1. Eat a little less
  2. Eat a little better
  3. Exercise a little more

And let time and attrition do its thing. I knew this wasn’t going to get me any book deals, but it felt like it would work with me.

Eat a little less & Eat a little better

I like to eat. No, I really like to eat. I find it to be one of life’s pleasures, and you get to do it three times a day!3 I could never be a vegetarian, despite any health risks because I love to eat cows and pigs. Paleo won’t work for me because I have a torrid love affair with sandwiches.

Yeah, any diet that was courting me was gonna need a really good pre-nup before I’d commit to it.

I also had developed quite a fondness for soda. That became my caffeine delivery system of choice. Some people would come into the office with their Starbucks cup, I had my thirstbuster. At some point I switched over to diet soda, which is gross, but had less calories. I never really dug into the science of it, but I don’t know if that’s an even tradeoff, especially at 48oz (or more, yikes) per day. I eventually decided to ween myself off soda and force myself to like coffee (and it’s miniscule calorie content) instead. It took some doing, but I now appreciate the stuff. Being able to do the ceremony of Aeropressing everything helped trick my brain into thinking it was doing something fancy too.

Despite curtailing soda significantly, I still usually had at least one 12oz can of Coke with lunch, nearly every day. I really wanted to kick it altogether, but the flesh is weak. Through sheer willpower I got it down to one (or two) per week. That maintained for a couple of months, and then I got the stomach flu and was waylaid for a few days. Due to a coincidence of timing, I managed to go a whole seven days without any soda, hitting that one week goal. After that, it was easier to skip. That’s a rather “unorthodox” approach, but I guess it worked? I’ll have a soda every now and then4, but it doesn’t have the same allure as it used to.

Snacking has been harder to cut back on. Free food at the office seems to be able to circumvent most of the circuits in my brain and hit the MUST-HAVE centers directly. After a really long fight I’ve cut back on trips to the candy dish full of delicious Hershey nuggets to a minimum.

And let’s talk about diet in general. Like I said, I’m never going to be be successful on one found in a magazine, but I could make changes to my diet in general. I’m not going to ever cut carbs out completely, but I don’t have bagels nearly as much as I used to. Now they’re more of a special occasion than a common occurrence.

Time permitting, I cook up some breakfast sausage and eggs in the morning. If I’m feeling like I really want to be healthy (which is rare), I’ll get some greek yogurt, granola, and fruit, but frankly I’m not much of a fan of that.

I try to track the calories too. I use MyFitnessPal, but I I’m not exactly in love with it. Their database is probably the best out there, but it’s not complete; and as a software developer I empathize with the notion of tracking every single food item in the world, but it’s still too annoying to try to find everything, especially if you cook a lot of your meals or don’t eat at chain restaurants. I probably only track about 50% of the stuff I eat, and sometimes go for days without tracking anything (which I’m betting maps up quite well with the times I eat worse).

My meals themselves aren’t too bad. My wife and I both like to cook, so we have a home cooked meal most days, and don’t go the Full Paula Deen. My achilles heel at home is snacks. I love chips, crackers, cookies, pretzels, anything crunchy really. If given the choice between some chocolate and cheez-its, I’ll side with Team Kellogg. I can’t mandate that those things are forbidden in our house either. My wife and kids have better self-discipline. Rather, I’m trying to look at trends when I snack down too much. I usually snack in the evenings. The snacks are downstairs, and I’ll usually spend the evenings upstairs. With these two data points, I try to avoid going downstairs in the evenings; laziness beats out food, usually.

I also regress more often than I care to admit. I’ll have a few good days, and then eat too many chips or cookies. It’s getting better, but fortunately the other prong in my plan helps offset some of that.

Exercise a little more

Filling in the rings on the Apple Watch is fun. Getting a chain is of completions is even better, and creates a virtuous cycle. I wanted to keep hitting my goal and increasing the streak of completions. I got my watch right before traveling to San Fransisco for WWDC, a conference for Apple-centric software developers. It was a full week out there, and involved a lot of walking. I hit 2.5-3x my move goal every day out there. I was hooked on keeping that thing filled out.

Since the gym wasn’t going to work for me, I tried to think about what I could do that would be sustainable for my lazy self. Walking was the obvious choice, but I also wanted to move just a little beyond that. I didn’t want to take up running [yet]; I had tried doing that before and burnt out on it quicker than weight training. Also it was summer in Phoenix which meant if you were outside you ran the risk of facing the wrath of an angry God.

Smaller steps were needed. I started to take a lap around the office atrium a few times a day. I’d walk for 8 minutes to a nearby shop for lunch rather than drive for 2. I even ended up running in place at home (in the air conditioning). Yes, that’s as dorky as it sounds, and I would stop if another human came within visual range because it’s just so embarrassing. But it worked well enough, and I could do it just a few minutes here and there to fill out the rings.

Eventually, I figured that I should try to suck it up and run again. So, in late July (yes, when your shoes might stick to the pavement because they’re melting) I “ran” a 5k. My time was 40:49 and involved a lot of walking, but I completed it, and drank a gallon of water afterward.

After that, and the heat, I didn’t do another run until mid September, and then again until early October. I remember not wanting to run — I felt incredibly sore, sweaty, and would much rather continue doing the little stuff that I had been for the first few months. But by this time, I wanted to “level-up” a bit. The competitive side of me started to kick in and I wanted to see if I could be a “runner”.

My first goal was to complete a 5k without taking a walking break, which I had never done before. As I got a little better, my time dropped down to the mid 30’s. Then I managed to run it with taking a single walking break for just one minute. Finally in mid November I ran 3.11 miles straight without any breaks. Shortly after that I cracked another goal and ran it in under 30 minutes5. I wasn’t expecting to do that quite so quickly.

This wasn’t without any hitches though. I had some nasty shin splints at the start. I would be incredibly sore from a run and hit the Advil right after the shower. And the chafing, oh the chafing [picture removed, gross, come on. —ed]. Taking it a little easier helped the shin splints to go away, and eventually the soreness stopped being an issue.

Something weird happened. I started to enjoy running. The sense of satisfaction from completing a run grew. I started to go a little longer, occasionally a little faster. I got to the end of my first 5 mile run and thought, “hey, I bet I could go a little further”. I forced myself to not overdo it and not add too much distance too quickly.

I also started to consider races. I am under no illusion that I would be competitive in any kind of race6, but actually putting something on an official record sounded interesting. I could run a 10k well enough, and thought about one of the longer races for a stretch goal. A full marathon was probably out of the question due to time constraints, but the half was alluring. You train for something like that by doing moderately short runs during the week and a longer one on weekends. You don’t even have to run the 13.1 miles during training. I figured why not? and signed up for the Rock-n-Roll half marathon in January 2017.

This process took a whole year. And it was a gradual, incredibly gradual, increase in effort. If 2015 me had flipped a switch and started running right away as often as I do now, he would have given up pretty quickly.

The other big question: is this sustainable? It’s been a year, and if anything, my dedication to getting healthier is stronger than it was before. I weigh 194 pounds now, and want to get to 175. The road has been uneven, but the trajectory is going in the right direction.

Misc

  • Calorie counting is such a pain in the butt, and I don’t necessarily believe what any of my trackers tell me… After putting my age/weight/goal into MyFitnessPal it told me that if I ate 2600 calories per day I’d lose a pound a week. That’s a flat out lie. If I want to lose weight, I need to keep it under 2000 and keep up my exercise routine.
  • Relatedly, that thing where if you lose a bunch of weight and then your body fights against you to gain it back? I think I’m seeing that to a small extent. My hope is that since I don’t plan to stop my routine, it won’t come flooding back, and that it’ll settle down after a while.
  • I still drink alcohol too. I don’t drink much beer anymore, preferring wine and whiskey.
  • My fastest 5k is 26:56, and I almost always keep them under 30 minutes. I don’t think I’ll be breaking that again until it cools off… in November.
  • I used to run at night, but switched to mornings. My body would be tired after a run, but my brain would be rocking out like Andrew WK, causing me sleep difficulty.
  • My calorie burn rate has dropped significantly. I think this is a good thing. The jog in place thing used to get up to 12 per minute, but that’s down to 9 now. Yes, I still do it if I’m close to my daily goal and don’t want to go back outside. Stop judging me.
  • If you’re on RunKeeper, look me up!
  1. Seriously, I’m not too proud to eat from a gas station if necessary. I find Olive Garden and Chili’s to be gross though.

  2. I don’t have the exact number because I was too embarrassed to weigh myself before starting on my latest track. I regret this decision because now I don’t know exactly how much weight I lost. My first measurement was at 239 after about a month and a half, so with a little guess work I figure it was around 245.

  3. This is why I don’t really understand Soylent and the things like it. I don’t eat to get full, I eat because food is great. This is probably also why I’m fat.

  4. I think I’ve had under 5 for all of 2016 so far.

  5. 29:56 on November 23.

  6. Hearing a teenaged runner talk about getting his 5k under 20 minutes was an eye-opener. I am optimistic about improving my speed, but I don’t think I’ll ever get that fast.

I’m very happy to introduce a new open source library that I’ve been building. It’s called Gliphy and it makes implementing Dynamic Type on iOS with Swift a breeze.

Accessibility is one of those things that everyone knows they should be better at implementing, but falls to the wayside when deadlines are looming. I liked the idea of supporting Dynamic Type in the apps I write, but it was kind of a slog to implement it. There are two main ways to do it right out of the gate with what is built in to iOS, code and Interface Builder.

Let’s go with code first. The easiest way to get the preferred font for a given text style.

1
titleLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)

iOS will set whatever the “Headline” style is to your titleLabel’s font. If a user changes their default font size in Settings, it’ll scale with that.

Interface Builder makes it fairly simple too:

Interface Builder
Choose a style from the font menu in Interface Builder

But there’s a catch. If a user launches your app, then changes the font size, your app won’t display any new changes until it has quit and is relaunched. You can watch for the UIContentSizeCategoryDidChangeNotification notification though and then re-render your text based views then1. So, regardless of your choice you’ll have to code out a lot of labels, buttons, and text fields. In a normal app, that’s hundreds of lines of boring code.

The other big downside is that you’re limited to the system font. So, San Francisco on iOS 9 and Helvetica Neue on older versions. I like those fonts just fine, but sometimes you need to switch it up.

BNRDynamicTextManager

I first came across Big Nerd Ranch’s BNRDynamicTextManager library and found it to be pretty useful. You still have to wire up each field (or use subclasses), and you’re still stuck with the system font, but it handled all the notification watching for you. Here’s a sample:

1
2
[[BNRDynamicTypeManager sharedInstance] watchLabel:label
                                         textStyle:UIFontTextStyleBody];

The library was lightweight and easy to understand2, which got me thinking.

Gliphy

I decided to port BNRDynamicTextManager to Swift and see if I could add some functionality to it. Porting it wasn’t a big deal, although I made a couple of changes to better match Swift’s idioms. I then added a couple of nice features on top.

Big Feature 1 — Custom Fonts
Watcher methods can take the name of a custom font:

1
2
3
DynamicTypeManager.sharedInstance.watchLabel(titleLabel,
                                             textStyle: UIFontTextStyleTitle1,
                                             fontName: "MarkerFelt-Thin")

This will apply the font size of the “Headline” style to your label, but keep the glorious MarkerFelt font. If the fontName is not associated with an installed font, it will fall back to the system font.

Big Feature 2 — Custom Styles
iOS 7 brought us six text styles, and iOS 9 brought four more3. They cover a decent range of use cases, but sometimes you still want your own. You can do that with the DynamicFontRegistry class:

1
2
3
4
// This could be in AppDelegate, some custom Theme class, or wherever
DynamicFontRegistry.registry.addTextStyle("UIFontTextStyleReallyReallyBigTitle",
                                          scaledFrom: UIFontTextStyleHeadline,
                                          byFactor: 4)

In order to keep with the feel of how Dynamic Type works, you pick a style to serve as a base reference for the new one and then scale it from there. That way you don’t hardcode a point size. You can then watch it the same way you would before:

1
2
3
DynamicTypeManager.sharedInstance.watchLabel(titleLabel,
                                             textStyle: "UIFontTextStyleReallyReallyBigTitle"", 
                                             fontName: "MarkerFelt-Thin")

Big Feature 3 — Global Styles
I liked how it was working now, but it was still a long process to cover all the text based views that could be in an app. I wanted a solution that would make it super easy to handle the 90% scenario. Enter the StyleWatcher and StyleConfig constructs. The StyleWatcher will recursively examine each subview in a parent view and watch them with the DynamicTypeManager if they have a text style associated with them. That way you can set up the text-styles solely UI within Interface Builder and remove an entire swath of code. Additionally, you can set a global style that the watcher will use to find your custom font (or style).

1
2
3
4
5
// AppDelegate
// Setting up the global styles
StyleWatcher.defaultConfig.label[UIFontTextStyleTitle1] = "MarkerFelt-Thin"
StyleWatcher.defaultConfig.label[UIFontTextStyleTitle1] = "MarkerFelt-Thin"
StyleWatcher.defaultConfig.textField[UIFontTextStyleBody] = "Avenir-Light"

Once that has been set up, tell the watcher to watch a view:

1
2
3
4
5
6
7
// In some View Controller
let watcher = StyleWatcher()

override func viewDidLoad() {
    super.viewDidLoad()
    watcher.watchViews(inView: view)
}

And the magic will happen.

You can customize that process too. The watchViews method can take its own StyleConfig as well, allowing you to define multiple different styles and applying them where necessary.

I’m pretty happy with the way Gliphy turned out. The code is pretty straightforward too; no mind bending swiftisms required. There are some downsides though. Attributed strings aren’t handled at all, and due to their nature, they probably will never be. Gliphy only watches four kinds of views: labels, buttons, textfields, and textviews, (plus their subclasses) and is a little difficult to extend beyond that.

All said though, it should make supporting Dynamic Type something more than an afterthought in your apps.

  1. There is a bug in the iOS simulator and Xcode 7.3. You can change the font size, but the notification will not fire. It still works on a device though.

  2. I raise my glass to jgallagher for that.

  3. Here they are:

    • UIFontTextStyleHeadline
    • UIFontTextStyleSubheadline
    • UIFontTextStyleBody
    • UIFontTextStyleFootnote
    • UIFontTextStyleCaption1
    • UIFontTextStyleCaption2
    • UIFontTextStyleTitle1
    • UIFontTextStyleTitle2
    • UIFontTextStyleTitle3
    • UIFontTextStyleCallout

What I like about Swift is that it feels like a natural progression when learning more of the language’s features. Case in point: I’ve known about the where keyword for a while, and have used it plenty of times before, but just found this little ditty:

1
2
3
if let someBool = someThing?.someBool where someBool {
  // do stuff if someBool is true
}

That will only execute the body of the statement if someThing is not nil and someBool is true. Before, I had thought that would’ve been two separate statements, and extra indenting. Kinda cool.

Here’s a quickie. In Swift, the default accessor for strcutures is internal, meaning that it is available to everything else within the module. You can change this by adding private or public in front of them like so:

1
2
private class Foo {
}

private means that the construct is only available within the same file. I just “discovered”1 that you can also mark an Extension as private, which I hadn’t considered before. This let’s you add functionality to a struct or class that may be useful in the current context of everything within that same file, but doesn’t make sense elsewhere.

For example, in a HealthKit project you may be only dealing with fluid ounces for an HKQuantitySample and converting all the time is a bit of a pain. You don’t necessarily want to make a quantityInOunces computed variable available on the whole app, but in a specific file where you’re computed all those ounces, it makes sense.

1
2
3
4
5
6
7
8
9
private extension HKQuantitySample {
  var quantityInOunces: Double? { /* blah */ }
}

class HealthKitManager {
  func doStuff() {
      // Calculate a bunch of ounces easy peasy.
  }
}
  1. Couldn’t think of a better word, but yeah I’m regular Vasco de Gama.

A new Apple device! I’m required to have opinions. Here they are in a contrived fashion!

The Good

Great UI and aesthetics.

It’s faster.

Scrubbing through video is significantly improved. I almost don’t believe how much better it is.

Siri works as advertised. Accuracy, speed, and results are all great.

All the potential!

The Bad

Siri doesn’t index content stored in iTunes libraries on the network. So to get to all the DVDs and Blu-rays I’ve ripped I have to use the same UI as the old Apple TV.1

Siri doesn’t work with Music. Not for music libraries on the network, iTunes Match, or Apple Music. I really don’t understand this one, I’m hoping it comes soon.

Only one remote can be paired with it. This makes gaming tricky. Hoping this changes soon too.

The Ugly

Passwords. This has already been discussed at length everywhere. One thought I had would be that it’d be cool to enter passwords via Siri. Y’know, like that one scene in TNG:

(A Touch ID powered remote would probably be better)

App discoverability. It’s really bad, and I don’t know how they’re going to fix it. Top Charts was just added, which is better than nothing, but if someone wants to market their app anywhere online they have no way to provide a link to it! “Go to the app store, search for s-o-m-e-t-h-i…” is all kinds of awful.

Overall I like it. I have almost no reason to use the Roku now2.

  1. I do not have Plex, so I don’t know if that circumvents it. If it does, I may grab it asap.

  2. Other than the rare times there’s something on Amazon Video that isn’t anywhere else.

Getting the last photo taken is a convenient feature to have in your app when dealing with photo picking. Prior to iOS 8, you could get it by using the Assets Library Framework and then looping through the various groups to get the right photo.

In iOS 8, the Asset Library is still available, but Apple introduced a new framework to go along with Photos.app. It’s creatively called the Photos Framework and it makes certain things, such as querying for the recent image much easier. To further entice you to use it, the Assets Library is deprecated in iOS 9.

Here’s a function that’ll get the most recent photo with the Photos Framework:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import UIKit
import Photos

// I like to typealias my blocks, makes for easier reading
typealias ImageCallback = (UIImage? -> Void)

func fetchLastPhoto(resizeTo size: CGSize?, imageCallback: ImageCallback) {
    let fetchOptions = PHFetchOptions()
    fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]

//        fetchOptions.fetchLimit = 1 // Available in iOS 9

    if let fetchResult = PHAsset.fetchAssetsWithMediaType(.Image, options: fetchOptions) {
        if let asset = fetchResult.firstObject as? PHAsset {
            let manager = PHImageManager.defaultManager()
            let targetSize = size == nil ? CGSize(width: asset.pixelWidth, height: asset.pixelHeight) : size!
            manager.requestImageForAsset(asset,
                targetSize: targetSize,
                contentMode: .AspectFit,
                options: nil,
                resultHandler: { image, info in
                imageCallback(image)
            })
        } else {
            imageCallback(nil)
        }
    }
}

There’s a fair amount going on in there. First, we create a PHFetchOptions object which we can use to pass in additional information to filter the query. In this case we only need to sort by creationDate descending. iOS 9 introduces fetchLimit which could reduce a bit of overhead since we know we only need 1 image.

PHFetchOptions also has a predicate property that has a bunch of interesting capabilities, such as restricting the search to specific mediaSubtypes like PhotoHDR or PhotoPanorama. If you want all photos just ignore it; that’s determined later. Do note that “Photos does not support predicates created with the predicateWithBlock: method.”.

Once we have our options we are ready to query. The Photos Framework gives you access to 3 types of things that are stored in Photos.app: PHAsset, PHAssetCollection, and PHCollectionList. We only care about PHAsset right now. A PHAsset is a representation of the media stored on the device (photo or video). The other two are ways to group those assets in general.

Querying happens through class-level methods on the PHAsset class. There are a few to choose from, but we want fetchAssetsWithMediaType:options:. This returns a PHFetchResult which is kinda like an NSArray, but not exactly. In Swift it’s filled with optional AnyObjects. The first one should be the most recently created file so we cast it to a PHAsset.

Now comes the time to convert that PHAsset into what we really want: a UIImage. This is the responsibility of PHImageManager.

Most of the time when querying for images, we just want to resize the photo to fit into a UIImageView so Photos does most of the heavy lifting for you. Give it the size you want and a couple other options and off it will go. This happens asynchronously, so hand off the results to your callback block when it’s done. This’d be especially handy for generating a bunch of thumbnails.

Photos gives you quite a bit of flexability. We didn’t touch on how it helps you to edit assets, manage/query video, or observe changes to photos. I wasn’t aware of this framework until today, but it looks like a powerful one worth getting to know if you do anything with the images on your device.

Great Customer Service is not dead! When I was doing freelance/independent work I used Harvest’s Solo plan to track my time and invoice clients. It was affordable for a single person and wasn’t over-engineered. Since it was cheaper in the long run I paid for a yearly plan and renewed each time it came up. I would’ve recommended them to anyone in a similar position. Now I’ll recommend them even more heavily.

A few months ago I took a full-time position and just didn’t need the paid account any more. I couldn’t figure out how to downgrade though so I sent a request to their customer service. The page told me to expect a response within an hour. Within 5 minutes I got a response from Jennifer telling me that my plan had been downgraded to the free one, AND they refunded the prorated amount for the remainder of the year back to me.

Getting a refund wasn’t even on my radar. I was perfectly happy with keeping the paid plan and just not renewing it when the time came. What I love about this is that Harvest didn’t waffle around the issue for a long time only to end up doing the bare minimum to get the customer to go away. They were fast, proactive, and exceeded expectations.

That’s how you do customer service.

Copyright © 2016 - Scott Williams - Powered by Octopress