Angelo Stavrow [dot] Blog

Missives and musings on a variety of topics.

A little bit of a UI/UX deficiency I've found when using Swift Package Manager in Xcode 12 β2 is that adding a Swift package to a multiplatform (iOS/macOS) SwiftUI app requires you to choose a target for the package:

"Add Package to App sheet in Xcode showing a target selection menu"

That means that if you have a platform-agnostic package that you import, and try to use it for both the iOS and macOS targets, you'll invariably run across an error (”No such module 'ModuleName'”) when you're writing code against the target you didn't add it to in the above step, and your project won't build for that target.

To fix this, Stuart Breckenridge shared the following tip: go to the Build Phases tab for each of your targets, and make sure it's added under Link Binary with Libraries:

"Target Build Phases settings in Xcode"

Build the app, and you'll be good to go.

I've filed FB8094575 to improve this user flow.

#spm #swiftui #swift

Discuss...

Today, I published the v1.4.0 release of Indigo, my theme for the Hugo static site generator (which powers this site as well).

What's Web Monetization?

Web Monetization is a draft Web API spec that lets the user agent (i.e., your browser) stream payments to a website. I got to speak with a few people on their team, and the idea of a privacy-focused, patron-driven way for creators to generate revenue really spoke to me.

I then promptly got caught up in a bunch of other stuff, so when a couple of posts that were recently shared by Chen Hui Jing (1, 2) showed up in my feed reader, it reminded me that I wanted to add it to Indigo (as well as my own site).

Hui Jing explains it all in great detail, but in essence, the way this works is as follows:

  • Someone signs up as a creator with a web monetization provider, like Coil, and creates a wallet with an associated provider.
  • The wallet provider generates a payment pointer for the creator.
  • The creator adds this payment pointer to a specific <meta name="monetization"> tag to the head of their site.

Once that's set up, anyone that's got a Coil subscription and is using a compatible browser will now micropayments to that creator whenever they visit that site. Hui Jing's site is monetized, as are some others you may know.

How to enable web monetization in Indigo

First, make sure you update the theme to v1.4.0, just as you would update any Hugo theme:

$ cd path/to/your/hugo-site
$ cd themes/indigo
$ git pull origin v1.4.0

Then, open your site's config.toml file and, under the [params] section, add:

paymentPointer = "$your.payment.pointer/here"

Build and deploy your site, and you're ready to go! If you've got any comments or questions, don't hesitate to reach out and let me know.

#indigo #projects

Discuss...

Just because I closed out the Per rewrite journal in late March, doesn't mean I'm not still working on the app! Here's what's been happening.

Minimum Dogfoodable Product — 2020-04-30

At the end of April, I closed out last issue in the Minimum Dogfoodable Product (MDP) milestone. This represents the first internal release that gets the rewritten app to feature parity with v1.2, the currently-shipping version in the App Store. At that point, Christina and I replaced the shipping version of the app on our iPhones to start, as the milestone name implies, dogfooding it.

Minimum Launchable Product — 2020-07-06

With that done, I moved on to issues in the Minimum Launchable Product (MLP) milestone. This release represents an internal beta that collects bugfixes from using the MDP along with a set of launch-blocking features to be implemented before releasing the first external beta. That work wrapped up yesterday, so now we're on to the Minimum Viable Product (MVP)!

Minimum Viable Product — TBA

The MVP, once work is complete, will be the first externally-available (via Testflight) release of the app, and includes all features planned for the public release. The plumbing and framing was completed in the MLP work, so now —new bugs notwithstanding— it's about UI and UX polish. I've been building Per with mostly-stock UIKit components and typography, such that I get things like accessibility and dark-mode support (mostly) for free. But as Christina's pointed out, there's lots of little UX improvements that can be made, and a little bit of UI/personality would be nice, too.

So, New Features?

You've already seen one of the new features: increasing the number of compared products beyond two.

New features will, for the most part, live behind a small in-app purchase. Because the current version is free, I don't want anyone to lose out on existing functionality; free users will get to compare up to three products, with paying customers being able to compare unlimited products (or some technologically-constrained number that approximates “unlimited”).

The in-app purchase will also unlock something I've personally been wishing for: a way to save a product for later. Like many, I know where to go to get the best price on certain staples, but I don't always remember exactly what that price or quantity is. So, when I come across some sale on that product at another store, I can't be sure if it's a better deal or not. 🤔

Well, with the next release of Per, you'll be able to save that information for later. It's been a fun feature to implement, including setting up persistence with SQLite/FMDB, but it still needs some more work to get it where I'd like. I hope you'll find it useful!

Will there be other features? Yes, eventually, but probably not for this release. So, as always, more TK.

#per #perRewriteDiary #ios

Discuss...

It's been a while since I posted an update here, and I wanted to announce a new project: a WriteFreely Swift package that you can drop into your Mac and iOS apps.

You can find it here: https://github.com/writeas/writefreely-swift

What's WriteFreely?

