Note: This app mainly served as a prototype and has been put on hold in favor of a new one built with Flutter. See it here, and if you're interested in why this decision was made, read below.
A Twitch client for iOS built with SwiftUI (and some UIKit).
Unfortunately, SwiftUI is currently not fit to support an effective Twitch chat. Until Apple releases performant layouts similar to span (HTML) or flex wrap, this app won't be able to achieve a consistent chat. The issues described next prevent this app from reaching its full potential and have made me put it on hold.
- When constructing a chat message containing various dynamic assets, each item (text, badges, emotes, and GIFs) is contained in a view.
- These views need to be placed horizontally and wrap when the container width is exceeded in order to emulate a properly formatted chat message.
- SwiftUI has the HStack view layout, but it does not support wrapping. Inline images are supported through Text but only static images, not GIFs, will work.
- FlexLayout was introduced as a workaround, but required the use of some UIKit through UIViewRepresentable, resulting in unforeseen side effects such as having to manually size the view by overriding the intrinsicContentSize. This would sometimes cause chat messages to overlap each other and long strings of text to wrap in their own container.
- Each view is created as a new instance, so GIFs will always start at their first frame when appearing.
- A possible workaround is to keep track of the current frame for each GIF and start playing the GIF from there, but again it's another workaround and would require a bit of overhead.
- Since emotes first have to be fetched before being displayed and cached, there will be a blank placeholder image view until the image request is complete. Once the request is complete, the image will fill in the space of the placeholder.
- The placeholder image view can only have a predefined size, but with so many emotes having varying widths and heights they won't fit into the view perfectly. When the emote, such as one that is very wide, is finally loaded and displayed, it will appear warped and smaller than anticipated.
These issues may be fixable, but would lead to a workaround-rabbit-hole that I've already gone far enough into and don't have enough time for. This led me to look into other declarative mobile frameworks with iOS support, specifically React Native and Flutter.
While experimenting with React Native (RN), issues 1 and 2 persisted. This was somewhat unsurprising as RN uses native components under the hood. Although RN does have flex wrap, span was still necessary to have elements truly inline with text. As a result, the prototype was promptly scrapped.
While working with Flutter, it was clear that the team wanted to bring web-specific behaviors to mobile. Features like TextSpan/WidgetSpan, synced GIFs, height-only image sizing, and more are core features of the framework.
Since I was trying to emulate the web Twitch chat experience on iOS, it made perfect sense to take advantage of these features. Having the app work identically on Android through the Skia engine was a neat bonus too.
Flutter is not the perfect framework by any means and has its own issues and caveats, but it's the best framework to achieve the goals of this app. See the Flutter version here.
- Nuke & NukeUI
- Caches thumbnails, badges, and emotes.
- FlexLayout
- Lays out text and emote views with wrap and align.
- KeychainAccess
- Stores user tokens securely.
- FLAnimatedImage
- Enables GIFs.