I didn’t get a golden ticket to WWDC. It’s par for the course for most folks I guess, but I’m still disappointed. I’m still going to San Jose from June 2-6 and can’t wait.

I did submit a talk for AltConf and I’m hoping it gets accepted. 🤞

I finally got a shortcut set up the way I want so that I can post to my site from iOS easily. A new update to iA Writer added the ability to browse a textbundle as a folder which is exactly what I need to get going.

This summer I hope to make lightweight editors for iOS and Mac (the Mac using Marzipan if it’s good enough).

You can find the shortcut here

Scorebook Sync Log 10: Shipped!

Scorebook v1.6 shipped a little over a week ago, and it finally includes all my sync code. When I cracked the project back open late last year I decided to turn off the engine that I'd put together just to get the app back in shippable condition. But I also knew that I wanted to ship the sync shortly thereafter. Well, it took about 6 weeks for me to be comfortable enough to get it out the door.

Honestly it's a little embarrassing how long it took to get this feature out. I started writing about it over 4 years ago (if you're interested in reading through the journey, they are curated here). I thought I was going to get the feature shipped by WWDC 2015. Welp. It's out there now, and that's the important thing.

Back when I was building the feature I knew I wanted it to be as seamless as possible. A modal on startup asking the user if they want to turn it on, and then a simple switch in the Setting screen. Everything else should be handle-able by my sync code. And because their data lives in iCloud I don't have to worry myself about encrypting or having any access to user data. Mission accomplished on both fronts.

As I revisited the feature when getting back into the app there were a few deprecations that had come in since iOS 8 but the code I wrote back then was still good. I did it all in Objective-C so there weren't any crazy language changes to migrate through (thank God I didn't adopt Swift 1!).

Now I get to look forward to building truly new features. I know I want to tackle dynamic type and accessibility, and might do that next. Then come the summer I want to rethink what the gameplay setup screen looks like as well as the scorecard screen. I know the whole UI could use some updating since it's very much from the iOS 7 era of design. But for most of that I'll hold off to see what Apple has in store for us at WWDC.

Check out Scorebook 1.6 in the App Store!

Hello Dark Mode

One of the projects that's been on my mind since WWDC last year when the Mac got dark mode was adding support for it to my website. I know that Safari doesn't support it directly yet (though it does in the Technology Previews) but it will soon, and the rumors are hot and heavy that iOS 13 will have it as well. I wanted to be ready, and break outside of my normal comfort zone for a bit.

Things are getting dark around here

I'm no CSS wizard by any means, and I found a very helpful Iconfactory post detailing how they added support to their site that served as my guide. I took the time to refactor some CSS into helpful variables (a few were already in place from the Ghost theme I used to use), and it wasn't too much work to get the initial pieces in place.

The bigger work came with syntax highlighting. I use prismjs for my syntax highlighting, and it consists of 2 components: some Javascript and CSS. The JS file didn't need any changes but my default CSS uses their light theme. Thankfully CSS has handy conditional @import rules that let me use a media query. So I downloaded a separate CSS package for a dark them and applied it and all was well. This also allowed me to move the code highlighter CSS import to my main CSS file, meaning each page on the site loads exactly 1 CSS file. I count that a win in my book for maintenance.

The last thing was figuring out table styling, since I used one in my post about iOS push notifications, and that wasn't too bad either. If you want to check out what I did, the commit is right here.

If you'd like to check out what the site looks like in dark mode, download the Safari Technology Preview and flip your Mac running Mojave into dark mode.

Photo Project: Daily Batmobile

A big part of the creative process is showing up every day, and that's one place I've fallen short many times over the years. It's super easy to start something, but seeing it through is another. This year I wanted to do a creative project that would challenge me to show up every day, and I found one.

When I got my new camera a while ago I read a blog post that gave a simple piece of advice to help shoot more: take a trinket with you. The idea is to photograph it in many different settings, and being mindful of wanting to take more pictures encourages you to learn your camera and think about how you can frame the next shot. My wonderful wife got me a Hot Wheels Batmobile for me to carry around, but I didn't do much with it at the time.

I decided this year to take on the creative project of shooting a photo a day with my Batmobile and I'm pleased to report that I've shown up every day this year. Some are better than others (which is to be expected!) and a couple have made me very happy with how they've come out.

Honestly I've gone back and forth deciding whether or not I want to share them. On the one hand, it's a fun project and I'm really enjoying it. On the other hand, I know my skills aren't fantastic and it opens me up to critique (and knowing the internet, possibly some sharp barbs along the way). But I've decided to go for it and share them because I want to get better and I want to hold myself accountable this year to deliver a photo every day.

I'll be posting my photos on an iCloud shared album here. I would like a slightly better web presentation but this method is easy for me to publish from my Mac or iOS devices so that's the way I decided to go. I'm anxious to hear any feedback that you might have.

I never thought that I'd become "sharp edges guy" but the last couple of days at work I've run into doozies.

  1. The AVCaptureMetadataOutput.rectOfInterest rectangle has to be transformed into the coordinate space of the output itself - meaning that you can't give it a CGRect from your view and have it work. Instead it must be converted to the coordinate space using AVCaptureVideoPreviewLayer.metadataOutputRectConverted(fromLayerRect:).
  2. When adding a custom view to a UIBarButtonItem you need to have that view handle the tap on the item itself. Assigning a UIImageView, for example, negates the bar button item's target and action properties for some reason. Of course it's not documented.

I wonder what I'll run into tomorrow 🙂

