“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
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.

I’ve been noodling with an idea for a watchOS 2 app. It involves Connectivity – transferring files from the Watch to its paired iOS device. The WWDC sessions are very good, but I ran into a couple of snags with the framework. I had tweeted something that I thought was a bug, but on closer inspection of the docs it actually wasn’t. I tweeted another message stating as much, but it hasn’t gotten the notice that the original did.1 Anyways, here’s what I learned.

[ed: This is with iOS 9, watchOS 2, and Xcode 7, all in beta 2. Stuff might change.]

You can run both simulators at once

But it’s not obvious. First, run the watchOS simulator. In the scheme control, it’s the “[App Name] WatchKit App” scheme, which has both the iPhone and Apple Watch available (for example, “iPhone 6 + Apple Watch – 38mm”), and run it.

Xcode Scheme for iPhone6 and Watch

When it’s running, change the scheme (without stopping the simulator) to the normal iPhone scheme. In this case “[App Name] > iPhone 6”.

Xcode Scheme for iPhone6 and Watch

Now, run this scheme without compiling: hold down ⌘ and click the Run button. The iOS app will now be running in the simulator. Sometimes the watch app will close, but you can open it back up again.

The console and breakpoints may get a little squirrelly, so you probably won’t be able to depend on them so well.

WCSessionDelegate

Connectivity has one main delegate to implement: WCSessionDelegate. This can be implemented on both the iOS and watchOS apps, and different behavior will happen. For example, when you transfer a file, the receiver will need to implement session:didReceiveFile:. If you want to receive some kind of notification that the file was sent the sender needs to implement session:didFinishFileTransfer:error:.

This seemed counter-intuitive to me at first, but on further reflection, it makes sense. I do wish the docs more clearly illustrated this though.

You’re in the background

Don’t forget that your iOS app is not guaranteed to be running when these notifications come in. And they will come in on a background queue too, so don’t immediately try to update the UI without getting over to the main queue first.

Demo

I created a simple demo app to illustrate all of this. It’s up on GitHub. Take a look at the AppDelegate for the iOS app, the ExtensionDelegate and InterfaceController in the watchOS extension target. This demo was thrown together quickly without regard for best practices, blah blah blah.

  1. Such is the nature of Twitter, alas.

Copyright © 2016 - Scott Williams - Powered by Octopress