<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Danilo Campos</title>
    <description>The latest articles on DEV Community by Danilo Campos (@_danilo).</description>
    <link>https://dev.to/_danilo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F285749%2F9e51715d-338d-4a1b-bc84-424f42bb05f5.jpg</url>
      <title>DEV Community: Danilo Campos</title>
      <link>https://dev.to/_danilo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_danilo"/>
    <language>en</language>
    <item>
      <title>Designing, building and shipping an iPhone app in five weeks with SwiftUI</title>
      <dc:creator>Danilo Campos</dc:creator>
      <pubDate>Thu, 23 Apr 2020 13:04:21 +0000</pubDate>
      <link>https://dev.to/_danilo/designing-building-and-shipping-an-iphone-app-in-five-weeks-with-swiftui-34e4</link>
      <guid>https://dev.to/_danilo/designing-building-and-shipping-an-iphone-app-in-five-weeks-with-swiftui-34e4</guid>
      <description>&lt;p&gt;I was laid off last month.&lt;/p&gt;

&lt;p&gt;I’m hardly alone in this. With COVID-19 upending the game boards of every industry under the sun, lots of us have lost jobs.&lt;/p&gt;

&lt;p&gt;Of course, this makes finding my next act complicated. Even in the technology business, thousands of highly qualified professionals have been cut loose in recent weeks. It’s just not a great time to look for a job.&lt;/p&gt;

&lt;p&gt;So I decided to go back into business for myself.&lt;/p&gt;

&lt;p&gt;The first step in that process was &lt;a href="https://tallymander.app"&gt;building an app&lt;/a&gt; as both a source of income and a showcase of my abilities as a product designer and software developer. I’ve spent every day, night and weekend of the last five weeks designing, building and shipping an iPhone app. Part of what made this possible was SwiftUI. Apple’s new declarative UI framework makes building touch interfaces faster than ever. Even with the pitfalls you’d expect from a brand new technology, it is already the best and fastest way to build an app for iOS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a product
&lt;/h2&gt;

&lt;p&gt;Once upon a time, I fell in love with product design, code, and shipping software in Second Life. I didn’t think I’d do anything else with those skills—I just had a lot of fun with them, and paid a few bills on the side.&lt;/p&gt;

&lt;p&gt;But in 2008, when the App Store debuted, I saw a chance to put those skills to work for a truly global audience. I wanted a career building software.&lt;/p&gt;

&lt;p&gt;I knew no one would hire me without experience, so I set about giving myself some. I built a raft of products for the iPhone—a game, a travel organizer, a simple tool for shopping at IKEA. I sold a few of each.&lt;/p&gt;

&lt;p&gt;But there was one app that truly stuck. Called Tallymander, it was a flexible tool for counting and math. Early on, to my shock and exhilaration, Apple featured it on the front page of the App Store. I heard from customers across all walks of life—research scientists, drywall installers, collectible card game enthusiasts—who were in love with it and had feature suggestions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f08xmzeX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2020/04/image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f08xmzeX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2020/04/image.png" alt=""&gt;&lt;/a&gt;Look at that shiny, glossy UI. Were we ever that innocent?&lt;/p&gt;

&lt;p&gt;Eventually, I got my career as a professional product designer and iOS developer. In fact, Tallymander opened the door: the CEO at my first startup gig was an early Tallymander customer. But I didn’t have time to maintain my indie projects, so I took them off the store.&lt;/p&gt;

&lt;p&gt;No one really minded. Except my Tallymander customers, who emailed, bereft, for months.&lt;/p&gt;

&lt;p&gt;Like an estranged friend, I’ve thought of Tallymander constantly in the years since. I loved that product. I loved that it was useful to people. And I loved my tiny contribution to the Great Conversations of products like VisiCalc, Excel, and Airtable. All of these make data and algorithms something tangible, experimental and fun.&lt;/p&gt;

&lt;p&gt;Over the years, I’ve wanted desperately to revisit Tallymander. But the level of effort it takes to build an iOS app is significant. Quality is paramount, for one thing. You have to do things well to thrive in that environment.&lt;/p&gt;

&lt;p&gt;But UIKit, Apple’s original UI development framework, can be such a slog. It was hard to maintain the velocity needed to get a project off the ground.&lt;/p&gt;

&lt;p&gt;Last month, I was given more time than I asked for. And SwiftUI, Apple’s replacement for UIKit, had gelled enough to build with.&lt;/p&gt;

&lt;p&gt;This confluence of events meant one thing: it was time to bring Tallymander back.&lt;/p&gt;

&lt;h2&gt;
  
  
  SwiftUI: the fast, modern way to build an interface for Apple platforms
&lt;/h2&gt;

&lt;p&gt;UIKit emerged for third-party developers in 2008. It was a very different world. There were only two models of iPhone. Both phones had identical screen sizes. The iPad was still a secret project, known only to a few.&lt;/p&gt;

&lt;p&gt;In the years since, iOS evolved to accommodate a vast array of devices. You can use UIKit to target all of the different iPhones and iPads. Using tools like auto layout, you can fiddle your way to a view that looks right on any size screen. Eventually.&lt;/p&gt;

