Arboretum SwiftUI/TCA app
quick sample app using The Composable Architecture, Swift, and Realm
It's been a couple years since I got to work professionally on Swift applications, and I have to say, I miss it! The language has always been super attractive to me as someone who prefers to more defensive and opinionated environments.... yeah, I kinda like thinking about memory!
So I'm looking to get back into the Swift game, but the language has released a lot of updates since I last used it professionally on the GiantEagle grocery app (oddly under two different companies, Giant Eagle themselves and Hatched Labs. Not worth getting into here but basically the former bought the latter and moved all their mobile dev over to them).
I've had to watch from the sidelines (& with little test projects to keep up) as Swift finally got async/await syntax among so many other features. Even SwiftUI finally became more viable as a framework, something we only got to experiment with at GiantEagle/Hatched (our application was built on UIKit and Auto Layout).
Then there was The Composable Architecture! I had been hearing a lot of rumbles about this redux-elm-like, swift-first framework and I wanted to try it out since we were using RxSwift at GE/Hatched.
So over the recent holiday I set off to make a tiny complete application with SwiftUI and The Composable Architecture, using Realm for local data storage. The choice to use Realm was another attempt to broaden my experience – I haven't used it in so many years and definitely not in a uni-directional Swift application. In hindsight I wish I could've spent more time understanding how I could cleanly fit Realm in a bit better, and that's something I definitely look to improve after this initial work.
I grew up in State College PA, so I have always been a big fan of The Arboretum at PSU. In an often over-crowded campus this is a beautiful reprieve and the staff works really hard to make sure it's always alive and changing. In the last few years they opened their Pollinator Garden which was an insanely large and incredible expansion of an already inspiring space. It's one of the last great human-managed things in State College to check out – if you get a chance you must go.
The Arboretum also served as a great subject for this little micro-app project. The staff at The Arboretum keeps meticulous amounts of data on their collections and have a nice interactive ArcGIS/ESRI map with all the plants mapped out on their property. Opening that map and looking at the network tab of your browser will expose the actual requests for the map data, here's the url of the request I ended up using:
https://apps.pasda.psu.edu/arcgis/rest/services/PlantFinder_Combined_Accessions_01212019/FeatureServer/0/query?f=json&returnGeometry=true&spatialRel=esriSpatialRelIntersects&geometry=%7B%22xmin%22%3A-8668570.5047493%2C%22ymin%22%3A4983694.245496575%2C%22xmax%22%3A-8667959.008523071%2C%22ymax%22%3A4984305.741722804%2C%22spatialReference%22%3A%7B%22wkid%22%3A102100%7D%7D&geometryType=esriGeometryEnvelope&inSR=102100&outFields=*&outSR=4326&resultType=til
Actually, that request is slightly modified. The value for the query param `outSR` has been changed from its original value to `4326`, which signifies that we want our returned data to be projected into Web Mercator space (id found here). The map as it exists on the web is projected in NAD 1983 space, which makes sense since that projection seems to be focused on the continental United States and the Arboretum map has no need for accuracy outside of that.
But, alas, iOS maps (like many other tools) project in Web Mercator space. So I had to look up that proper projection id to make sure all of the map annotations didn't appear dozens of meters away from where they should be when I went to add them to the iOS map.
After about a week of poking at this in between holiday plans and family visits, I did complete the app. It's not published on the AppStore or anything since it is really just an educational tool for me, but perhaps I will add more polish and release it some day. Check out the code and some screen grabs below, if you're interested in a little more info on some choices, features, and challenges keep scrolling past that.
The app uses the latest/new SwiftUI Map to drive the main exploration view. There was one main challenge with this, however, just in that the Arboretum has a lot of plants on their Plant Finder map...my dataset is even currently missing that giant pollinator garden I mentioned earlier, but still drawing all the markers on the map at once was causing some serious performance issues. You wouldn't notice it too much scrolling around the map or zooming in/out but there were two very nasty side effects. The main issue was that tapping on a map annotation seemed to be causing the map to do a redraw pass on all of its annotations before my navigation code was able to execute, causing there to be a multi-second delay between an annotation tap and seeing the plant detail view. The less show-stopping but equally annoying issue was that on the initial draw of all the annotations the map seemed to take a broad and then narrow phase of actually placing the annotations, causing items to be drawn in the wrong spot on app load before seemingly re-drawing and correcting itself. As a quick sanity check I reduced the amount of items being drawn from almost 3k to 10 and those bugs were gone so I knew what the culprit was for sure. I was not able to test if annotation clustering would reduce the problem since it is not yet supported on the new SwiftUI maps, but I have a suspicion it wouldn't have helped much since the underlying mapkit logic would still have to process all that data to visually cluster it.
The only way I could quickly solve this performance problem was simply to limit the user to being able to look at one area of the Arboretum at a time. The data I ripped from the web map included a location
field for the plants that denoted (by simple string, unfortunately) where they were within the Arboretum. I added a navigation bar drop-down control so the user could select which area to view and the map would only draw the markers for that area. This change significantly helped those major performance issues... unfortunately some of these locations still contain a lot of data (looking at you, Children's Garden) so there is still sometimes a small but annoying nav delay for detail pages in those more dense areas but nothing close to what it was before.
Once that was all sorted I was able to try and put Realm in for the map data. I created a main AppView/Reducer to host the TabView and read the actual data, then write it to Realm. This way the MapReducer didn't have to interact with that data layer at all, and instead subscribes to the Realm collection for the map items and just draw them, which felt a lot cleaner and makes app start up super snappy after the first open. I couldn't quickly find a lot of examples online for integrating Realm with the latest Composable Architecture, so I'm hoping to think more on this and improve the code to be more readable and extendable over some more time....but it seems to work for okay now!
I also used Realm to store some kind of "User Settings" and some idea of the "Last Session State" so that I could allow a user to decide what portion of the Arboretum map to load on app-start, which felt like it would soften the blow of not being able to see all the data at once. The user can decide to always open the map on the last area they selected, or if they'd rather, always have it open on a specific location. The latter is a little brittle since the data is all scraped and the location
field in there are just "magic strings", but I have to work with what I got. In the case of mis-aligned or missing location data the app should just fall back to using the last selected location since that is always tracked in the separate "Last Session State" Realm object.
And that's about it for now! I have a couple of open issues on GH as of writing this for both bugs and ways to improve the experience...hopefully I'm able to do so soon!