iOS 13, Unit Tests, and Mocking a SceneDelegate

I've been working through the exercises in the excellent iOS Unit Testing by Example book by Jon Reid, which I highly recommend. However, the book is in beta at the moment and there are some curveballs thrown by iOS 13 that aren't handled in the text yet. Specifically, when I hit the section about using a testing AppDelegate class I thought "This is very good. But what about the SceneDelegate?"

That kicked off quite the odyssey to be honest. If this is interesting to you I wrote up a Gist documenting what I found, and a partial solution to the issue.

WatchKit UI Colors

When I was developing the first version of the watch app for Today Timer I noticed something a bit odd. For a running or paused timer I was setting the background color of the table row for that timer. The docs say that that if you set the background color of a WKInterfaceGroup to nil the group uses the default background color of clear. Which I think is true, but the table sets the row background color to a what looks like a really dark gray. After some digging around I came up with a file named Guide_ButtonTable_Rows_Colors.psd that you can download from the Watch Human Interface Guidelines (look under Resources->Guides).

Turns out the WKInterfaceGroup provided for a table row is configured as a "platter" so it has this platter color, which is actually a pretty bright white at a very low alpha. (RGB 242, 244, 252 at 14% opacity.) Since I was overwriting the background color when I wanted to "clear" that change I needed to set back this custom color in order to make everything look right.

I've used a category on UIColor that provides the colors used in iOS7 style UI for a while, here's the original repository and here's my fork which makes the code compile with -Wno-semicolon-before-method-body and adds the default tint color. So I wanted something similar, but I didn't think the WatchKit colors belongs in iOS7Colors. I just pushed a UIColor Swift extension to GitHub. Although it's written in Swift you can drop it in an Objective-C project and use it. There's a sample app in the repository that shows the colors and how to use it.

If you're coding WatchKit hopefully you'll find this useful!

Hearthstone! For the Horde!

I should write a bit about Hearthstone. I signed up last year when it launched in open beta but sometime after the iPad version hit it sunk hooks in me but good. I suspect it was probably the videogame I spent the most time with in 2014 and while Destiny made a bid for competing in 2015 I can see where my Destiny time is already beginning to taper off a bit while I still play Hearthstone at least four or five nights a week. Yesterday I decided not to update my bounties in Destiny but I made sure to log in and kill at least one Hearthstone quest so I could get a new one today.

On the one hand, I'm not surprised at Hearthstone grabbing me: it pushes enough of the Magic: The Gathering buttons and the digital version isn't … look I'm trying to be nicer in my public persona in my middle age so let's just say when I quit playing Magic Online I was confident that it wasn't me it was them. It's been like a decade and they still don't have the mode (League) I liked the most back in MTGO. But a decent OS X client would probably get me playing again, I just don't think that will happen. So yeah, there are known hooks into my attention that have lain fallow lo these many years.

On the other hand, an online-only, adversarial game? Not my bag baby. Never has been. I have some friends who claim to play (eyes Bwana) but I don't have any friends who ever actually log into the thing. I was really surprised to realize that the crux of why I like Hearthstone was this simple fact: you can't chat with your opponent. There are six emotes you can say and they are basic things: Greetings, Well Played, Oops, Sorry and so forth. That's it. And if somebody is being rude/obnoxious/annoying with that limited palette you can squelch 'em in two taps. It's weird how liberating that is. And if somebody plays well, and accepts the outcome of the game with good grace you can friend them later and THEN have text chat. I've had a couple of times where I exchanged a bit of dialog post-match with my opponent and they have been really satisfactory interactions. I assume the racist, homophobic tweens who dominate Xbox Live don't make it through the "play an entire game without being an ass-hat" filter because I've never sent nor accepted a friend request in Hearthstone and then not liked whatever interaction has occurred afterward.

