Me porting Runestone from UIKit to AppKit.

Currently leaning towards having separate implementations of TextView for UIKit and AppKit with shared logic moved into separate types. The alternative is a single implementation with lots of if-else-endif macros.

It makes for some duplicated logic but also means that the two implementations can easily be maintained and implemented in isolation.

@simonbs Sound idea. In practice, text views always ends up being a mess with all the different types, delegates, etc.

If you try to manage the different APIs within that code you end up breaking the iOS version while working on the macOS version, etc..

I’d just have completely separate technical implementations with platform independent code shared in platform neutral classes.

I’ve found testing and re-testing changes to be a much bigger time killer than writing separate implementations.

@frankreiff @simonbs Agreed. You have to treat the whole entire thing as a single entity, because it’s always impossible for the different delegate methods to be isolated. They always end up needing to work together in weird ways.

@simonbs I really dislike maintaining macro “if” based multiplatform code. I always try to structure my way out of it, like what you describe.

Much easier to read and reason about I think.

@simonbs Definitely sounds like the way to go. Easier to have platform specific customizations in those iOS and Mac TextViews then.
@simonbs I’d go for separate implementations too.
@simonbs I remember a WWDC talk from several years ago (from before Catalyst) about how Apple implemented Pages across iOS and macOS using the same code base. I can’t find it unfortunately, but I think they used used TextKit and text layers to implement all the juicy stuff with the actual NS/UIViews being super thin. Maybe the video is available in some dark corner of the web.

Yay! Runestone now works with... *Checks notes*... UIKit?? 🤨

In order to prepare for AppKit support, I had to rip the internals of Runestone apart and put it together again. For the first time in 36 hours, Runestone now works with UIKit again.

So I now have an NSWindow with an NSView that receives keystrokes. That's like step 0 in building a text editor, right?

There's such a long way to go still before Runestone is rewritten in AppKit. I hope I'll eventually turn a corner where all my work from UIKit can be reused and I ✨magically✨ have an AppKit implementation.

Runestone just rendered its first text using AppKit. Baby steps, y'all.

The AppKit version of Runestone now supports scrolling the content.

This involves a bit more than just wrapping everything in an NSScrollView because Runestone only renders the lines within the viewport.

Baby steps, y'all.

Time to add a caret that shows where characters will be inserted. I'm a little bummed that I have to implement this myself. We get that for free in UIKit.
Runestone for AppKit now has a caret. Baby steps, y'all.

I need to implement all moving within lines myself 😑

In the screenshot I'm logging the selectors that I don't handle but that AppKit expects me to handle. This is something we get (almost) for free in UIKit. Honestly, I really don't want to write this logic.

Got navigation with the arrow keys working in Runestone for AppKit.

I figured out how to reuse some of the code from the UIKit implementation so this turned out to be much easier than anticipated.

Next up is adding support for jumping between words with Option+Left/Right arrow keys.

Baby steps, y'all.

In order to move from word to word, UIKit relies on an implementation of UITextInputStringTokenizer. I managed to replicate UIKit's calls to my string tokenizer and reuse a lot of logic for moving between words. Baby steps, y'all.

While working on moving between words in Runestone for AppKit I found that the UIKit version had an incorrect behavior when moving between words followed by an emoji. The caret would always jump all the way to the end of the document which isn't correct, obviously. Fortunately, that was easy to fix and the fix works in both UIKit and AppKit.

And yes, it is supposed to jump all the way from the word "emoji" to the word "cool". That's how TextEdit does it too. Baby steps, y'all.

And now Runestone for AppKit supports moving to the line and document boundaries as well as clicking with the mouse to move to the closest location.

Maybe the next step is to support text selection. Or something more fun like line numbers.

Baby steps, y'all.

@simonbs I’m pretty sure this will end up being a much more polished editor than Apple’s NSTextView, with your track record
@simonbs watching you build this is so cool! Thanks for sharing the baby steps! It’s really inspiring.
GitHub - krzyzanowskim/STTextView: Performant and reusable text view component (TextKit 2), with line numbers and more. UITextView / NSTextView replacement.