&lt;p&gt;But in a lot of ways, UIKit and friends feel pretty creaky. Want a table-based UI? You’ll have to implement dozens of lines of data source and delegate boilerplate just to get started. Want to draw shapes? It’s an exercise in such tedium that an &lt;a href="https://www.paintcodeapp.com"&gt;entire app&lt;/a&gt; exists to spare you the worst of it.&lt;/p&gt;

&lt;p&gt;UIKit also let you create bugs if you weren’t careful. Cocoa Bindings never made the journey from macOS to iOS. With no reactive frameworks built in, a typical approach was to write UI values in one pass, maybe when the view appears, and read back any inputs in another, perhaps when it disappeared. This could work, but the space in between was room for bug city. Frameworks like &lt;a href="https://github.com/ReactiveX/RxSwift"&gt;RxSwift&lt;/a&gt; emerged to fill the gaps, but didn’t have the widespread, universal adoption of Apple’s many iOS libraries. You could cobble something together yourself with KVO, but managing subscribers was its own headache. Get it wrong, you get crashes.&lt;/p&gt;

&lt;p&gt;Apple’s edge in mobile was multiplied dramatically by the existence of developer tools and frameworks adapted from Mac OS X. Preserving that edge meant investing in new developer-focused technologies, especially Swift, which could be fertile ground for a new generation of APIs.&lt;/p&gt;

&lt;p&gt;That strategy is has borne some meaningful fruit in technologies like SwiftUI and Combine.&lt;/p&gt;

&lt;p&gt;SwiftUI is &lt;em&gt;declarative&lt;/em&gt;. You tell it what you want to see, it figures out the details for you. This is a departure from UIKit, where every point of every frame of every view was a detail the developer could worry about.&lt;/p&gt;

&lt;p&gt;Tell SwiftUI you want a field here, a slider there, and a toggle switch &lt;em&gt;if&lt;/em&gt; the slider exceeds a certain value, and you’ll just get that interface. Maybe it won’t be exactly what you had in mind, so you’ll add a few modifiers. Primp with some padding, polish with some rounded corners.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lth9u7kC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2020/04/Screen-Shot-2020-04-21-at-1.55.07-PM-1024x516.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lth9u7kC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2020/04/Screen-Shot-2020-04-21-at-1.55.07-PM-1024x516.png" alt=""&gt;&lt;/a&gt;An entire form with conditional fields specified in under thirty lines. Some of these call out to additional, more complex views, but most of those are reused across this and other forms.&lt;/p&gt;

&lt;p&gt;This is already pretty convenient. But SwiftUI debuted alongside Combine, Apple’s framework for “processing values over time.” In practical terms, Combine lets you easily, accurately keep track of everything from network request progress to the state of your user inputs, consistently reflecting that state in both your UI and your data model.&lt;/p&gt;

&lt;p&gt;These two frameworks together make it ridiculously fast to build a UI. This operated on two levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to experiment/prototype and quickly see results&lt;/li&gt;
&lt;li&gt;Less time spent on debugging because things largely &lt;em&gt;just&lt;/em&gt; &lt;em&gt;worked&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building Tallymander from scratch, in Swift, I started out with UIKit and thought I might experiment with SwiftUI. It seemed like a good way to build detail views and forms.&lt;/p&gt;

&lt;p&gt;By the time I finished, 95% of the app’s view code was SwiftUI. It was just so much better at solving the problems I was tackling, and so much more fun. Before bed, I’d imagine some new feature I wanted. With SwiftUI could often knock it out within an hour or so the next morning.&lt;/p&gt;

&lt;p&gt;The almost-instant previewing of SwiftUI interfaces was a big factor here. Rather than building and launching to check sizing, typography or content, you can see in seconds whether your layouts and drawing code are doing what you expect. One thing that surprised me was how deep these previews can go. You can include anything in your application as input to a SwiftUI preview, and that includes Core Data entities. Set up a garbage context that writes to &lt;code&gt;/dev/null&lt;/code&gt; and you can build arbitrarily complex object graphs to test your designs.&lt;/p&gt;

&lt;p&gt;SwiftUI’s layout system takes some getting used to—I’d be lying if I said I’d come close to mastering its more advanced features. Still, the basics are so easy to learn, and get you quickly to a UI that easily scales to any screen size. It’s a big improvement on the fiddly autolayout experience of yore.&lt;/p&gt;

&lt;p&gt;I was helped along the way by &lt;em&gt;&lt;a href="https://www.objc.io/books/thinking-in-swiftui/"&gt;Thinking in SwiftUI&lt;/a&gt;&lt;/em&gt;. To give you an idea how early days we are, I started reading it while it was available chapter-by-chapter on a pre-release basis. It’s done cooking now and worth every penny. It’s a helpful guide to building a new mental model after years with UIKit.&lt;/p&gt;

