I got to play a few songs as a Conditional Breakpoint! It was a total thrill for me. Check out the Breakpoints album on iTunes if you haven’t already.
Sometimes days are like this:
Our testers — who are great — report a regression I caused but that I didn’t notice. Why is sub-pixel anti-aliasing not working in this view? The text looks bad. (Note to self: test with a non-retina display before comitting.)
I investigate, and find, to nobody’s surprise, that the culprit is a layer-backed ancestor scroll view. I turn off layer-backing — I just uncheck that particular box.
Then run the app. It’s good to see sub-pixel anti-aliasing back. But look at how things have gone wonky.
Wonky? Let’s be precise: the layout inside outline view cells is incorrect. If I check the box (turn layer-backing back on), then layout is fixed. Uncheck it, and layout is broken.
(This all sounds straightforward, but I’m glossing over a bunch of things I tried and researched. The above is a couple hours of work.)
The wonky view uses auto layout, but it’s not entirely constraint-driven: there’s a manual
-layout method. When an ancestor is layer-backed, then that method is called at the appropriate times. When it’s not, then that method isn’t called often enough, or not at all the right times.
Or… but it also looks like something else might be moving those views, giving them a zero origin when it shouldn’t be. Exactly as if it’s doing constraint-based layout and not calling the
-layout method. Sometimes.
I click around a while, add NSLogs and breakpoints and all the usual things. I can’t figure out what’s happening. I bring in
resizeSubviewsWithOldSize: (the old friend). I accidentally introduce an infinite loop at one point.
To reassure myself, I check that checkbox again (turning on layer-backing) — and layout works again as expected.
Time to get pragmatic.
If it seems as if those views are sometimes getting laid-out by their constraints, and
-layout isn’t getting called, then maybe that’s really what’s happening. So one possible solution is to give all the views their necessary constraints, so that
-layout isn’t needed at all.
I tried it. Success! (I think. At least so far.)
And that was a day of work. In the end I don’t know why there was a problem, but I have a solution. I should probably file a Radar, but it seems like exactly the kind of thing that will be hard to duplicate in a simple case (though I could be wrong).
There’s nothing wrong with days like that. In the end the thing is solvable. What worries me is when I have too many days like that strung together, when I have too many Radars to file.
I’m still the newest person here. Make that not true anymore.
For one of my projects I’m working with NSVisualEffectView and behind-window blending.
I’ve found a few things that could help people doing the same thing:
Reminder: don’t forget that its subview should respond YES to
If it’s in an NSSplitView, make sure the NSSplitView is not layer-backed. rdar://18585148
Make sure the effect view’s superview is not the first layer-backed view. rdar://18587102
There may be other gotchas, of course, but these are what I’ve found so far.
On Twitter, Martin Johnson asks:
How long until a crit mass of users get burnt one to many times and simply avoid indie products altogether?
I don’t think people outside our industry use the word “indie” to describe any software developers. They may think “probably small” or “never heard of them” or “not Apple or Google or Microsoft” when they think at all about a company.
I don’t think that product failure is a problem specific to indies. VC-backed apps and companies fail all the time. Large services fail too — you can’t dial in to America OnLine any more. Google has turned off so many services that there’s a Google Graveyard. Apple has retired lots of things too.
The beauty of indie software is that many apps don’t make financial sense for a larger company, but they make great sense for a small shop. So you can have sustainable apps such as Capo, Acorn, and MarsEdit that you wouldn’t get without indies. And you can also be sure those apps won’t get shut down on some manager’s whim.
Going with indie software can be safer than the alternatives.
But relying on any software or service, from anybody, is a risk. Always. The best way to minimize that risk is to pay money, because it’s money that keeps things going.
Justin Williams on Glassboard closing at the end of this month:
Over the last year we have tried a variety of different methods of converting Glassboard into a sustainable business. The reality is that we failed to do that.
I have some idea of what he went through — before it was Justin’s, Glassboard was Sepia Labs’s product. That was a team of six, including me, so I know the challenges of turning Glassboard into a business. We at Sepia Labs couldn’t do it, but we all hoped Justin would be able to.
Glassboard, or something just like it, is so needed. An app where you can communicate with persistent groups of people, where you control who’s in a group — and do so privately (for real) and with no ads — seems like it should be a basic service. Something everybody would use.
But since it’s social it needs to be free to get people on board, because it’s not useful without people.
But then how do you make enough money? I don’t know.
It’s easy to look at this as part of the indie-life-is-tough story. And certainly conditions are tougher right now than I’ve ever seen. But an app like Glassboard — with the social network app’s dilemma — is going to be a challenge no matter what.
* * *
I’m proud of the work we at Sepia Labs did, and I’m proud of Justin for all his great work and all the energy he put into this. I thought he had a real shot at turning this into a business, but I suspect I underestimated the difficulty.
At any rate: I want to say thank you to my co-workers at Sepia Labs — Walker, Brian, Jenny, Nick Harris, and Nick Bradbury — and also to Justin for taking this on and working so hard at it, and to all the people who used Glassboard. Thank you so much.
You can download the test case.
Launch using the iPhone 6+ simulator.
Rotate to landscape.
Tap the Show Popover button.
Rotate to portrait.
Note the message in the console, which will be something like: “Unbalanced calls to begin/end appearance transitions for <UITableViewController: 0x7fbb02346170>.”
As bugs go, a message isn’t a serious thing. But it could be indicative of a more serious bug, and so it’s worth reporting.
* * *
There’s very little code in the test case. The action is in TableViewController.m, which presents a view controller as a popover with an adaptive presentation style (full-screen).
I have a folder at
~/Projects/Bugs/ where I create test cases that get attached to Radars.
It’s a good practice. Assume you’ll have to do this sometimes, and assume you’ll want to keep those test cases. (You’ll want to keep them so you have an easy way to find out if they’ve been fixed in later releases.)
Sometimes in the process of creating a test case I find out it’s my bug. Sometimes. Either way, it’s a good exercise, because at least I find out one way or another.
(I’m procrastinating writing a test case right now. Hence the blog post.)
In the old days, programmers spent hours and days and weeks working very hard, and sometimes brilliantly, on difficult things that no one had ever done before.
These days, programmers spend hours and days and weeks working very hard, and usually unsatisfactorily, on getting around bugs in their platform.
Most SDKs are just capturing and streaming data — and what’s being collected and streamed is usually redundant.
Furthermore, the challenges of the app business extend beyond just sending data to an analytics partner or two. You also need to invest in user acquisition and in tools such as push notification and email to keep users engaged (which also require SDKs).
With mParticle, app developers can solve both sides of the equation using a single, lightweight SDK. App developers can not only stream data to any service such as analytics, push, crash, and so on, they can also segment and send data to Facebook, Twitter, Mailchimp, and similar to increase engagement and retention.
- Less time integrating. More time innovating.
- Eliminate SDK updates and code changes required.
- Reduce costs paid to reporting services through data filters.
- Improve app stability, security, and user privacy.
Sign up free today. No credit card required.
1995 was the year the internet went mainstream. That’s the year when it seemed like everybody got email accounts and web browsers.
It was funny watching TV back then, because the announcers always said "aitch tea tea pea colon forward-slash forward-slash double-you double-you double-you period…” (These days you just get a hashtag. One day we’ll look back on that and laugh.)
In 1995 I was 27 years old. Nobody had ever tried to scam me (at least, not that I noticed). Here’s the thing: though TV and movies made it seem otherwise, most people didn’t run into actual con men. Cons were rare.
And now, by my estimation, people have tried to scam me 693,500 times via email. (Assume an average of 100 scams a day for the last 19 years. These days it’s closer to 200 a day.)
Grifters used to have to work hard for a living. I miss that.
* * *
The immorality that inflames me is when people prey on vulnerable people.
“Vulnerable” could mean desperate, overly-trusting, poorly educated, forgetful, less-abled physically, less-privileged, less-well-connected, and so on.
And it’s my extreme good fortune to be less vulnerable than most everybody else on the planet in the history of the world.
But I know two things: that position could change on any very bad day, and that position will change in the future. (The problems of aging get to everybody lucky enough to make it that far.)
Knowing that one day I will be more vulnerable to predators than I am now doesn’t make me any more sympathetic to the people who are already more vulnerable than I am. I’m very sympathetic.
But it’s not a bad reminder, either. Sometimes I look at a piece of email and wonder how I’ll figure out if it’s really my bank when I’m 80 years old. And I’m not sure I’ll succeed 100% of the time, and it really needs to be 100% of the time.
Which reminds me that there are people all over having a hell of a time right now. Today.
Cartography is a Swift project on GitHub that gives you the ability to “set up your Auto Layout constraints declaratively and without any stringly typing.”
Dave Winer, Are the silos winning?:
People get all comfy and bored in their little nests, and then boom, everyone goes somewhere else. If it’s yet another boring and nesty silo, only a few people will go there. But if it’s open, eventually, everyone is there.
Just ask America OnLine.
Feedback about blog engines was mixed.
And Santiago Valdarrama suggested I should stick with my current homegrown blog engine.
The Requirements, again
I could rule out almost all of these right away.
Many of these suggestions would just replace what I have today — but with less functionality. My current system at least works with MarsEdit running locally, but many of these don’t. MarsEdit (MetaWeblog API support) is not optional.
I wanted something that runs on a server that allows me to post from an iPhone and iPad when I’m away from home. Ideally there would be a native app with a sharing extension, but I could be flexible on that.
WordPress was the obvious best fit. (Works with MarsEdit and has a native app.)
The Work Involved
WordPress deserves top billing in a hypothetical web app hall of fame. It’s a remarkable achievement, and I have nothing but respect for the good folks at Automattic.
But I started thinking about importing my blog into WordPress and making sure the content and the URLs all remained the same. I thought about editing a theme to get it to match the current look of the site. And it started to seem like work, and not the kind of work that’s much fun (to me).
So I went back to thinking about what I have now. Which is:
- A static blog generation system written in Ruby. It’s fast and bug-free.
- A CGI script that implements the MetaWeblog API, so that the blog generator works with MarsEdit.
What separates this system from the system that I want? Two things: it doesn’t run on a server, and there’s no app or browser-based UI for posting from iOS.
The first one is fairly easily solved: I could put it on a server. The CGI script, perhaps converted to a Sinatra app, would run on some port other than 80, while Nginx serves the static pages that the system generates. I’d also have to deal with Mercurial — the copy of the blog on the server would have to be the parent repository. None of this is difficult, and it feels like less work than moving to WordPress.
That leaves the issue of posting from iOS. I could do a browser-based UI, or — here’s where this starts sounding fun — I could write an iOS app just for me.
So that’s what I’ll do: I’ll write an iOS app with an audience of one.
It will give me the chance to work with some technology where I need practice: auto layout and size classes, adaptive UI, storyboards, Core Data, sharing extensions, and Swift.
And then I’ll be able to post from anywhere, any time, which is exactly what I want.
PS I mentioned that my static blog generator is fast. It takes about 10 seconds on my MacBook Air to generate this entire site, which goes back to 1999. (And I have ideas on speeding that up a bit. The generator has some features that I’ve never used, that I could remove and get a performance boost.)
PPS My friend Jake Savin has been writing about moving his Manila blog to WordPress. Here’s part one and part two. (My blog — this blog, inessential.com — also started out as Manila blog back in 1999. Originally it was just for developing Manila’s static-rendering feature, which I was writing. And then I kept the blog going. Of course, I felt at the time like I was late to blogging.)
I’ve been at Omni four days so far — and it reminds me a little of the first episode of Red Dwarf. (I love that show. Because of Cat.)
Remember how when Dave Lister came out of stasis after a million years and everyone was dead? Holly (ship’s computer) kept answering Lister’s question: “He’s dead. They’re all dead. Everybody’s dead, Dave.”
It’s like that at Omni, except that everything’s nice.
Are the people nice?
They’re all nice, Brent.
Is this chair nice?
The chair is nice. Everything’s nice, Brent.
Is the food nice?
The food’s nice, the chair is nice, everyone is nice. It’s all nice, Brent.
Is the view nice?
The view’s nice. Everything. Is. Nice. Brent.
* * *
I’ve actually committed a little bit of code (to OmniFocus for Mac), but mainly I’m nibbling around the edges as I learn a new-to-me codebase. I haven’t been through an experience like this since learning the Frontier kernel in the late ’90s. (I’m eager to contribute more substantially.)
You can get a taste of Omni’s code by looking at the open source frameworks on GitHub. There’s a ton of stuff there, and Omni’s open source frameworks are one of the many reasons I’ve been a long-time admirer.
Another part of my learning curve is learning the app. I’ve been a happy OmniFocus user (on Mac, iPhone, and iPad) since pretty much the beginning, but I’m not a power user. I pretty much ignored contexts, never made folders, never flagged things, never did reviews, etc. I’m learning quickly. (It’s a great app.)
* * *
I’ve given myself this week to adapt to my new schedule. It’s a big enough change that I shouldn’t try and get everything perfect all at once.
So I’m eating as much of the (wonderful) food as I want and I haven’t figured out when I’m going to go for my normal jog. (Probably it’ll be first thing in the morning before work.)
I have a nice commute in the morning (the 40 from Ballard) and a crappy commute going home. I think my mistake is the classic rookie mistake — I’m commuting at the end of rush hour. I should wait just a bit.
* * *
My office is still sparse. I’ve brought in a couple things and I have a plant. It’s a big blank canvas right now, waiting for me to turn it into something that feels like me. The last time I had this experience was 1999 when I moved into my current house. (I might have to go shopping.)
And I’ve never had my own office out in the working world. I’ve literally never programmed while other people besides my wife were around, other than a few trips to Dave Winer’s office in the ’90s. This is very new to me. People can see me.
* * *
I’ve also given myself the week off from blogging and from working on Vesper while I adjust. But I didn’t even manage to go a whole week without blogging, and it’s a cinch I’ll be working on Vesper tomorrow night and on the weekend.
* * *
My job is awesome. I am happy. I’ve earned it.
PS Seattle Xcoders is a week from tonight. At Omni’s theater. I’ll see you there, or afterward at Cyclops. :)
This blog is powered currently by a home-grown Ruby thing — which is super-fast and wonderful, but has the disadvantage of needing a computer. And I’m mobile these days.
(I’ve been working on syncing and mobile apps for years now, but I never actually needed that stuff for myself since I’m always at home in front of a Mac. Until now. Nowadays I actually leave the house. There are all these things happening in the world. I had no idea.)
I could take my current engine and put it on a server and make it work in that context. Probably a few evenings of work.
But I kinda don’t wanna.
I’m thinking that somebody else could write this thing for me. And someone probably has.
What should it be? Which system?
Requirements: can stand up to extremely busy days. (Think of Daring-Fireball-and-Hacker-News-links-at-the-same-time kind of traffic.)
I must be able to post from Mac, iPhone, and iPad. The ideal native app has a sharing extension so I can initiate a post from another app (Twitter client, Unread, etc.).
I have to be able to make it look like my current site, and I need to be able to preserve the URLs I have now. (So there must be a way to import existing pages.)
And I need to be able to host it myself.
Got recommendations? Find me on Twitter.
PS This blog has existed since 1999, and it’s always been powered by something that I wrote. (It’s on its third blog engine.) And I admit that part of my head is still stuck in the ’90s — real bloggers write their own thing. But I think it’s probably okay to let that thought go.
I start my new job as a developer at the Omni Group today. You already know them and their wonderful products, and I’ve expressed my admiration for them here on my blog many times.
But, before I talk about how I’m excited for this new job, I’ll reassure Vesper users: Vesper will continue, and I will continue as its developer.
It’s in great shape, actually — we’ve done the heavy lifting of designing and writing an iOS app, writing client sync code (that’s shared between iOS and Mac apps), and writing and deploying the sync server. And we have good work in the pipeline.
Plenty of work — lots of work — remains for Vesper, but we’re over the biggest hurdles, which is great. We’ll keep at it.
I love that I get to work on both Vesper and on Omni apps. Omni is one of the great Cocoa development companies, and they’ve grown slowly and steadily over many years. They write lovable productivity apps — not just great iOS apps but also great Mac apps. They’re generous to and respectful of their users, employees, and the local development community. Their values and ambitions align perfectly with mine.
And for me it’s a chance to do something I’ve never done: to work on a large team with lots of products and lots of users. To be part of something not just a little bigger than me but a lot bigger than me.
I could have shopped around and talked to other companies (including Apple, I suppose) — but I didn’t. I could have considered contract programming as a way to bring in extra money, but I know myself well enough to know I’d hate it.
Instead, I wanted to work at Omni. And now I am.
I wrote that thing about the Skillet Diner because I was insulted and angry about it the next day. (I’m not an angry person in general: this feeling is rare for me.)
But there are also some lessons to learn for software developers.
The first obvious one is to treat people with respect from the second they open the door — or from the second they launch your app. The first-time, first-run experience is massively important.
There’s also a second, less-obvious lesson.
The most charitable explanation I have is this: I said I’d be willing to sit at the counter, but I didn’t specify that I’d be willing to sit outside or at the bar. I figured I didn’t actually have to figure out all the different sections and specify each one to the host.
But the host knows the place: he thinks of the restaurant in terms of outside, dining room, counter, and bar. Whereas I, new to the place, think mainly of just sitting down somewhere. I’m no expert in that restaurant, having just arrived, but I was observant enough to notice some counters with empty seats together, so I did say we wouldn’t mind sitting there.
If my theory is correct, then the host expected me to treat him like a computer. He expected me to specify which sections would be acceptable, and he expected me to know all of them and present a list of acceptable choices.
But picture us: we were hungry, brand-new to the place, and we had enough experience to know that every other host in every other restaurant ever has managed to understand our wishes just fine — and has asked questions when needed, which is totally fine.
In other words, this particular UI was designed to be technically true but was entirely lacking in understanding of the (potential) customer.
Sheila and I had been meaning for some time to try the Skillet Diner in Ballard. We went last night around 7 pm.
When we got there there were a couple parties standing by the front desk. The host seated them.
Then we told him that there were two people, and answered no when he asked if we had reservations.
He told us it would be about 20 minutes. I asked if we could just sit at the counter — there’s a long counter with a bar at one end, and there were clearly two seats together in a couple spots — and he said no, but he’d add a note saying we’d be willing to sit at the counter.
So we stood and waited. I counted two two-tops open outside and two in the dining room. As we waited another table paid and left, which made it three two-tops open in the dining room.
As we waited nobody was seated in those tables or at the empty counter spaces.
About five minutes in to this, another couple walks in. They don’t have reservations either. The host explains that there’s a wait. He goes somewhere else for a minute. They flag a waitress, and ask her. She explains that the restaurant is very busy. The host returns, and they ask if the bar is open seating.
The host looks bugged (he’d look bugged the whole time we’d been there) — but then he grabs a couple menus and seats them at the counter in the bar.
Ahead of us.
We walk out.
* * *
As we walk out there are still empty tables-for-two outside and in the dining room, and there was space at the counter for two.
And I had figured by saying that we’d be willing to sit at the counter, that would have been enough to say that we’d even sit at the bar. Or wherever.
The bar appears as just an extension of the counter, where it turns 90 degrees. Someone new to the place, like me, might not even realize that they’re not the same thing — but it seems like I shouldn’t have to specify every single section where I’d be willing to sit. If you’ve got space, just ask me, right?
Now, it’s possible he was about to seat us at a table after seating that couple. But he should have done so first, instead of insulting us by seating another couple ahead of us. Or he could have at least told us and then come back for us.
But he didn’t.
In other words: the service at Skillet Diner in Ballard was so bad that we didn’t even get as far as sitting down. This was a first for me.
We won’t give them a second chance.
* * *
Update 11:50 am: On Twitter, Skillet Diner replies:
there is no excuse for terrible service and that host's actions will be corrected.
I’m still not giving them a second chance. But I’m glad they recognize that this was terrible service.
Picture an app with three levels of hierarchy. Let’s pretend it’s an RSS reader (it’s not).
Feeds > Timeline > Article
The modern way to make this work for iPad and iPhone is to use a UISplitViewController.
Left: navigation controller with Feeds and Timeline.
Right: navigation controller with Article.
Launch in portrait on iPhone 6 Plus. The Feeds view controller takes up the full screen. Good.
Tap a feed. The Timeline appears full screen. Good.
Now rotate — the split view now appears. On the left is Feeds; on the right is Timeline. Not good.
And, if I tap a feed, a second Timeline appears in the left, which means two timelines are showing.
I have my project set up almost exactly like the Xcode template for a split view controller app. The difference is really just that the master has Feeds and Timeline view controllers, instead of just a single Feeds view controller.
How do I get the Timeline to move to the master (leftmost) side when going from compact to regular horizontal size class?
My question is much like this unanswered question on Stack Overflow. A difference is that I’m using a selection segue instead of pushing a view controller. (I’m not sure the difference matters. At any rate, we have the same issue.)
Update 2:30 pm: I ran into serious trouble when nothing I tried seemed to do anything. Clean builds didn’t help. It wasn’t till I noticed that breakpoints in delegate methods weren’t getting hit — and I was about to file a Radar — that I realized I probably needed to delete the DerivedData folder. So I did, and things started working. So: consider that a reminder.
I’ve published a gist that shows what I’ve got so far. I’m sure it’s not the final version of the code —
splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: will need more logic — but it solves the problem so far.
And — bonus — @FriedLemur has the tip of the day (regarding DerivedData):
Option key turns Clean into Clean Build Folder, pretty much rm -rf
That’s so much better than me trying to remember where the DerivedData folder actually is.