swift posts

Subscribe to just swift posts via RSS.

The Intricate Image Caching Architecture of Tusker

A fairly important part of Tusker (my iOS Mastodon app) is displaying images. And a bunch of varieties of images: user avatars, post attachments, custom emojis, user profile headers, as well as a few other types of rarely-shown images that get lumped in with attachments. And displaying lots of images in a performant way means caching. Lots of caching.

In the beginning, there was nothing. Then I started displaying images and almost immediately realized there would need to be some amount of caching. Otherwise, just scrolling down a little bit and then back up would result in images being re-loaded that were present mere seconds ago.

The very first implementation was super simple. It was basically just a dictionary of image URLs to the Data for the image at that URL. This fulfilled the primary goals of being 1) super easy to build and 2) mostly working for the simplest of use cases. But, the blog post doesn’t end here, so clearly there are some issues remaining.

Automatically Scroll the Text View Caret into View

That’s right, it’s time for this month’s installment of the never ending SwiftUI text view saga! The text view previously implemented is of course auto-expanding and has scrolling disabled. While this mostly works, it has a rather unfortunate UX problem. Let’s say the user is typing into the text view, and they reach the end of the screen. As they continue to type, the text will wrap onto the next line and the caret will go with it. But, because they’re already at the bottom of the screen (or immediately above the bottom of the keyboard), the caret, along with the text that they’re currently typing, will no longer be visible.

A UI Framework Dilemma

For the past couple of weeks I’ve been building Rocketeer, an iOS browser for the Gemini network.[1] The gemtext format is very minimal, so I thought it would be fairly easy to build something to render Gemini documents. The format is line-oriented and only allows a few different line types. There are regular paragraphs, link lines, 3 levels of headings, unordered list items, preformatted blocks, and block quotes. All of these are pretty simple, visually speaking, and the layout is also straightforward. So, I expected to be able to build a renderer quite easily. Unfortunately, there turned out to be lots of little details that were not so obvious at first and introduced a bunch of complications.

More SwiftUI Text View Features

In my last post, I went over the implementation of a custom SwiftUI view that wraps UITextView in order to make it non-scrolling and auto-expanding. That worked quite well, but in continuing the reimplementation of Tusker’s compose screen in SwiftUI, I ran into a couple more things I had to re-implement myself.

SwiftUI Auto-Expanding Text Views

I’m currently in the process of rewriting the Compose screen in Tusker to use SwiftUI. This has mostly been a smooth process, but there have been a few hiccups, the first of which was the main text box. The updates to SwiftUI introduced in iOS 14 included TextEditor, the SwiftUI equivalent of UITextView to allow multi-line text editing. Unfortunately, there’s no (straightforward) way of disabling scrolling, making it unsuitable for some designs where the text view is embedded in a separate scroll view. Additionally, the fact that it’s not available at all on iOS 13 means layouts that require non-scrolling multi-line text editors must wrap UITextView themselves in order to be achievable with SwiftUI.