&lt;p&gt;One thing I appreciate about SwiftUI is how easy it is to construct novel UI components. Example: The reset control on a counter expands to cover its neighbor and present a confirmation message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DiQKnxPF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2020/04/reset.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DiQKnxPF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2020/04/reset.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Could I have built this in UIKit? Of course. But I’m not sure I would have thought to try, because doing it would be so fiddly. SwiftUI’s state management is so tidy, it’s trivial to add a &lt;code&gt;@State&lt;/code&gt; variable, attach it to a few views, and see how you like it. Putting things into layout based on state is as trivial as an &lt;code&gt;if&lt;/code&gt; statement—so is pulling them out. Tacking on an animation is simple, too. You can go from “what if…?” to working results in a couple of minutes, and then decide from there if you want to keep those results. More experimentation, more fun.&lt;/p&gt;

&lt;p&gt;It’s a brand new technology, so there are occasional hiccups. Passing numerical values to TextFields—critical to any math-centric application—is a little buggy, and requires a &lt;a href="https://stackoverflow.com/questions/56799456/swiftui-textfield-with-formatter-not-working/58031510#58031510"&gt;workaround&lt;/a&gt;. But even with these bumps in the road, it’s a much better path to shipping.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--amWywvxd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2020/04/card-image-1024x1024.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--amWywvxd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2020/04/card-image-1024x1024.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything in the new Tallymander is drawn with SwiftUI. Aside from the icon assets, there are no pre-rendered images. This is an approach worth taking even in the old world of Quartz and UIKit. With multiple screen densities, and now Dark Mode, if you can afford to do your drawing in code, you’ll save yourself a mess of image variations. SwiftUI makes the economics of this so much more favorable, as you get a realtime preview of your efforts as you go, and the drawing API is significantly &lt;a href="https://twitter.com/_danilo/status/1245487974936084490"&gt;less verbose&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  But what about iOS 12?
&lt;/h3&gt;

&lt;p&gt;I say don’t even worry about it. My policy has been to target only the latest major OS version for new apps. iOS update adoption is aggressive, especially in the age of emojis. &amp;gt;90% of customers end up on the latest major release within six months, so why bother doubling your test burden? A small and shrinking slice of the market isn’t worth the headache of backwards compatibility in most cases.&lt;/p&gt;

&lt;p&gt;This case is even more dramatic now, where it’s a choice between SwiftUI’s velocity and slogging through the mud of UIKit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Old friends
&lt;/h2&gt;

&lt;p&gt;I said 95% of Tallymander is SwiftUI. So what about the rest?&lt;/p&gt;

&lt;p&gt;There was one place where I needed classic, UIKit-style control over the interface down to the &lt;em&gt;quantum&lt;/em&gt; level: the actual table that houses your Counters and Calculators.&lt;/p&gt;

&lt;p&gt;Right now, SwiftUI doesn’t expose a lot of customization for its analog to &lt;code&gt;UITableView&lt;/code&gt;, &lt;code&gt;List&lt;/code&gt;. But since this was going to be the core of people’s day-to-day experience of Tallymander, I wanted to be able to tweak everything about layout, cell rendering, edit behavior, you name it.&lt;/p&gt;

&lt;p&gt;What’s wild about this, though, is that while the enclosing cells and tableview are UIKit, all of their content is SwiftUI. It’s possible to make them work together &lt;em&gt;that&lt;/em&gt; seamlessly. Watch:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k_2o3cx---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2020/04/table-magic-2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k_2o3cx---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2020/04/table-magic-2.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Counter buttons that fade away, along with the bandage symbol, are all views in SwiftUI. They animate alongside the red delete controls on the left, and the reorder controls on the right, which are provided by UIKit.&lt;/p&gt;

&lt;p&gt;But to the user, these details are invisible. Thus, where you need an escape hatch to UIKit or SwiftUI in your project, you’ve got one, and there’s no compromise in user experience. That’s powerful stuff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other modern niceties
&lt;/h2&gt;

&lt;p&gt;Working with Swift is a joy. The rigid type took some getting used to after years of Objective-C. But the reward for this temporary discomfort is &lt;strong&gt;fewer fucking bugs&lt;/strong&gt;. According to TestFlight, Tallymander has never crashed in the wild. Not once. The only crashes I’ve observed were the immediate consequences of doing weird things with text that SwiftUI didn’t like.&lt;/p&gt;

&lt;p&gt;This is a lot of time back in your pocket, once again, because you’re working on features instead of chasing bugs.&lt;/p&gt;

&lt;p&gt;I want to take a moment to celebrate a small feature in iOS 13. SF Symbols debuted at the last WWDC and it’s a game changer for teams large and small. By providing a vocabulary of 1500 symbols, developers now have a low friction way to enhance the communication of their UI. This was an enormous headache when I was starting out in 2008. The OS was filled with symbols that &lt;em&gt;meant&lt;/em&gt; things—chevrons as “disclosure indicators,” little red gems to indicate delete controls, green dots for creation controls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cZdb9-1q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2020/04/Screen-Shot-2020-04-21-at-1.16.10-PM-1024x683.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cZdb9-1q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2020/04/Screen-Shot-2020-04-21-at-1.16.10-PM-1024x683.png" alt=""&gt;&lt;/a&gt;SF Symbols: Apple provides a Mac app as a directory of all the glyphs&lt;/p&gt;

