Blood, sweat, and tears. How we built a Mac app and threw it away.
Shortly after we started brainstorming and sketching out ideas for what would later become Conveyor, we agreed that we needed to build a desktop client for the concept to work. Being a web dev company with no experience building desktop applications, we needed to learn how to do that. And quickly! Our feeling was that we could start with building an app for Mac only. Then if the product took off, we could invest time in a Windows app separately.
Right away the debate started:
Should we build the app using native Apple tech stack or use a web stack such as Electron?
Chris recently shared our decision to scrap our macOS client and rebuild Conveyor with Electron. I’d like to follow up on that and share a bit of our experience. Making the decision to scrap two plus years of work is not easy — but neither was building a native client for macOS. The following is a summary of the last two years.
It may sound cheesy, but User Experience has always been of paramount importance to our team. It’s ingrained in our culture and the way we build our products. Most of us are avid Mac users. So we had a pretty good idea of what makes a great Mac app.
Throughout our discussions on native vs. Electron the same sentiment would come up again and again: Electron apps just don’t provide the same level of user experience and performance as great native apps do. And since we wanted to build a great application, we took a leap of faith (a huge gamble really) and decided to build our desktop app with the native stack. The only concern was that none of us had any experience with it.
Now, we weren’t crazy to think that we could just buy a book on Swift and learn how to create Mac apps overnight. We knew that we needed experienced help. Andrew Theken joined Wildbit at around that time, and he had some experience writing iOS and Mac apps. So he joined Conveyor team to help us with the native development.
Slava Karpenko joined the team shortly after. Slava is a veteran Mac developer from Russia who we’ve known for years through his work on various iOS projects. Slava has been doing Mac development since OS 9, which gave us a lot of hope. Having two Mac developers on board, we considered ourselves equipped to build our desktop client. Boy, were we up for a nasty surprise…
Adjusting to the new dev cycle.
That ensured nothing got lost in translation and that the designer had full control over the user experience. We continue iterating on the design as it progresses from sketches to markup to the actual web application. We would sometimes change the design drastically multiple times in one day, after seeing how it integrated into the app.
Unfortunately, that workflow didn’t apply once we started working on the Mac app.
Our designers could no longer implement their designs themselves. They would have to learn the complicated Interface Builder, and then learn to code in Swift. Apparently, significant chunks of a modern Mac application UI is implemented in code and not in Interface Builder. There are still some things that IB doesn’t support, so you have to fall back to the lower level and write the UI by hand. We couldn’t expect our designers to start writing Swift code to make their designs work.
As a result, designers had to resort to creating static visual mockups of their designs and passing them over to the Mac team for implementation. And that opened a whole can of worms. Since our Mac guys were very busy building an entire application from scratch, these designs were sometimes sitting in the backlog for months. And once implemented, there were always things we wanted to adjust based on how it felt in an actual application.
That design-develop-repeat cycle was taking weeks on the Mac instead of days on the web. And because one person created the design and someone else did the implementation, things got lost in translation. There was far more back and forth needed to get all the details right. It felt like web development in the early 2000’s, translating PSD mockups to HTML. It was a downgrade for our team.
Problems with the tooling.
We quickly realized that Swift is not ready for production (despite being a version 2.0 language at the time).
While Apple was boasting about language features and new frameworks, things were breaking left and right.
The compiler was throwing cryptic errors at us. And Xcode would choke on Swift, sometimes even being unable to syntax highlight it. And since most of Apple’s frameworks and libraries are still in Objective-C, things were breaking even more because of the bridging between the two languages.
But we decided to stick with Swift in hopes it would mature enough by the time we were ready to launch Conveyor. Sadly, things never improved.
And then there’s Xcode. The official Apple way of developing Mac and iOS apps. An IDE from Apple, how exciting, I thought at first!
But it’s well known among Mac developers that Xcode is, how to say it, not as dependable as it could be. The amount of Xcode rants in our Slack room is beyond belief. It was breaking, freezing, hanging, or crashing daily. It was slowing us down. Things got so bad that Dima switched over to AppCode by JetBrains shortly after he joined the team.
Another problem was a lack of quality open source tools and libraries to use. The Objective-C online community is small. And the Swift community is even smaller. It was a struggle to find libraries or Stack Overflow threads for what we needed. Sometimes we would be dealing with a bug in AppKit that we couldn’t find any information about online, except for some old mailing list thread from 2004. It was very frustrating.
The promised land.
But, you might ask, all of that struggle was sure worth it for that elusive last mile of user experience and performance that we were so hoping to achieve, right? Well, it’s complicated.
As we quickly learned, there’s no free user experience/performance lunch in Mac development. If you wanted your app to feel fluid and beautiful with animation and top notch speed, you had to do a ton of manual labor. There was little to no support from the built-in frameworks and libraries to help you achieve that.
If you look at the fantastic new Things app for Mac, its entire UI is custom built.
So if you want your app to look and feel like one of the greats you have to be prepared to rewrite a lot of what AppKit was supposed to provide. Design of our application wasn’t that complicated or unique, but we still had to create our own implementation for simple push buttons because of how limiting default buttons were in AppKit.
And doing things from scratch on Mac is not easy. An animation written in Swift could sometimes require a hundred lines of code. And since you’re dealing with memory management, these things tend to take a long time to write and debug. Same with performance. Our native Mac app was performing slower than some Electron apps that we’ve researched. We could theoretically get that performance edge that we wanted, but it was just too costly for us. We didn’t have the time or manpower to reach that goal as a small, agile team.
Picking up the pieces and moving on.
As you already know, we scrapped our native app before we could finish it, because we realized that we’d probably never reach the promised land. We had to be honest with ourselves. We made a very bold attempt at creating a native app, despite people telling us how painful native development could be.
So even though we failed at creating a delightful macOS app, we went through many iterations of designs and prototypes while trying. Which helped us solidify our concepts and ideas. It helped us better understand what the heck we’re building in the first place. We learned a ton about developing a desktop client, native or not. And we’re now applying all that we’ve learned to the new version, this time using Electron.
And we’re off to the races!