Generating and sharing a markdown file in SwiftUI and SwiftData

Colin Wren
4 min readJan 10, 2025

--

The Data Export functionality in action

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.

Generating a markdown string and saving it

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:

  1. I create the markdown document on every initialisation of the view with the ShareLink button
  2. 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.

Wrapping UIActivityViewController to use it in SwiftUI

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 .

Showing ShareSheet on data being generated

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.

The sheet automatically dismissing itself

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.

Hoisting the ShareSheet to parent view stopped the ShareSheet dismissing itself

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.

UX improvements to inform user that the export is generating and handling errors

Lastly added a presentationDetents([.medium]) modifier to the sheet as I didn’t want the share options to obscure the app when presented.

Summary

The final product

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.

--

--

Colin Wren
Colin Wren

Written by Colin Wren

Currently building reciprocal.dev. Interested in building shared understanding, Automated Testing, Dev practises, Metal, Chiptune. All views my own.

No responses yet