&lt;p&gt;If you wanted to put your own spin on these, you had to build them from scratch. If you needed your own symbols, you needed to design or procure them as well. An entire cottage industry formed around this—I supported the &lt;a href="https://www.glyphish.com"&gt;Glyphish&lt;/a&gt; Kickstarter because it promised a few hundred such symbols.&lt;/p&gt;

&lt;p&gt;There was also friction with getting such images to the screen. You had to size and convert them, add them to your Xcode project, and with the advent of Retina displays, manage multiple resolutions.&lt;/p&gt;

&lt;p&gt;With SF Symbols, it’s a one-liner: &lt;code&gt;Image(systemName: "chevron.right")&lt;/code&gt;. It’s trivial to style your symbols with colors appropriate to your app design. If you’re using SwiftUI, composing them with other UI elements is a snap using &lt;code&gt;ZStack&lt;/code&gt; and friends.&lt;/p&gt;

&lt;p&gt;This makes it so much faster and easier to make a UI that’s clear and easy to understand. Because you’re using a shared vocabulary with the OS and other apps, users will feel at home with your iconography.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open for business
&lt;/h2&gt;

&lt;p&gt;With SwiftUI, it’s a great time to build iOS apps. The time costs are lower than ever, the APIs are fun and modern, and the results are satisfying. The new technologies have their quirks, but have already gelled to an impressive degree with less than a year in developer hands. I know this was very high-level, but rest assured I have a few practical tips I’ll be sharing soon, especially about how to make UIKit and SwiftUI work well together.&lt;/p&gt;

&lt;p&gt;I hope you’ll check out the new &lt;a href="https://tallymander.app"&gt;Tallymander&lt;/a&gt; and share it with anyone you know who’d enjoy a tool like this.&lt;/p&gt;

&lt;p&gt;I’ll be continuing to flesh out Tallymander, and plan out my next projects. If you need support for an iOS project on a freelance basis, &lt;a href="https://danilocampos.com/hire.html"&gt;drop me a line&lt;/a&gt;. I can give you the perspective and experience of someone who has not only been with this platform from the beginning, but who is also up to speed on the latest technologies.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>swiftui</category>
      <category>uikit</category>
      <category>ios</category>
    </item>
    <item>
      <title>Swift: Markdown files from string literals</title>
      <dc:creator>Danilo Campos</dc:creator>
      <pubDate>Thu, 05 Mar 2020 16:00:45 +0000</pubDate>
      <link>https://dev.to/_danilo/swift-markdown-files-from-string-literals-1p4i</link>
      <guid>https://dev.to/_danilo/swift-markdown-files-from-string-literals-1p4i</guid>
      <description>&lt;p&gt;My &lt;a href="https://twitter.com/_danilo/status/1227650622377414656"&gt;new iOS fiction project&lt;/a&gt; relies heavily on text.&lt;/p&gt;

&lt;p&gt;That means I want to make it easy to create that content anywhere, and I want it to be frictionless to drop it into the project as needed.&lt;/p&gt;

&lt;p&gt;My solution: Markdown files I can load from the bundle using string literals. Look how easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MarkdownFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"markdown.md"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here’s how to do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  String literals
&lt;/h2&gt;

&lt;p&gt;In Swift, you use string literals all the time. Usually to initialize strings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello, I am a string."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;But Swift includes a protocol called &lt;code&gt;ExpressibleByStringLiteral&lt;/code&gt;. Which means if your Swift type adopts it, that type can be initialized with nothing more than a string. While this is immediately convenient, it has real power for assets that need tedious boilerplate. Say, anything that needs to be loaded from a bundle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic example
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;MarkdownFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ExpressibleByStringLiteral&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bundleName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;rawMarkdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;stringLiteral&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;bundleName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringLiteral&lt;/span&gt;

        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;loadedMarkdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;filepath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Bundle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forResource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bundleName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ofType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//By skipping the ofType argument above, we'll match to the first file whose name&lt;/span&gt;
        &lt;span class="c1"&gt;//exactly matches bundleName&lt;/span&gt;
            &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;loadedString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;contentsOfFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;loadedMarkdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loadedString&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not load string: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not find file: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;bundleName&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;rawMarkdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loadedMarkdown&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here’s a basic example of a &lt;code&gt;MarkdownFile&lt;/code&gt; struct. It knows two things about itself: the name of the file used to initialize it, and any string it was able to load from a file in the bundle with that name.&lt;/p&gt;

