Working Around WebKit’s Incomplete(?) API

One of the features of Conscriptor, my Markdown editor, is a live preview that shows you a web-rendered version of your Markdown document. I implemented this using a very naive solution: a WKWebView that renders HTML generated via a Markdown parser. As you type, this HTML gets re-generated many, many times, and so the WKWebView refreshes to render the HTML many, many times. While this doesn’t appear to have a clear performance hit at the moment, it does come with one major catch: every time the web view refreshes, it scrolls back to the top. Of course, this is a huge showstopper: if you’ve scrolled down at all in the editor pane, then the live preview becomes useless as you’re no longer seeing the part you’re editing previewed live. I explored many, many, many, solutions to this. I eventually ended up with a mostly-workable solution that gets the feature to work (mostly) as-intended, and I documented this journey through a Twitter thread. Here, I’ll recap this thread and give some additional thoughts on my somewhat-elegant somewhat-messy solution to this problem.








Sadly SwiftUI’s NSViewRepresentable doesn’t take advantage of Swift 5.5’s asynchronous features, so I couldn’t use them to get the JavaScript to execute when I needed it to. Hopefully this’ll be remedied in the future…



When using pure SwiftUI to write a cross-platform app, it’s very difficult to get manual control over framework views the same way you’re able to when using UIKit or AppKit. SwiftUI assumes that you just want the “default” behavior and implements it for you. Even using view modifiers can only get you so far.

However, throughout this process I discovered a member of UI/NSViewRepresentable that I hadn’t made use of before: Coordinators. The official documentation for Coordinators is regrettably scant, but the gist of it is that they essentially act as delegates between SwiftUI and the underlying UI/NSView. They allow you to create a custom Coordinator class that can adopt any number of protocols, letting you register yourself as the delegate or data source for any protocol you adopt!



Here is an example of this phenomenon:



Future Resolutions

I’m sure there’s any number of other ways that I could implement the live preview: I could create a SwiftUI view that parses the Markdown and uses native components to render them. In theory, this would be a flawless solution, however it would take a significant amount of time to implement - time that I don’t currently have. I could also look to any number of third-party packages that implement web views or Markdown renderers. I could even look into using the now-deprecated Web View. At this point, I’m hesitant to mess with it as I’d like to get the rest of the features finished so I can just ship it and move on. Maybe a rewrite would be in order for a “2.0” release, if it ever comes.

Either way, it’s surely frustrating that I’m being actively punished for using macOS’s native tools and platform when I could have simply used Apple’s own Catalyst framework and gotten all the tools I would have needed through UIKit. Even in their newest framework which already suffers from its young age, macOS feels like an afterthought.

Next
Next

Hello World