danielsaidi / RichTextKit

RichTextKit is a Swift SDK that helps you use rich text in Swift and SwiftUI.
MIT License
857 stars 110 forks source link

Dynamic Editor Height Question #33

Open rydamckinney opened 1 year ago

rydamckinney commented 1 year ago

Is it currently possible to make the RichTextEditor frame dynamic with the following behavior?

danielsaidi commented 1 year ago

Hi @rydamckinney

I think this would be a bit tricky to achieve on all platforms, since the text views work a bit differently on iOS, where scrolling is part of the UITextView, compared to on macOS, where the NSTextView must be added to a scroll view.

Would be nice to have though 👍

darrarski commented 1 year ago

Not sure about macOS, but this can be done easily on iOS:

struct MyTextView: UIViewRepresentable {
  typealias UIViewType = UITextView

  /* ... */

  func sizeThatFits(_ proposal: ProposedViewSize, uiView: UITextView, context: Context) -> CGSize? {
    // make sure we have a valid proposed width:
    guard let proposedWidth = proposal.width, proposedWidth > 0, proposedWidth < .infinity else {
      return nil
    }

    // calculate size of UITextView that fits current text:
    var sizeThatFits = uiView.sizeThatFits(CGSize(
      width: proposedWidth,
      height: .greatestFiniteMagnitude
    ))

    // fill parent horizontally:
    sizeThatFits.width = proposedWidth

    return sizeThatFits
  }
}

It's just an example of unconstrained, dynamic size. Constraining the minimum and maximum height should be straightforward.

danielsaidi commented 1 year ago

Thank you @darrarski! Did you get this to work @rydamckinney?

rydamckinney commented 1 year ago

Hey @danielsaidi Yes I was able to get this working, thought not in the context of RichTextKit.

I needed markdown live syntax highlighting for the text input so I modified a fork of HighlightedTextEditor (https://github.com/kyle-n/HighlightedTextEditor) to switch between 2 wrapped NSTextViews (fixed vs dynamic height) based on the usage.

The solution should be generalizable to RichTextKit. The dynamic height view works in a pretty hacky way by checking & changing a constant height constraint on the scroll view that holds the NSTextView anytime that the text is changed. While it works, there's some dropped frames / animation glitches that I don't have a workaround for.

The rest of the code is pretty specific to the view hierarchy it's embedded in so I'm not sharing that so as to not confuse others (as I was led astray by many use-case-specific solutions I encountered in my research).

Here's the crux of the logic:

open class DynamicHeightNSTextView: NSTextView {
...
    open override func didChangeText() {
        super.didChangeText()

        self.invalidateIntrinsicContentSize()

        if let scrollHeight = self.scrollViewHeight {
            NSAnimationContext.runAnimationGroup { context in
                context.duration = 0.25
               //scrollHeight is a constant NSLayoutConstraint for the containing scrollview's height  
                scrollHeight.constant = min(self.maxHeight, max(self.intrinsicContentSize.height, self.minHeight))
            }
        }
    }
...
}
danielsaidi commented 1 year ago

Thank you for sharing @rydamckinney!

I will keep this issue open for when I have time to return to this project.

gabrielalbino commented 1 year ago

This feature would be my dream!

slh-ideaflow commented 6 days ago

I have what I think is a simpler question but which has led me here -- how can I get the RichTextEditor to dynamically adjust its height based on the text content inside, so that it doesn't have to scroll for you to see all the text? I'm working in iOS (and SwiftUI).