&lt;p&gt;On &lt;code&gt;init&lt;/code&gt; it goes looking for a bundle resource matching the name it was provided through the string literal. If it finds one, and it can load its contents as a string, those contents are stored to &lt;code&gt;rawMarkdown&lt;/code&gt;. If not, &lt;code&gt;rawMarkdown&lt;/code&gt; returns &lt;code&gt;nil&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is already pretty convenient. Again, to initialize, all you need is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MarkdownFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"markdown.md"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;But we can take it further.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding convenience
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;MarkdownFile&lt;/code&gt; struct can be responsible for converting its contents into a display representation, as well. Let’s add a computed property to parse the Markdown into HTML. I’ll be using &lt;a href="https://github.com/JohnSundell/Ink"&gt;Ink&lt;/a&gt; for this, but you could use any project—or convert it into something else, like &lt;code&gt;NSAttributedString&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;htmlRepresentation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rawMarkdown&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;MarkdownParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;With our output property all set up, we have a small, convenient API for handling Markdown files in any way we want. Here’s how we use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MarkdownFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"markdown.md"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;htmlRepresentation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;webview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadHTMLString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bundleName&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---MY1uMBd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/213358/75999380-1cf13600-5ed0-11ea-82b2-c1f2ff5a19ec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---MY1uMBd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/213358/75999380-1cf13600-5ed0-11ea-82b2-c1f2ff5a19ec.png" alt="Screen Shot 2020-03-05 at 10 39 58 AM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Behind the scenes, lots of stuff is happening to load and parse the file. But when you need Markdown across your project, you need only concern yourself with a filename. If you want to change any part of how this works later on, you have a single struct that’s responsible for all the Markdown behavior in your code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/daniloc/MarkdownStringLiteral"&gt;Full example code here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>markdown</category>
    </item>
    <item>
      <title>ECDSA signatures with node.js and Swift</title>
      <dc:creator>Danilo Campos</dc:creator>
      <pubDate>Tue, 19 Nov 2019 21:41:24 +0000</pubDate>
      <link>https://dev.to/_danilo/ecdsa-signatures-with-node-js-and-swift-365j</link>
      <guid>https://dev.to/_danilo/ecdsa-signatures-with-node-js-and-swift-365j</guid>
      <description>&lt;p&gt;In the process of de-risking some business model questions for &lt;a href="https://dev.to/_danilo/post-mortem-digamo-beta-20ma-temp-slug-7148590"&gt;Digamo&lt;/a&gt;, I went looking for an open source project that would allow us to generate and verify license keys. We’re not sure what our business model will be there, but understanding the technical costs of licensing is important to making decisions.&lt;/p&gt;

&lt;p&gt;While this is a problem that &lt;a href="https://github.com/bdrister/AquaticPrime"&gt;has&lt;/a&gt; been &lt;a href="https://github.com/vslavik/ellipticlicense"&gt;solved&lt;/a&gt;, I didn’t find anything especially modern, well-maintained, or documented—and nothing in Swift or Node, the stuff I’m using for everything else right now.&lt;/p&gt;

&lt;p&gt;Some research did suggest that asymmetric cryptography was the way to go, using a private key to sign a registration string, and a public key distributed with the app to verify the signature. This was also the approach used in the older projects that tried to solve this problem for Mac apps.&lt;/p&gt;

&lt;p&gt;Still, I found precious little documentation or tutorials to walk me through the implementation. All the signing examples took place within the same context, rather than between client and server. Here’s an approach that appears to be working, using Node and a Swift Mac app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the demo
&lt;/h2&gt;

&lt;p&gt;Generate a signed message file here: &lt;a href="https://eccsign-server.glitch.me"&gt;https://eccsign-server.glitch.me&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Download, build and run the macOS verification app here: &lt;a href="https://github.com/daniloc/eccsignaturetest"&gt;https://github.com/daniloc/eccsignaturetest&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the app is running, you can double-click the signed message files from the Glitch app to automatically load them. File free to tamper with the content of the &lt;code&gt;plaintext&lt;/code&gt; field by opening a file in your favorite text editor, then try to verify again. Tampering should cause the verification step to fail.&lt;/p&gt;

&lt;p&gt;The public and private keys are included for your inspection and experimentation, as a “known-good” configuration. &lt;strong&gt;In your own projects you should use care to ensure your private key is not distributed.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate encryption keys
&lt;/h2&gt;

&lt;p&gt;Per &lt;a href="https://developer.ibm.com/swift/2019/03/04/blueecc-elliptic-curve-cryptography/"&gt;the instructions from IBM’s BlueECC project&lt;/a&gt;, here’s how you get started:&lt;/p&gt;

&lt;p&gt;On macOS you can install OpenSSL using &lt;code&gt;brew&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brew install openssl&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once you have installed OpenSSL, create your private key:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;openssl ecparam -name prime256v1 -genkey -noout -out ec256priv.pem&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Using the private key, create your public key:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;openssl ec -in ec256priv.pem -pubout -out ec256pub.pem&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It seems you want to use the &lt;code&gt;prime256v1&lt;/code&gt; curve—other algorithms have some cross-platform issues.&lt;/p&gt;

&lt;p&gt;This will leave you with a private and public key. The private key goes on your server to generate signatures. The public key can be distributed with your app to verify those signatures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signing with Node.js
&lt;/h2&gt;

&lt;p&gt;The npm module &lt;code&gt;EC-Key&lt;/code&gt; will make it easy to load your key:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;let ECKey = require("ec-key");&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;let pem = fs.readFileSync("./privatekey.pem"); let eccPrivateKey = new ECKey(pem, "pem")&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here my implementation gets a little clunky—there may be better ways to do this, but at least it seems pretty flexible. Create a JavaScript object with whatever keys and content you want:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var objectToSign = {} objectToSign.message = message let date = new Date().toJSON() objectToSign.date = date&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Convert that into a JSON string:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;let outputString = JSON.stringify(objectToSign)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then, create a signature from that string:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;let signature = eccPrivateKey.createSign("SHA256").update(outputString).sign("base64")&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And pack the plaintext string and signature into a second object:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;let outputObject = {} outputObject.plaintext = outputString outputObject.signatureBase64 = signature&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can then convert the output to JSON and have the user download the file.&lt;/p&gt;