A while back I got rid of Google Analytics for this website and Taphouse's too. I also went and downloaded fonts that I could embed in the site to get rid of TypeKit. I thought that was it, but it turns out my monospace font was downloaded too. Thanks to IBM Plex that's no longer the case.

Goodbye anything that can track you!

Automation Revelation

When I relaunched my website last summer using my homegrown blog engine (Maverick) I adopted the textbundle format for writing posts and I was using Ulysses for my writing. But after launching the site and using Ulysses for long enough I was running into some friction with their Markdown syntax. That led me to the decision to not renew my subscription when the time came.

I'd since gotten a BBEdit license after hearing tons of people I highly respect speak glowingly about it and decided to use it for writing my posts. It doesn't support textbundle natively like Ulysses does, but it does well enough for my needs. textbundles are in fact just a folder with a Markdown file inside with an optional assets directory. So I've been using BBEdit for writing on my Mac ever since.

But I've been running into a different kind of friction: making new posts was kind of painful. I've been manually making the directory structure, copying the info.json file, and creating the text file myself. I'd written an Alfred snippet to create the front matter that each post needs, but that's become problematic too because dates are hard (more on that in a minute).

Well, we've gotten deluged with snow in my neck of the woods so I figured I'd dive in to a couple of new tools and get this thing automated. I watched through the NSScreencast episodes with John Sundell explaining his Marathon scripting tool written in Swift, for Swift. I was able to take that knowledge and make a little script which outputs the directory structure I need, fills the front matter on the post, and launches me into BBEdit where I can write away.

Part of the spark for this was that I'd approached dates all wrong when I built Maverick. I used dates in my current time, not against UTC. I have posts automatically show up on my microblog using micro.blog's cross posting functionality and when my posts came over they were off by 8 hours or so (I'm in the Pacific time zone). So I had to go through all my posts and update times, then use dates that are UTC based (formatting them using an ISO8601DateFormatter and .withInternetDateTime).

I should be all set now, and my posts showing up in the correct order on my microblog. Happy Saturday!

Update

Here's the script that I wrote, in case you're interested 🙂

CALayer Mask Inversion

I've got a view controller with a camera view, overlaid with a partially transparent dimming view. I need to punch a hole out of the dimming view to let the camera shine through in its full glory. My first crack was the code below, and it works:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    let inset: CGFloat = 16.0
    let rectWidth = self.view.frame.width - (inset * 2)
    let rectHeight = rectWidth * 0.63
    let rectSize = CGSize(width: rectWidth, height: rectHeight)

    let rectOriginY = (self.view.frame.height / 2) - (rectHeight / 2)
    let rectOriginX = inset
    let rectOrigin = CGPoint(x: rectOriginX, y: rectOriginY)

    let maskRect = CGRect(origin: rectOrigin, size: rectSize)

    let maskLayer = CAShapeLayer()
    let path = CGMutablePath()
    path.addRect(self.dimmingView.frame)
    path.addRect(maskRect)
    maskLayer.path = path
    maskLayer.fillRule = .evenOdd

    self.dimmingView.layer.mask = maskLayer
}

Which produces what I want:

But it was redrawing too often (during transitions especially). I decided to have the mask only draw once, and I needed the view hierarchy set up. So I put it in viewWillAppear. Exact same body as above just different lifecycle method. Here's that result:

How in the world can the same code produce such different results?

Update

Thanks to Tom Bunch for asking me about the state of our view controller's superLayer. At viewWillAppear, it has no super layer but at viewDidLayoutSubviews it has one. The class of the layer is a UIWindowLayer (which is a private class). Turns out that the super layer must apply some transform that causes our inversion to happen.

To get around the problem of the too many redraws I have instead opted for a simple hasAppliedCutout boolean state check during viewDidLayoutSubviews. It's not the cleanest solution but it will work for what I need without too much extra fuss.

On the Sharp Edges of UIView.mask

I'm working on implementing a new user-facing feature at work, and the designs I've been given call for having a dark gray semi-transparent view overlaying a view, and this semi-transparent view has a rectangle punched out of it. Not having done this kind of thing before I went searching the docs on CALayer to see what options are available. I knew that layers could mask other layers so this seemed like the right place to go.

While using a layer-based mask seemed promising initially, I wanted flexibility to move this mask around pretty easily using Auto Layout constraints. I stumbled upon a really helpful article from Paul Hudson outlining the UIView property called mask. From the docs, this property is:

An optional view whose alpha channel is used to mask a view’s content.

That's the entire description we're given. I ran the code from Paul's blog post in a playground and sure enough I was able to mask the view (more on this later). So I adopted it in our app, and I ran it. I looked and there was no mask. In fact, the entire overlay was not visible. Huh? Why would this work in a playground but not the app?

Turns out there are a couple of undocumented gotchas that go along with using a mask, and if you don't know them you could spend a lot of time learning the hard way. I'm hoping this will spare you some lost hours. Here we go.

  1. The mask view cannot be part of a view hierarchy. This means no Auto Layout, and no constraint manipulation. It's all frames with this view.
  2. The mask cannot be held on to by anything except for the view it's masking. Don't try to store it as a property or you're in for a bag of hurt (as in it won't work at all, and the view it's masking will no longer be visible).

I suppose that if #2 were to be solved it could make #1 possible, but the reality is that if this property is going to have as many usability issues as it does then the documentation needs to be updated to reflect that. If you've been in the Apple developer sphere for very long you've heard the phrase "Radar or GTFO". With that in mind, I submitted rdar://47809462 (OpenRadar link) as I do not want to GTFO 🙂.