WriteFreely is an open-source platform for writing on the web. It powers the Write.as service (find me here!), and lets you build writing communities on the web.

I was introduced to Write.as and WriteFreely while working with Glitch, where I got to chat about the service and its principles with Matt and CJ. Matt wrote a thoughtful post on Glimmer about the importance of privacy for creating on the web, and CJ built a tonne of cool sample apps that connected to the Write.as service for their Glitch team.

Cool, Tell Me More About The Project

This project represents a couple of firsts for me:

  • This is the first time I've worked on a Swift package
  • This is the first time I've wrapped a RESTful API in Swift
  • This is the first time I've worked with URLSession and Result in Swift

Right now, it's an alpha/developer-preview release. There's a lot of room for improvement here, and I'm looking forward to working towards a 1.0 with the WF community.

As I mention in this forum topic and my Write.as post, the design for the WriteFreelyClient is to leverage completion blocks that return a Result tuple with either a User, Post, or Collection (or an array of these types where that makes sense), or an Error on failure. That makes it pretty easy to build completion handlers:

func loginHandler(result: (Result<User, Error>)) {
    do {
        let user = try result.get()
        print("Hello, \(user.username)!")
    } catch {
        print(error)
    }
}

guard let url = URL(string: "https://your.writefreely.host/") else { fatalError() }
let client = WriteFreelyClient(for: url)
client.login(username: "username", password: "password", completion: loginHandler)

What's Next For The Project?

Good question! There are definitely some major to-do items that are obvious to me:

  • Add a test suite (there will be some refactoring required to facilitate this)
  • Create generic-ish request templates to DRY out the WriteFreelyClient public methods
  • Extend for use with the Write.as platform

Mostly, though, I'm excited for people to try it out and let me know how it works for them!

#writeas #projects #swift

Discuss...

I set up a TiddlyWiki on Glitch for myself — prompted by watching this talk by Sönke Ahrens yesterday. I'm very much intrigued by the idea of digital gardens or the idea of a public Zettelkasten, but I'm neither experienced with this style of note-taking nor quite certain what all I should capture there.

Maybe the best plan is to have no plan: for now, I'll treat it as a “Today I Learned” (or “TIL”) catch-all for notes, apply tags indiscriminately, and see what comes together. I don't know if TiddlyWiki is the right tool for the job, but everything I've read so far is compelling.

More TK.

(H/T: Jack Baty)

#glitch #til #notes

Discuss...

I kicked off a new weekly “development diary” series on Dev.to today.

This post is an entry in a weekly development diary on building a feed-aggregator-based blog on Glitch. Only a small part of building an app is code; the bulk of your time is spent planning, experimenting, making mistakes, getting frustrated, and making progress in baby steps.

You can read the first post here. The goal is to build a web app that takes as input an OPML file of your RSS feeds across various platforms, and automagically generates a blog with its own “firehose” RSS feed with that content.

I really found it helpful when I journaled daily while rewriting my iOS app. I hope you find it interesting too.

#glitch #devto #elsewhere

Discuss...

This post is part of a series about rewriting my iOS app, Per. Per is a price per unit comparison app with a bunch of neat convenience figures, but it hasn't been updated in years, so I'm rewriting it from scratch to eliminate a bunch of technical debt. Just because it's not an open-source app doesn't mean I can't share what I learn as I go!

See the rest of the series here.

A Recap

Today is the fiftieth —and last— entry into this daily development diary. I'm not going to be discussing further work on Per v2 here, because at this point the work I'm doing is new features, and because I think it's probably getting tiresome for some readers.

