Building Glyph: A Year With SwiftUI - Part 2

July 30, 2020

This is a followup post for Building Glyph: A Year With SwiftUI where I talked about some of the pros of SwiftUI after having used it for a year. There are a lot of advantages to SwiftUI, but in this post I'll cover some of the downsides.

Cons

The Built-In View Library Is Small

There were two big drawbacks to SwiftUI before iOS 14, and the first one is the lack of built-in views. SwiftUI has a lot of the foundational views that meet minimum requirements for building a UI, we have views for constructing layouts (i.e. VStack, HStack, ZStack), displaying data (e.g. Image, Text), and collecting inputs (e.g. TextField, Slider, Toggle, Button). You can get pretty far with just this foundational set of views, but there are many views from UIKit that have no equivalent in SwiftUI. Here's a collection of views I've found on various developers' wish lists:

  • A VideoPlayer view
  • An equivalent for UIActivityIndicatorView
  • A GridView or CollectionView to complement the List view.
  • PDFView
  • MetalView for views backed by metal rendering
  • TabView
  • A MapView for displaying map/geo data
  • A multiline text editing view (TextField only allows one line)

These are all admittedly "nice-to-have" views since most of them can be custom built using the foundational views and a UIViewRepresentable wrapper around UIKit. The good news is several of these wishlist views have been implemented in iOS 14, including VideoPlayer, LazyVGrid, LazyHGrid, and TextEditor.

State Management

SwiftUI allows you to define "view states" using the @State or @ObservableObject property wrappers. When a state object or property on a state object changes, the view automatically updates. SwiftUI also caches views whose properties haven't changed, so overall it's very efficient with view updates. This works great until you have to deal with persistence or your application state starts to get large.

A common practice with frontend web development is to start with one global "AppState" object that is passed to every view. But eventually you run into performance bottlenecks because any change to the global AppState triggers changes to every view. Here's a seemingly innocuous example of a slider that changes the brush size in Glyph:

 Slider(value: $state.brushSize, in: 1...100, step: 1)

But a user moving the slider can trigger dozens of updates on the global state which updates most of the UI each time. SwiftUI is very fast so you won't notice for small projects. For larger ones, you can start to deconstruct your global state into smaller state objects. It would be nice to have a way for views to subscribe to specific properties instead of an entire object since most of my views don't need to update on brush size changes.

In general there's a lot of lacking documentation about how state management works in SwiftUI and how to handle advanced use cases. I've also been unable to find a good set of practices for handling persistence, it is possible to integrate CoreData with SwiftUI but I'm hoping Apple comes out with a more elegant solution.

Summary

If you haven't already, check out the first post on Building Glyph: A Year With SwiftUI for the pros. Overall I've found SwiftUI to be a fantastic upgrade to UIKit. It was almost production-ready for Glyph with iOS 13, but with iOS 14 I'll be able to finish the app and launch in September.

I would highly recommend anyone doing iOS development to start considering an incremental transition to SwiftUI. It will be impossible for many developers to build their apps 100% in SwiftUI for a while longer because of the small view library, but Apple's APIs for bridging between the two frameworks are well designed so it's easy to start swapping out UIKit views with SwiftUI ones.