&lt;p&gt;See the whole thing in action with &lt;a href="https://glitch.com/edit/#!/eccsign-server"&gt;this Glitch project&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification in Swift
&lt;/h2&gt;

&lt;p&gt;Add the BlueECC package to your project. Install &lt;a href="https://github.com/IBM-Swift/BlueECC"&gt;manually&lt;/a&gt;, or use the Swift Package Manager. In Xcode, choose &lt;strong&gt;File &amp;gt; Swift Packages &amp;gt; Add Package Dependency…&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KR_6cmh---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/11/Screen-Shot-2019-11-19-at-4.10.26-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KR_6cmh---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/11/Screen-Shot-2019-11-19-at-4.10.26-PM.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Search for “CryptorECC,” and pick “BlueECC.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UKGotF0l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/11/Screen-Shot-2019-11-19-at-4.12.01-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UKGotF0l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/11/Screen-Shot-2019-11-19-at-4.12.01-PM.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add your public key file to your project, and &lt;code&gt;import CryptorECC&lt;/code&gt;&lt;br&gt;&lt;br&gt;
to the file where you’re working. Then you can grab the public key like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;let filepath = Bundle.main.path(forResource: "ec256pub", ofType: "pem")!&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;let keyString = try? String(contentsOfFile: filepath)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;let publicKey = try ECPublicKey(key: keyString)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With the public key loaded from your bundle into memory, you can now verify a signature with it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;let signature = try ECSignature(asn1: Data(base64Encoded: signatureBase64)!)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(&lt;code&gt;signatureBase64&lt;/code&gt; is the output from &lt;code&gt;createSign()&lt;/code&gt; above)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;let verified = signature.verify(plaintext: plaintext, using: publicKey)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The constant &lt;code&gt;verified&lt;/code&gt; will tell you if the plaintext and signature match.&lt;/p&gt;

&lt;p&gt;Here’s a &lt;a href="https://github.com/daniloc/eccsignaturetest"&gt;Mac app&lt;/a&gt; you can build and run to see this in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback
&lt;/h2&gt;

&lt;p&gt;Is this a good approach? See anywhere it could work better? Drop me a line.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>node</category>
      <category>elipticcurve</category>
      <category>macos</category>
    </item>
    <item>
      <title>Post Mortem: Digamo Beta</title>
      <dc:creator>Danilo Campos</dc:creator>
      <pubDate>Mon, 28 Oct 2019 20:21:45 +0000</pubDate>
      <link>https://dev.to/_danilo/post-mortem-digamo-beta-242g</link>
      <guid>https://dev.to/_danilo/post-mortem-digamo-beta-242g</guid>
      <description>&lt;p&gt;I’m excited to share a new project with you. It’s called &lt;a href="https://digamo.app"&gt;Digamo&lt;/a&gt; and it’s a macOS app built for people who have, or should have, 1:1 meetings at work.&lt;/p&gt;

&lt;p&gt;Digamo is a collaboration with my dear friend, &lt;a href="http://impossibletomanage.com"&gt;podcast co-host,&lt;/a&gt; and occasional boss, &lt;a href="https://twitter.com/nmsanchez"&gt;Nicole Sanchez&lt;/a&gt;. It’s something we’ve been thinking about doing for years. Back in April, we decided to go for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the research said
&lt;/h2&gt;

&lt;p&gt;Before diving headlong into development on a project we were both excited to build, though, I asked Nicole to conduct some user research. We had some strong convictions already:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Managers didn’t often get the support or training they needed to do their best work&lt;/li&gt;
&lt;li&gt;Healthy, regular communication, especially between bosses and their reports, was essential to inclusion&lt;/li&gt;
&lt;li&gt;We could improve both of these situations by creating structure and guidance for common workplace communication through software&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We still believe these things. But if we were going to invest the energy and effort needed to produce a whole software product, I wanted more information from the sort of people we wanted to serve.&lt;/p&gt;

&lt;p&gt;Nicole gamely conducted interviews with people managers, digging into their processes for communication and how they managed the many meetings and conversations that were essential to their daily work. One theme emerged clearly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Many managers had an enormous Google Doc they were using to keep track of… everything&lt;/strong&gt;. The text was monstrous and unstructured. Things often got lost over the longer term.&lt;/p&gt;

&lt;p&gt;This was an opportunity we hadn’t considered, but it fit into our goals well: software could also structure the information that emerged from these conversations. This would make it possible to slice, filter, search and summarize that information at a later date. In addition to training managers on good communication, and encouraging that communication, we could also provide value by capturing and summarizing outcomes.&lt;/p&gt;