In the last year there have been two expansions to Hearthstone: the "adventure" that was Curse of Naxxramas in late July through August, and the first expansion of Goblins vs. Gnomes in December. Naxxramas in particular because it was interesting: a single-player expansion that rolled out a bit at a time. For five weeks they put a new "wing" of Naxxramas up and each wing had a few AI's with special decks and powers. As you beat the encounters you would unlock cards for your collection, usually a card that you had just played against. So as an "expansion" it was small: I think it was 30 cards spread over a five week window but as the cards were revealed and dropped in the metagame would change. Naxxramas was a really fun time to play the game. I don't have as much to say about GvG, which is more of a traditional expansion, something like 120 cards. I like it fine and I think it did a great job of shaking up the game.

So yeah, Hearthstone. If you play it drop me a line with your BattleNet ID and I'll add you to my friends list. The game itself is free and in fact you play quite a bit without buying anything meaningful. I've bought some cards but not a ton and I get most of my cards through playing the game and earning in-game gold. So if you think you are at all interested I'd say grab the client and give it a whirl!

On Goodreads and Books

I decided to join Goodreads in 2015, in part to see if I can get some good book recommendations from it, and in part because I really like to talk about books with folks so it seems like a network I'd like. That's my belated explanation why the Goodreads lists popped up in the sidebar recently.

Overall I'm still figuring out Goodreads. It is super-aggressive about wanting my Address Book which isn't going to happen. My rule is this: I'd be mad if a friend gave my email address to any social network so I shouldn't give out my friends email addresses. I think it's settled down a bit now, but the first few times I logged it that was like a "Here's the super-important next step!" and I definitely had to click "No" multiple times.

While rating a few books I discovered the latest Discworld novel, Raising Steam came out last November which I did not know. I'm reading that now so we can definitely put that down in the win column for Goodreads-as-recommendation-engine.

Anyway, as I said I didn't give up my contacts so if you're reading this and you have a Goodreads account ping me and I'll look you up. With only a tiny bit of judging on your reviews and ratings ;-)

UILocalNotification & Do Not Disturb Settings

File this under "Totally Obvious, no doy."

The app I'm working on these days has user-specified timers. I've been dogfooding it now for a couple of months and periodically I would get these things where I'd stop getting the audio timer notifications and I'd burn some cookies or a loaf of bread or whatever. Every time I'd dutifully mail myself the log file and the next morning I would A ) not be able to reproduce the problem and B ) not see anything wrong in the log file. Then one day I finally figured out the common factor: I had turned on "Do Not Disturb" on my phone at 10 PM. I have no idea why, it's not like I ever go to sleep at 10 PM. While iOS's Clock app can make alert sounds during Do Not Disturb my app cannot.

My immediate problem was easy: I pushed Do Not Disturb until midnight and if I need a timer after midnight I need to use Apple's Clock app. But I have a bigger problem with the app: how do I educate the user about this issue and/or alert them when it is a problem? Well, the thing is the timer mostly runs via a scheduled UILocalNotification. If you're running TodayTimer in the foreground then I'm running timers directly in order to update the UI. But as soon as TodayTimer enters the background I schedule a UILocalNotification to go off when the timer finishes. If I could see that Do Not Disturb will be active when the timer completes then I could alert the user not to rely on the timer. This wouldn't be perfect in two cases: when the user manually turn on DnD or adjusts the scheduled DnD window; or when the device travels into a new timezone (because DnD works in local time-zones but the UILocalNotification is scheduled against GMT. I might check the time, decide it was safely outside the DnD window, but the phone could travel into the DnD window before the timer expired.) Still, both of those were going to be super-rare.

This is a great theory, but the problem is the OS doesn't expose the DnD settings. So I filed a bug report (Open Radar), as a good iOS ecosystem citizen. I can understand why they aren't letting my app override DnD, I don't want Clash of Clans having the ability to play noises because I haven't clicked the cow in the last hour or whatever. But I don't see any issue with letting me know when DnD is active. I guess I can see a concern that app developers would schedule around DnD then. I'd settle for the call that schedules the notification returning an error message letting me know that my sound isn't going to happen.

Anyway, something you want to know if you have time-critical notifications with sounds. Both the sound and the vibration are silently failed by DnD and I don't see an Apple-approved way of handling the situation. I'm going to have to write some terrible end-user text that nobody will read and then just put up the with complaints & bad reviews …