Performant and reusable text view component (TextKit 2), with line numbers and more. UITextView / NSTextView replacement. - krzyzanowskim/STTextView

GitHub
@pixelscience @krzyzanowskim Yes but mine needs to work with UIKit too.
@simonbs looks like that baby is taking some big steps… 👏
@simonbs for the record, I’ve been really enjoying watching these little baby steps coming together piece by piece. Appreciate you!
@danbetcher Thanks! Glad to hear you enjoy it.
@simonbs not being a coder but I thought you got these text navigation shortcuts “for free” from the frameworks from Apple?
@simonbs NSTextView handles these, of course. Are you writing a a replacement NSView, or using Catalyst and finding that UITextInteraction in Catalyst doesn't handle them correctly?
@steveshepard I’m subclassing NSView and conforming to NSTextInputClient. I’m essentially trying to do what I’ve done with UIView and UITextInput but in AppKit.
@steveshepard I’m curious to know which approach you’re taking with Storting. Is it AppKit or Catalyst?
@simonbs On macOS, Storyist uses NSTextView. Writing a replacement for that is a lot of work. Unfortunately, NSTextInputClient only gets you a small subset of what UITextInput get you on iOS and there isn't a UITextInteraction equivalent.
@steveshepard Yeah, it seems to be a lot of work. But you are also using Core Text on iOS, right? Can you reuse that with an NSTextView?
@simonbs Yes, CoreText on iOS, implemented in the days before TextKit came to the platform. I don't currently use CoreText on macOS because NSTextView is pretty complete (and extensible).
@steveshepard Hmmm… I fear I’ll end up with vastly different implementations if I go that route. Maybe I made a mistake to go with Core Text over TextKit when I started the project. TextKit just had some performance issues that I couldn’t live it 😕
@simonbs Yes, they'll be quite different. I think @krzyzanowskim has an NSTextView replacement, so if you're going to invest the effort, that might be a place to start.
@simonbs Baby steps, y’all. — an AppKit series by Simon B. Støvring
@simonbs now do option for different carets
@pasi I've considered this a lot and may do it eventually. Probably won't do it on iOS though.
@simonbs It’s incredible to me that you’re building an editor literally “from scratch”. Very cool!
@simonbs cannot wait for it. Scriptable for Mac could take some Runestone love. I'm still clad it works without it too.
@simonbs just realized that I can use this for Dias if/when I get development on Dias.app started again. Awesome work!!
@simonbs looking really good! Is your plan to also release the Runestone-app on macOS, or will you just be supporting AppKit for the framework? I'd love to have the former, macOS is really missing a good non-IDE text editor!
@jvnknvlgl My current plan is to have the framework support AppKit. I don't have any concrete plans to bring Runestone to the Mac. I have, however, toyed with the idea of using the Runestone framework for something else on the Mac.
@simonbs I don’t believe in magic in software engineering.
But I do believe in expertise and experience!
Good luck with your AppKit journey!
@simonbs Are you rewriting entirely in AppKit, over using Catalyst?
@simonbs What irks me the most about the UIKit & AppKit differences is not that some APIs have vaguely different names, but that the bugs are all different, as are the undocumented behaviours.

@terhechte That's what's scaring me the most: the uncertainty of which bugs I'll have to deal with now.

Working around crazy issues with UITextInput has probably been the most exhausting challenge I've faced when building software.

@simonbs I ran into the same issues when building Hyperdeck. It being Catalyst certainly didn't help. It's so understandable when companies just use a good JS editor plugin in a WebKit and call it a day.
@simonbs congrats! Love Runestone and all your apps so much.
@simonbs congrats! Lots of apps could benefit from a native framework like this on macOS. All the stuff I found early on when making my app was all web view based. None of them work well enough for a good experience. I’m positive runestone for macOS will improve my app by a lot. Currently just using SwiftUIs TextEditor. Upgrading to runestone would be like getting an electric car lol
@simonbs curious why port instead of using Catalyst? Is it dead in the water?
@simonbs good luck, you got this! 💪🏻
@simonbs notifications turned on for this

@simonbs Looks great, can’t wait!

Maybe more yarn? 🧶