&lt;p&gt;This insight was a North Star that guided our product development, and we share it eagerly with you. People are drowning in information. Help them organize it better, you’ll make a better workplace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting development
&lt;/h2&gt;

&lt;p&gt;At first, we weren’t sure how eager people would be for yet another piece of software at work. We started with the goal of reaching an alpha that would run on an iPhone, sync with your work calendar, and help you prepare for meetings.&lt;/p&gt;

&lt;p&gt;The idea here was that we could meet our users in their spare time, relieving their anxiety about upcoming meetings by providing a clear structure for the sorts of things you do before and after sitting down. You might have some decisions that you want to make sure are made by those in attendance. We suggested warmup activities to build rapport between attendees. How will you know the meeting was successful?&lt;/p&gt;

&lt;p&gt;We prompted the user to think about, and write down, responses for all of these and more. A neat party trick was that the output was nicely formatted into an agenda you could email.&lt;/p&gt;

&lt;p&gt;There was just one problem: the iPhone was a bad fit for these tasks. No one wanted to look at their phone in the middle of a meeting, and prepping for meetings just isn’t the sort of casual task anyone is eager to do with their thumbs.&lt;/p&gt;

&lt;p&gt;And we were about to have company.&lt;/p&gt;

&lt;h2&gt;
  
  
  ~Validating the market~
&lt;/h2&gt;

&lt;p&gt;When competitors join the scene, it’s Silicon Valley tradition to smile fixedly and announce how excited you are to see them &lt;em&gt;validate the market&lt;/em&gt;. There’s some truth to this posturing, though: it’s encouraging to see others converge on a conclusion you’ve reached on your own. Maybe the timing is right for your idea.&lt;/p&gt;

&lt;p&gt;We took the validation to mean we could dispense with the idea of building this as an iPhone toy. We would proceed as though the world was ready for another piece of workplace software.&lt;/p&gt;

&lt;p&gt;Still, no amount of business aphorisms would change that we were now approaching a problem alongside teams that were bigger and better-capitalized than we were. Moving to a desktop format would help our project be taken seriously, but we were still outgunned.&lt;/p&gt;

&lt;p&gt;For our vision to have a future, we would need to chart an unconventional course. As the sole engineer on the project, it was my job to make things work while keeping things lean. If people were going to use this &lt;em&gt;on the job&lt;/em&gt;, it had to be sturdy and reliable. And we had to ship quickly, with competition around the corner. These are interesting constraints to manage when your developer &lt;a href="https://en.wikipedia.org/wiki/Bus_factor"&gt;bus factor&lt;/a&gt; is one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the decisions only we would make?
&lt;/h2&gt;

&lt;p&gt;As outsiders, Nicole and I had little hope of raising money for this project without showing significant progress, so that ruled out bringing on more engineers. Without a larger crew, I didn’t feel confident in using web technology for something so mission critical. I’d be tied to my keyboard indefinitely, protecting the infrastructure that kept the product alive.&lt;/p&gt;

&lt;p&gt;So we tried something different: decentralizing our software entirely. Our approach turned to a native macOS application that would host and maintain all its own data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SOXGO-K4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/giphy-2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SOXGO-K4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/giphy-2.gif" alt="Gif: Man points knowing at his head and grins."&gt;&lt;/a&gt;Server can’t go down if you don’t have a server.&lt;/p&gt;

&lt;p&gt;Because we weren’t starting with non-negotiable hyper growth as a requirement, this old-fashioned approach would be viable for us. The more we explored it, the better it fit our values. By designing our project without a centralized server infrastructure, we could guarantee that no one else surveilled your usage of it. This was important because we wanted our users to be real about the thorny communication issues they encountered at work. We wanted them to trust our product the way they would their own physical notebook.&lt;/p&gt;

&lt;p&gt;I’ve seen complex, misconfigured workplace software that leaks sensitive details like a sieve. It’s a privacy nightmare. Our approach sidestepped all of that.&lt;/p&gt;

&lt;p&gt;We also got some practical benefits: the application could run locally without needing to package an entire web browser under the hood, so the binary size is just 20 MB. The native code is also efficient. Even with a large data set, it doesn’t seem to need more than 50-100 MB of system RAM. So, as a bonus, we got a minimal resource footprint and the ability to run without an internet connection thanks to building a native desktop app.&lt;/p&gt;

&lt;p&gt;For object graph maintenance and persistence, the app uses Apple’s &lt;a href="https://developer.apple.com/documentation/coredata"&gt;Core Data&lt;/a&gt; framework. Ten years after my first Core Data projects, I’m impressed with how much easier it is to work with these days. The framework still has a meaningful learning curve and I’d be bullshitting you if I said I grasped all its complexities. Nonetheless, I found it a straightforward way to maintain all of Digamo’s relationships and generate the user-specified queries needed for summarizing data.&lt;/p&gt;

&lt;p&gt;As a long time iOS developer, I have to take a moment to gush about &lt;a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaBindings/Concepts/WhatAreBindings.html"&gt;Cocoa Bindings&lt;/a&gt;. They make UI development so fast and easy. With the advent of SwiftUI, they may be destined for sunset. I’m glad I finally got a chance to use them, after years of envy from afar.&lt;/p&gt;

