0

Plan

I have implemented a TextView into my Swift-app (macOS 11) using the following implementation, which makes the TextView scrollable by placing it into a ScrollViewer:

https://gist.github.com/unnamedd/6e8c3fbc806b8deb60fa65d6b9affab0

Now, I'm trying to display annotations on the side of the text, which should eventually look something like this:

Annotations

I planned to achieve this by

  1. Setting a custom AttributedString-key to the NSAttributedString of the TextView
  2. Add a second ScrollView on the left side, which contains the annotations, and synchronise the scroll position of both ScrollViews (i.e., the left one and the one in the TextView.)
  3. Calculate the Y-position of each annotation using the relevant substring and place/draw the annotation/connecting line

Problem

I tried to evaluate the Y-Position of a substring using the textView.firstRect function (which I found in all the other, old SO-questions), however, it does not seem to return the correct values unless the position of a substring within the first line of the TextView is used.

    class TextViewExtended: NSTextView {    

func getAllRectangles() {
    for CurRange in self.selectedRanges {
        self.layoutManager!.ensureLayout(for: self.textContainer!) // S. https://stackoverflow.com/a/30752520/2624880
        let CurRangeAsNSRange:NSRange = CurRange as! NSRange
        var actualRange = NSRange(location: 0, length: self.attributedString().length)
        let rect = self.firstRect(forCharacterRange: CurRangeAsNSRange, actualRange: &actualRange)
    }}}

Question

What would be to correct way to determine the coordinates of a substring of a TextView in SwiftUI & macOS 11? Or does anybody have an even simpler idea how I could achieve this?

Thanks a lot in advance for any advise!

Ps. I have omitted the full code of the TextView implementation since it follows the above tutorial closely; please let me know if I should add additional code.

Sebastian
  • 115
  • 1
  • 8
  • SwiftUI natively does not give such flexibility for now. You should continue work with NSTextView for that. – Asperi Aug 20 '20 at 04:16
  • Thanks a lot, @Asperi. Stupid question but does that mean I shouldn't implement a TextView in a SwiftUI project as shown in the link (i.e. with a NSViewRepresentable, Coordinator, NSTextView within a NSScrollView, etc.) but rather make an AppKit project? – Sebastian Aug 20 '20 at 11:30
  • Who said that AppKit variant is worse or impossible? Who said that it is impossible to combine them? Take into account that SwiftUI is 1 year-old, AppKit lives tens years (since NeXT)... – Asperi Aug 20 '20 at 12:41
  • Thanks again, @Asperi Didn't mean this offensive in any way! I'm still very new to Swift(UI) and that's why I'm quite confused when I should use which framework (and e.g. how I could use the native NSTextView here). I think, I'll wait for feature Swift updates in that case! Thanks again! – Sebastian Aug 20 '20 at 12:56

1 Answers1

2

Try updating the rect using the actualRange. some thing like this:

rect = textView.firstRect(forCharacterRange: range, actualRange: &actualRange)
while actualRange.upperBound < rangeToCheck.upperBound {
    rangeToCheck.length = (rangeToCheck.upperBound - actualRange.upperBound)
    rangeToCheck.location = actualRange.upperBound
    rect = textView.firstRect(forCharacterRange: rangeToCheck, actualRange: &actualRange)
}
Duke Fredrick
  • 163
  • 10
  • 1
    Thanks! Interestingly, `var rect = self.firstRect(forCharacterRange: CurRangeAsNSRange, actualRange: &actualRange)` seems to work now, so this issue is resolved (to be honest, I didn't manage to implement your while loop, but it seems to work anyway)! Cheers – Sebastian May 24 '21 at 12:19
  • 1
    Finally figured out how to get the position _within the TextView itself (not the window)_ : `layoutManager!.boundingRect(forGlyphRange: rangeToCheck, in: self.textContainer!)`. Correct this rect by the height/width of `textContainerInset` of the TextView and the current scroll position (using `enclosingScrollView.documentVisibleRect.origin`) and you've got the correct position! – Sebastian Feb 08 '23 at 14:06