Generating and sharing a markdown file in SwiftUI and SwiftData
When planning the development of Garner, my career focused reflective journalling app’s MVP I knew I had to include a means for users to export their Reflections from the app.
Sharing and utilising one’s Reflections is a key component of Garner’s Reflect, Grow & Thrive cycle, it means that the habit of reflecting not only helps the user identify areas of growth but it allows them to provide evidence of that growth in their CV or in their reviews at work.
In order to keep the MVP scope as light as possible I settled on a very basic data export that would create a markdown document that could then be either saved to the file system or shared with another app or service using the iOS share options.
My reasoning behind using markdown was that it strikes a nice balance between structured information and readable information, making it easier to work with either manually or via some automated process.
The implementation of the data export was split into two tasks:
- Generating the markdown document
- Adding the UI to trigger the document generation and sharing
Generating the markdown document
The generation of the markdown document is relatively straightforward, and involves fetching all the Reflection records and then iterating over them in order to build a big String
that could then be written to disk.
In order to write the String
to disk I first needed a URL
which I got using FileManager.default.temporaryDirectory.appendingPathComponent("filename").appendingPathExtension("md")
to generate a new file in the device’s temporary directory.
Once I had the URL
to for the file to write the String
to it was just a case of using the write(to: URL)
method of String
to populate the file.
Adding the UI to generate and share the document
In theory the UI to generate and share the document would be really simple, in iOS 16 SwiftUI introduced ShareLink
which takes a URL
and shows the iOS share options when pressed.
That theory however didn’t hold up for me as ShareLink
only works with URL
s that exist already, you can’t generate the URL
and the data at that address when the ShareLink
is pressed and then have it show the share options.
SwiftUI doesn’t have a separate “share sheet” view that can be used outside of the ShareLink
approach so I had to decide on how to tackle this problem, I had two options:
- I create the markdown document on every initialisation of the view with the
ShareLink
button - I could find a means to create the markdown document when the data export is kicked off and programmatically trigger the share options to show
I didn’t like the idea of generating the markdown document even when it wasn’t going to actually be used so I looked at how I could create an asynchronous generation and trigger the share options after the markdown document was created.
After a bit of digging it looked like it was pretty standard practice within SwiftUI before the introduction of ShareLink
to wrap UIKit’s UIActivityViewController
in UIViewControllerRepresentable
and using a boolean flag to control if it’s shown and using that as part of a .sheet
modifier within the SwiftUI view.
This worked, I was able to have a button that when pressed would create the markdown document, save it to the tempory URL
and then set some state to show and pass that URL
into the UIActivityViewController
.
There was a bug however — where I had contained all the logic to handle the generation and sharing of the document in a subview when the UIActivityViewController
was shown it would be dismissed immediately the first time the view was rendered.
To fix this I had to hoist the UIActivityViewController
up to the parent view and use @Binding
variables in the subview to set these values as part of the generation within it but have the parent view manage showing the share options.
I also added some extra state within the subview so that the UI gave the user some feedback that the generation was in progress as the time taken to generate the document would be dependent on the number of records.
Lastly added a presentationDetents([.medium])
modifier to the sheet as I didn’t want the share options to obscure the app when presented.
Summary
While I wasn’t able to make use of the convenient ShareLink
view that iOS 16 introduced I was able to add data export functionality to Garner so users can utilise the collection of Reflections they’ve been making over time and use them to update their CV to find a new job or as part of a review in their current job — a core component to the Reflect, Grow & Thrive cycle that the app is based on.