&lt;p&gt;We still ended up with a lightweight server to provide automatic updates, delivered by the excellent &lt;a href="https://sparkle-project.org"&gt;Sparkle Project&lt;/a&gt;. This is just good practice if you’re distributing outside the App Store, but it’s also essential for a beta. Our users will always have access to the latest features if they opt-in to these updates. Still, the server could go down for a weekend and no one’s work would be interrupted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reaching the home stretch
&lt;/h2&gt;

&lt;p&gt;Meanwhile, Nicole’s work in the field was yielding some interesting feedback.&lt;/p&gt;

&lt;p&gt;In her capacity as an expert in organizational design and workplace culture, Nicole spends a lot of time training leaders in the basics of good management. She had this slide:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-3.24.19-PM.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P0F3Eoy6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-3.24.19-PM-1024x590.png" alt="Spreadsheet depicting a grid of multiple categories of data to collect during 1:1 meetings."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;People were grasping at it more than anything else in her trainings. Just a simple spreadsheet, but it offered so much clarity about how to consistently do 1:1 meetings. Weeks after their first introduction, people were still using spreadsheet as part of their ongoing work.&lt;/p&gt;

&lt;p&gt;We had our product.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--59C4_V4F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-3.52.12-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--59C4_V4F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-3.52.12-PM.png" alt="Screenshot showing Digamo's note input interface&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From there, we dumped the complexity of calendar syncing and meeting prep. Our laser focus was one thing: supporting 1:1 meetings. Setting expectations for the topics people should cover, helping them keep track of how often they discuss things like feedback, professional goals, time off, major wins, and more—not to mention helping our users dig up those discussion notes later on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XqtpWhwg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-3.38.02-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XqtpWhwg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-3.38.02-PM.png" alt="Screenshot of Digamo's sentiment capture interface&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nicole’s spreadsheet also asked people to keep track of sentiment, and we worked together to find a structure for this fuzzy data so that we could surface it in the UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8hwnmK_G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-3.39.00-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8hwnmK_G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-3.39.00-PM.png" alt="Screenshot of Digamo's emoji timeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each teammate gets their own emoji timeline, showing the progression of their sentiments over time. We can also give you a bird’s-eye view of sentiment across your team at any given moment, as the teammate selector list view displays a summary of the most recent entry. That view also flags any teammates who have been neglected in your 1:1 meetings, always showing people with meetings furthest in the past at the top of the list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WIV5RHda--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-3.40.38-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WIV5RHda--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-3.40.38-PM.png" alt="Screenshot of Digamo's teammate list"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Emojis are subjective, so it’s possible to set your own preferences for them, too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R7emWZRY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-4.13.33-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R7emWZRY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-4.13.33-PM.png" alt="Screenshot of Digamo's emoji preference panel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With all this data structured, it’s easy to retrieve later on. Just choose a teammate, a time frame, and the notes you care about, and Digamo will generate a summary. We can also do the same with your whole team. We know how much work performance review season can be. We’re excited to see how this feature helps streamline those processes, reminding you of the full picture of your team’s challenges and accomplishments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZyrG3Ynw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-3.49.38-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZyrG3Ynw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2019-10-28-at-3.49.38-PM.png" alt="Screenshot of Digamo's summary generator."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So that’s Digamo. It’s your private, digital notebook for recording 1:1 meetings. Thanks to the &lt;a href="https://vayaconsulting.com/team"&gt;Vaya Consulting team&lt;/a&gt;, and especially our friend &lt;a href="https://twitter.com/nickbedo"&gt;Dr. Nick Bedo&lt;/a&gt;, for the support in getting this out the door.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hWtywRm1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2020-02-07-at-4.03.51-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hWtywRm1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://frisco-uplink.com/wp-content/uploads/2019/10/Screen-Shot-2020-02-07-at-4.03.51-PM.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hidden in the product are a handful of deliberate bias mitigation strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Summaries help mitigate recency bias, surfacing details you might have forgotten in the day-to-day maelstrom of surviving work.&lt;/li&gt;
&lt;li&gt;The teammate list really wants you to see anyone whose 1:1’s you’ve been dodging.&lt;/li&gt;
&lt;li&gt;By collecting your sentiments, in addition to those of your 1:1 counterparts, Digamo can help you compare how you’re engaging with one teammate versus another.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What’s next?
&lt;/h2&gt;

&lt;p&gt;There’s so much more we want to do with Digamo, but it’s time to get it out into the world. High on the list are things like automated backups, sync with other devices, and a return of calendar sync. We’ll get there. Meanwhile, we’ve got the start of something a lot of folks have been asking us for. We hope the structure and conveniences provided by this initial beta will be helpful in your daily work.&lt;/p&gt;

&lt;p&gt;While a lot of the people we know use a Mac, we know a macOS app doesn’t cover the full spectrum of people who need this product. We’ll be thinking about how to deliver this more broadly in the future.&lt;/p&gt;

&lt;p&gt;Meanwhile, Digamo is free to download and use while we develop it further. Download it &lt;a href="https://digamo.app"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>projects</category>
    </item>
  </channel>
</rss>
