Cross Platform Swift Apps with Native UI


Hey All!

Every year, I build an App for Wicked Woods Music Festival in British Columbia, Canada. The schedule release is happening on Monday, so I figured I'd share some of the technical details with you guys.

This year I took on the inadvisable task of rewriting the entire app. It was finally theoretically possible to use my desired PointFree ecosystem and toolchain. I've been a Composable Architecture purist since the early days, but i have a non-negotioable Android requirement for the festival.

TCA 2.0 compiles for Android, along with other critical libraries like swift-sharing and sqlite-data. I've been holed away all summer, but I'm happy to announce that I've succesfully got the entire toolchain working, with a singular TCA26/SQLiteData codebase that builds to iOS and Android, with native SwiftUI and Jetpack Compose UIs.

The big lift for this (other than a whole stack rewrite of the app into TCA2.0), was true swift-observation support. Skip had a custom Observation implementation, but this did not support 3rd party libraries that participate in Observation, but don't know about SKIP. SKIPs Observation shim had two assumptions:

  1. Classes marked with @Observable needed to be in a skip-fuse module
  2. Classes that correctly conform to the protocol, but did not apply the @Observable macro directly were unable to participate

TCA/SQLiteData have correct implementations of the Observation protocol, but don't use the Macro, and should not need to gain knowledge about SKIP to properly emit Observation events.

Observation does (and always has), provided the tools so that UI frameworks like SwiftUI can subscribe, and be notified when those values change, in order to trigger a recompute of its body, and display the new changes.

Under the hood, SwiftUI inserts something like this into every View

@Observable
class Model {
   var count = 0
   func increment() { count += 1 }
} 
let model = Model()

@State var id = UUID() // generated
var body: some View {
	withObservationTracking { // generated
		 Text("\(store.count))
	    Button("+") { store.increment() }
	} onChanges: {
	   id = UUID()
	}
}

Every time the body is evaluated inside withObservationTracking, it subscribes to all the values on the store (and other observable models) that are touched within that closure (just count in the example)
When that value changes, the onChanges closure runs, updates the @State variable, and reevaluates the body with the new data.

withObservationTracking is platform agnostic, and slides cleanly into the SKIP architecture. The skipstone plugin is what translates SwiftUI views to Jetpack Compose, and creates a new "View" surface for each view it finds. I've locally upgraded skipstone to install this at each view boundary.

I've been exercising this in some simple apps, but mostly through the Wicked Woods app (and a global app thats coming soon).
The app is complicated, with a lot of features. It relies heavily on sqlite database observation so that db changes are reflected immediately in the UI.

I've been poking at it for awhile, and it seems like true Observation is doing it's job.

Excited to share this with you all, and hopefully we can start discussion about how this could land in the eventual main SKIP toolchain.


Log in to leave a note.