swift posts

Subscribe to just swift posts via RSS.

Parsing HTML Slower

Last time, I wrote about how to parse HTML and convert it to NSAttributedStrings quickly. Unfortunately, in the time since then, it’s gotten slower. It’s still a good deal faster than it was before all that work, mind you. At fault is not any of the optimizations I discussed last time, fortunately. Rather, to get the correct behavior across a whole slew of edge cases, there was more work that needed to be done.

The root of all this complexity is the fact that I’m essentially trying to replicate a portion of the CSS layout algorithm using only the information provided by the HTML tokenization process (that is, the text that is emitted and the start/end tags) while flattening into a single string all the structure used to achieve those results.

Parsing HTML Fast

The urge to overengineer something recently befell me, and the target of that urge was the way Tusker handles HTML parsing. Mastodon provides the content of posts (and profile descriptions, and some other things) as HTML, which means that, in order to display them properly, you need to parse the HTML, a notoriously straightforward and easy task. For a long time, the approach I took was to use SwiftSoup to parse the HTML and then walk the resulting node tree to build up a big NSAttributedString that could be displayed.

And while that technique worked perfectly well, there were a number of downsides that I wasn’t entirely satisfied with. First: SwiftSoup alone is a big dependency, at around ten thousand lines of code. Since it’s shipping in my app, I take the view that I’m responsible for making sure it doesn’t break—and that’s a lot of surface area to cover (moreover, since it was ported from a Java library, the code is extremely un-Swift-y). Second: it’s not especially fast. Partly because of its aforementioned nature as a Java port (everything is a class, lots of unnecessary copies), but primarily because it’s just doing more than I need it to (the first rule of optimization is do less work): it was parsing a string into a tree and then I was flattening that tree back into a string.

The last time I needed to do something similar, I used lol-html, Cloudflare’s streaming HTML rewriter. But, while that worked (and is still in use for my RSS reader app), it’s written in Rust, and I didn’t want to introduce significant additional complexity to the build process for Tusker. So, writing a new HTML parser in Swift seemed like a great idea.

Theming iOS Apps is No Longer Hard

Well, at least not for the same reasons. I figured I’d write a brief follow-up post, but unless you’ve been living under a rock, you’ll have heard that UIKit gained support for custom traits in UITraitCollection with iOS 17. They work very similarly to SwiftUI’s environment and are exactly what I wanted.

A Hero View Controller Transition in SwiftUI

Out of the box, SwiftUI has a matchedGeometryEffect modifier that makes it relatively easy to build hero transitions (a style of transition where a new screen is presented and part of the source screen changes position and size to reach it’s place on the new screen). It’s cool that SwiftUI includes this out of the box, but unfortunately it has a few limitations that make it unsuitable for certain use cases. Particularly for me, that it doesn’t work with presenting another view. Most examples on the internet[1] work around this by faking a custom presented view: just slap a full-screen background color down and show your content on top of it. That’s essentially the same as a presenting a full-screen view, with the one major caveat that the pseudo-presented view can only cover the containing hosting controller. And if that controller isn’t full-screen (say, if it’s presented as a sheet), you can’t present anything that’s truly full-screen. So, let’s build a custom hero transition that actually presents it’s content across the entire screen.

Theming iOS Apps is Still Hard

Sorry to be the bearer of bad news. Last year, Christian Selig wrote a blog post about the annoyances of theming iOS apps. I won’t retread his entire article, but the gist of it is that there is no nice way to easily apply a theme to an entire iOS app. Either every view/controller has to listen for theme change notifications and update itself, or you have to resort to hacky workarounds to force all colors to update[1].