“Sharing is caring” is a phrase my four-year-old son delights in saying – usually as I’m about to indulge in something sweet. We’ve learned over the last couple of years that our iOS users care about sharing too: support for sharing notes from the Bipsync Notes iOS app has been one of our most requested features.
To that end we’re pleased to announce that as of version 1.15.0, which is available in the App Store today, notes can be saved as PDF files and exported via the native iOS share sheet to a multitude of destinations.
This means that if you’ve ever wanted to email a note about an earnings call to your Portfolio Manager right from your device, or need to print a model out to scribble over, we’ve got you covered.
The native share sheet also supports sending notes via iMessage, the system clipboard and iCloud Drive, as well as a host of third party apps. (We’re also able to disable this feature for more conservative funds).
Here’s how you share a note:
We think that the ubiquity of compatible readers makes the PDF file format a perfect way to make distributed notes accessible to a wide audience.
That said, there are some improvements we’re planning such as being able to send a note as the body of an email instead of being a PDF attachment to an email. We’re also considering alternative export formats, such as Microsoft’s docx, and would love to hear from our users regarding any specific requirements they may have.
If you’re not interested in the technical implementation of this note export feature, go ahead and download the new version of the app right now and give sharing a try! If you would like to learn about the gory details however, read on!
Sharing a note
The process of preparing a note for sharing can be broadly broken down into three steps:
- Rendering the note as HTML with a specialised “print” layout
- Converting the HTML to PDF and saving the result to a file
- Integrating with the native iOS share sheet to send the PDF to a Sharing or Action extension
Let’s discuss each in turn.
Rendering a note as HTML
All notes in Bipsync are represented as HTML. You could say that it’s the common denominator of data formats in our system, as lots of our apps and services use HTML to consume or display research content. Since it’s an open format there are plenty of tools which can transform HTML content into alternative formats, and the flexible nature of the markup means it’s possible for us to augment any data with our own in the form of proprietary tags and attributes.
When it comes to sharing a note however, it’s not as simple a process as taking that HTML and handing it over to the user.
Each Bipsync application has dedicated stylesheets which tailor the way a note is displayed to suit the medium. For example the iOS app is careful to scale large elements like images and tables to account for the smaller screen sizes of handheld devices. The desktop and browser-based apps each have their own concerns.
The considerations involved with rendering a note specifically for sharing are no different. We want to change the output stylistically now we’re rendering to a PDF document, and not to a screen. We also want to include additional content that isn’t included in the body of the note, like the author’s name, the date the note was created, and any tags that have been assigned to it. This requires a whole new layout and style that’s separate from that which we use to show a note within the host application.
Once we’re happy with the layout and appearance of the rendered note we need to somehow load it within the app. We can then capture the output and pass it on to the next step in the process.
On iOS, the best way to do this is to use a “web view”, specifically a WKWebView, which uses the WebKit library to display HTML content. We run an embedded web server on the iOS device, which uses a directory from our app’s bundle as its document root.
The web view doesn’t need to be visible to the user of the application; in fact we’d rather the user not see the note be rendered, and instead present them with a share sheet once the PDF is ready. By placing the web view off-screen the user won’t be able to see it while it does its job, but there is one “gotcha” with this approach: the web view has to be made a child of a parent view otherwise it won’t function properly on a real device (http://www.openradar.me/18512488). We add it to the main application window with a zero frame (i.e. no width or height) so it doesn’t affect the UI.
At this stage we have a web view that is displaying a note in a style suitable for saving as a PDF. Next we have to consider how best to retrieve that representation as data and transform it into a PDF file for sharing.
Saving a PDF
I’d wager that many modern developers, faced with a task with which they have no experience, will explore third party libraries and the like to see if there’s an existing component that does the job. That’s a perfectly sensible approach which avoids “reinventing the wheel”, as they say. It’s certainly the first thing I did when considering how to turn the HTML encapsulated within the web view’s web page into a PDF.
I didn’t find anything convincing.
Or perhaps we could send the HTML to our server and reuse our existing export logic? Since this approach wouldn’t work if the user was offline, that idea too was no good.
After some more research we discovered that there was a much simpler answer – to use iOS’ built in UIViewPrintFormatter functionality.
Each instance of a WKWebView exposes an instance of a UIViewPrintFormatter which is able to lay out the drawn content of a view for printing. This object isn’t knowledgable about dimensions of pages and such though – it just knows how to draw the contents of the view. In order to make this content fit into the bounds of a series of pages, we need to give the UIViewPrintFormatter to an instance of a UIPrintPageRenderer. A UIPrintPageRenderer takes the formatted data from the print formatter and fits it into the dimensions with which it has been configured.
There are two fundamental configuration options: the paperRect and the printableRect.
- The paperRect value dictates the overall size of the page
- The printableRect value is inset within the paperRect and determines where content can be drawn within that page
UIPrintPageRenderer also sports headerHeight and footerHeight properties which block out sections at the top and bottom of the page to provide headers and footers. These sections need to be drawn manually by subclassing UIPrintPageRenderer and overriding the relevant methods.
We decided to subclass UIPrintPageRenderer for two reasons: to provide a footer with some useful metadata like the date the note was shared, and also to neatly encapsulate the logic for rendering a page with the dimensions of an A4 piece of paper, following the approach described in this article.
Unfortunately the renderer doesn’t give us a PDF for free; it draws to a graphics context which is an off-screen buffer of image data. To capture the printed data into a PDF we need to set up a PDF graphics context and iterate over each page in our UIPrintPageRenderer, drawing to the context as we go. We need to be careful to define the bounds of the context to be the same size as our UIPrintPageRenderer‘s printableRect property – we made the mistake of using an empty frame to begin with, and blank PDFs resulted. We now take care to size the graphics context to match the size of the UIPrintPageRenderer.
Once we’ve finished drawing each page into the context we’ll end up with an NSData object containing the raw PDF data – this can then be output to a file, and we’re ready to share a PDF.
Working with the native share sheet
The “share sheet”, known as a UIActivityViewController in the UIKit framework, was introduced in iOS 6. It’s fairly simple to work with: you provide it with an array of activity items (these can be URLs, file paths, strings, etc.) and iOS determines which of the services it knows about can handle data of that type. It then presents the user with an interface that displays the relevant services, and the user picks one. That service is then provided with the data in question.
After we’ve created our PDF we have a path to that file on disk. That path is given to a UIActivityViewController as an activity item, and then we present the activity view controller. iOS takes care of the rest, which is pretty neat.
We’re able to control which services are exposed to the user via a blacklist. So if a fund has decreed that they don’t want their users to export notes to a specific source, we can prevent that service from appearing in the share sheet. Here’s how we’d go about removing email if it were disabled by a feature flag, for example:
A UIActivityViewController also has a completionHandler property which is useful for checking which activity the user chose. We record this (anonymized) information to enable us to determine the services for which we should be prioritising support.
And that’s it – now we’re able to share notes as PDFs right from the app. We hope it proves to be a useful feature, and do let us know if there are ways we could improve it!