(There are no analytics gathered on this site, so I don't know if readership has increased or decreased with this series.)

The goal was to get Per from a blank Xcode project to feature parity with the currently-shipping version in an hour a day, and to record my progress publicly as I went. I hoped to complete that work in February (so, 29 days). Non-goals included any kind of custom design work. As much as possible, I'm only using standard iOS 13 UI elements. That's not to say that custom design work won't be coming, but that wasn't part of this project.

Also, this rewrite is being done entirely in code — no Interface Builder work, no XIBs, and no storyboards (aside from the one required for the app's launch screen).

Without further ado, here's my takeaway on fifty days of public development journaling:

  • Public journaling means public accountability. While I technically took a day here or there where I didn't really post anything beyond saying, “I'm taking a break today,” I felt like I had to do something every day just because I'd set that expectation. I made more progress in these past seven weeks than I did in the last… ugh, has it really been four and a half years since the last update?

  • Limiting this to one hour (-ish) per day maintains forward momentum. Because I only dedicate an hour or so per day to this task, I have to make sure that I plan my work accordingly. I've tried to end every entry with a simple description of what I'll tackle the next day, a trick I use in my daily work. Knowing that I only have an hour or so to work on it means that I scope the work down to fit.

  • It's a record of my work. Writing it down makes it something I remember, but it's also something I can refer back to if I need to revisit insights. This is way more useful than comments in the source code. I only wish I'd thought to do something like a daily git commit with a link to the day's journal entry.

  • It can be stressful. Beyond the COVID-19 pandemic, there's a lot going on in my life right now, and this extra commitment has sometimes felt overwhelming. Those tend to be the days when I say, “yeah, I'm taking the day off.” Never trade your health in the name of “not breaking the streak.” But if you feel some capacity to do a little work, it's not a bad idea to do some catch-up and planning on days like that!

So, this has been fun, and very helpful. I'm going to continue working on Per, and I'll probably share something here now and again — but if you're interested in finding out more as the app gets closer to release, check out the Dropped Bits blog and Twitter account.

#per #perRewriteDiary #ios

Discuss...

This post is part of a series about rewriting my iOS app, Per. Per is a price per unit comparison app with a bunch of neat convenience figures, but it hasn't been updated in years, so I'm rewriting it from scratch to eliminate a bunch of technical debt. Just because it's not an open-source app doesn't mean I can't share what I learn as I go!

See the rest of the series here.

Just Stick It In A Stack View

I've discovered that —at least the way I'm calling things— no matter what I do, I just can't resize a UIPickerView after it's been instantiated, and instantiating it with the frame I want just gets ignored. I need to dig further into this because I'm clearly doing something wrong, but as I'm only working on this an hour a day, that's slow going.

So, in the spirit of making progress and not getting sidetracked with details that ultimately aren't that important, whenever I have to show my picker view, I call this method:

func showPickerView(frame: CGRect, sender: UITextField) {
      let stackView = UIStackView(frame: frame)
      stackView.axis = .vertical
      
      let safeInsetView = UIView(frame: CGRect(x: 0, y: 0, width: frame.width, height: view.safeAreaInsets.bottom))
      
      stackView.addArrangedSubview(pickerView)
      stackView.addArrangedSubview(safeInsetView)
      sender.inputView = stackView
  }

Fuck it. It works, so long as I set my calculator keyboard's frame height to 216 (the height of a picker view) plus the bottom safe inset area.

I hate magic numbers in my code, so I'll continue investigating this, but for now... it's fine.

I've decided that tomorrow will be my last day posting these. Fifty days of uninterrupted blogging... not bad. But I'm also pretty much at feature parity with the shipping version now, so I can start adding features instead.

So tomorrow, a wrap-up, and then back to your regularly scheduled programming.

#per #perRewriteDiary #ios

Discuss...

This post is part of a series about rewriting my iOS app, Per. Per is a price per unit comparison app with a bunch of neat convenience figures, but it hasn't been updated in years, so I'm rewriting it from scratch to eliminate a bunch of technical debt. Just because it's not an open-source app doesn't mean I can't share what I learn as I go!

See the rest of the series here.

Safe Area Insets

On Tuesday, I shared this image comparing the height of the two main input views (the calculator keyboard and the picker) in Per between an iPhone 8 and an iPhone 11:

"Screenshots from an iPhone 8 and iPhone 11 showing relative differences in input view height"

I could explicitly set the height of both input views to something like 240 instead of giving it a .flexibleHeight auto-resizing mask, but that didn't feel like the right approach. I asked about this in the Core Intuition Slack and Daniel Jalkut suggested I poke around in Xcode's view debugger to see what's going on there.

Here's what I've been able to surmise.

I layout a custom input view like CalculatorKeyboard as follows (it's really a set of nested stack views, but I'm leaving that out — details of the approach are from this Stack Overflow answer):

func setupView() {
    autoresizingMask = [.flexibleWidth, .flexibleHeight]
    addButtons()
}

func addButtons() {
    let stackView = createStackView(axis: .horizontal)
    stackView.frame = bounds
    stackView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    addSubView(stackView)

    // —code to layout all of the buttons goes here—
}

This basically just sets the frame of the custom keyboard to that of the system keyboard's bounds. Note that it doesn't do anything to add a safe area inset at the bottom of the keyboard. The view debugger tells me this is 375×291 on an iPhone 11 Pro, and 375×216 on an iPhone 8, a difference of 75 points.

I'm way less specific about setting the frame of the picker. In fact, I just set:

textField.inputView = pickerView

and call it a day. The view debugger tells me its size is 375×216 on both iPhone 11 Pro and iPhone 8 — the same size.

I'm getting closer here. I think part of this has to do with the intrinsic content size of the custom keyboard vs the picker, and also that I should probably be doing more in the parent view controller than just hoping things work out with autolayout in the views.

I'll dig deeper on this tomorrow!

#per #perRewriteDiary #ios

Discuss...

This post is part of a series about rewriting my iOS app, Per. Per is a price per unit comparison app with a bunch of neat convenience figures, but it hasn't been updated in years, so I'm rewriting it from scratch to eliminate a bunch of technical debt. Just because it's not an open-source app doesn't mean I can't share what I learn as I go!

See the rest of the series here.

Take Care Of Yourself

I'm taking a break from this work today because it feels like everything is falling apart right now and I need some time to sort out my own needs. That's way more important than not breaking the streak.

#per #perRewriteDiary #ios

Discuss...

Enter your email to subscribe to updates.