Laying out text in NSTextView around a subview

  • What I’m trying to do is have an NSTextView and add custom NSView subviews to it, but have it so the text can layout around the subviews.

    Right now, I can easily add a subview to the textview but of course, that goes into the textview and the text is ignorant of the subviews, so it just runs over/under the subview. Not what I’d like, of course.

    So I’d like to be able to add my own views at least in “block” (like when an HTML element is a block element, so it’s on its own line), and maybe “inline” as well (that is, a subview in line with the text, just like how an HTML element can be inline) although this is not absolutely required.

    I can’t quite figure out how to make this work. The only way I’ve seen that things can be added to a textview are with Text Attachments, but those seem relegated to only images/files, and only in an NSCell, which doesn’t contain (to my knowledge) an arbitrary NSView.

    I feel like this should be possible though, where do I start?

    Thanks!
    Jason Brennan
  • On Apr 25, 2013, at 5:27 PM, Jason Brennan wrote:

    > What I’m trying to do is have an NSTextView and add custom NSView subviews to it, but have it so the text can layout around the subviews.
    >
    > Right now, I can easily add a subview to the textview but of course, that goes into the textview and the text is ignorant of the subviews, so it just runs over/under the subview. Not what I’d like, of course.
    >
    > So I’d like to be able to add my own views at least in “block” (like when an HTML element is a block element, so it’s on its own line), and maybe “inline” as well (that is, a subview in line with the text, just like how an HTML element can be inline) although this is not absolutely required.
    >
    > I can’t quite figure out how to make this work. The only way I’ve seen that things can be added to a textview are with Text Attachments, but those seem relegated to only images/files, and only in an NSCell, which doesn’t contain (to my knowledge) an arbitrary NSView.
    >
    > I feel like this should be possible though, where do I start?

    What you want is possible, but likely difficult. First, you need to thoroughly understand the Cocoa text architecture and layout. From this alone, you would have known that subclassing NSTextView is likely the wrong approach. You will also need to see the WWDC 2010 talk on advanced text techniques. That will give you an idea about inline views.

    An all-Cocoa solution might not be the best choice, however, although it will probably be easier (though you won't think so as you trudge through it, take it from me ;-), though likely less performant. You may quickly get into having too many NSViews, which are heavy and slow.

    Anyway, you would likely start with an NSView subclass, using multiple NSTextViews when text is divided by a "block" view, and inline views for the inline part. You will also likely want to become familiar with NSTextBlock, which will make having text flow around an object much easier.

    HTH,

    Keary Suska
    Esoteritech, Inc.
    "Demystifying technology for your home or business"
  • On Apr 25, 2013, at 4:27 PM, Jason Brennan wrote:

    > What I’m trying to do is have an NSTextView and add custom NSView subviews to it, but have it so the text can layout around the subviews.

    You need a custom text container. See http://developer.apple.com/library/mac/#samplecode/TextLayoutDemo/Introduct
    ion/Intro.html


    --
    Seth Willits
  • On Apr 29, 2013, at 11:08 AM, Seth Willits wrote:

    >> What I’m trying to do is have an NSTextView and add custom NSView subviews to it, but have it so the text can layout around the subviews.
    >
    > You need a custom text container. See http://developer.apple.com/library/mac/#samplecode/TextLayoutDemo/Introduct
    ion/Intro.html


    I re-read this.

    Do you want something like Xcode's inline errors (when the error bubble is under the line of code with the problem)? The text flows around it, but you also want that bubble to move with a particular bit of text?

    You'll still need a custom text container to manage text flow around the block, but I think you'll probably want to combine that with a text attachment which gives you the location in-text of that block and a storage mechanism. The text attachment is not critical, but would be useful since the character location of that block would automatically move when the text storage is edited.

    --
    Seth Willits
  • Yeah what I'm trying to do mostly is a "block" element in the text,
    "inline" is just a side thing, not essential. What I'm trying to do is best
    described as an analogy with HTML.

    Let's say I've got the following HTML:

    <p>This is a paragraph</p>
    <img/>
    <p>Another paragraph</p>

    I'm only using <img> as an example because it's a block element. It goes on
    its own line and moves with the paragraphs (if, ex, the first paragraph
    grew). I know I can use NSTextAttachments for a "block image", but in my
    case I want this to be any NSView because it requires interaction.

    So, I need to be able to embed a block element in a text view and have it
    move with the text. There should be no text on the same line as the NSView.

    I feel like with NSTextContainer or NSLayoutManager but I can't quite
    figure out what the right combination is. I've read the Text Layout
    Programming guide but I feel no closer to knowing.

    Thanks for the help so far!
    Jason

    On Mon, Apr 29, 2013 at 3:01 PM, Seth Willits <slists...> wrote:

    > On Apr 29, 2013, at 11:08 AM, Seth Willits wrote:
    >
    >>> What I’m trying to do is have an NSTextView and add custom NSView
    > subviews to it, but have it so the text can layout around the subviews.
    >>
    >> You need a custom text container. See
    > http://developer.apple.com/library/mac/#samplecode/TextLayoutDemo/Introduct
    ion/Intro.html

    >
    >
    > I re-read this.
    >
    >
    > Do you want something like Xcode's inline errors (when the error bubble is
    > under the line of code with the problem)? The text flows around it, but you
    > also want that bubble to move with a particular bit of text?
    >
    > You'll still need a custom text container to manage text flow around the
    > block, but I think you'll probably want to combine that with a text
    > attachment which gives you the location in-text of that block and a storage
    > mechanism. The text attachment is not critical, but would be useful since
    > the character location of that block would automatically move when the text
    > storage is edited.
    >
    >
    > --
    > Seth Willits
    >
  • On Apr 30, 2013, at 7:01 AM, Jason Brennan wrote:

    > Yeah what I'm trying to do mostly is a "block" element in the text, "inline" is just a side thing, not essential. What I'm trying to do is best described as an analogy with HTML.
    >
    > Let's say I've got the following HTML:
    >
    > <p>This is a paragraph</p>
    > <img/>
    > <p>Another paragraph</p>

    So a quick fiddle offers a potential simpler solution. This is not quite complete, but shows the idea. The only bit this doesn't handle is removing the view when the attachment cell is removed from the text storage. To deal with that you need to create a simple text storage subclass and in the replace…: method, scan for ViewAttachments and remove the view from the superview.

    (Using a single view instance like this, you also won't be able to show the same text storage in two different text views since the view can't be in two places at once.)

    @interface ViewAttachment : NSTextAttachment
    @end

    @interface ViewAttachmentCell : NSTextAttachmentCell {
    NSView * _view;
    }

    @property (readwrite, assign) NSView * view;
    @end

    @implementation AppDelegate
    {
    IBOutlet NSTextView * textView;
    IBOutlet NSView * attachmentView;
    }

    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
    {
    NSTextStorage * ts = textView.textStorage;
    NSUInteger location = <pick something in the middle of a line>


    ViewAttachmentCell * attachmentCell = [[[ViewAttachmentCell alloc] init] autorelease];
    attachmentCell.view = attachmentView;

    ViewAttachment * attachment = [[[ViewAttachment alloc] init] autorelease];
    attachment.attachmentCell = attachmentCell;

    NSAttributedString * attachStr = [NSAttributedString attributedStringWithAttachment:attachment];
    [ts insertAttributedString:attachStr atIndex:location];
    }

    @end

    @implementation ViewAttachment
    @end

    @implementation ViewAttachmentCell
    @synthesize view = _view;

    - (NSSize)cellSize
    {
    return self.view.frame.size;
    }

    - (NSRect)cellFrameForTextContainer:(NSTextContainer *)textContainer proposedLineFragment:(NSRect)lineFrag glyphPosition:(NSPoint)position characterIndex:(NSUInteger)charIndex
    {
    NSRect cellFrame = [super cellFrameForTextContainer:textContainer proposedLineFragment:lineFrag glyphPosition:position characterIndex:charIndex];

    // Make it on its own line.
    cellFrame.size.width = textContainer.containerSize.width;

    // Side note: AFAICT, the x/y of the frame when calling super is always 0,0
    // which seems contradictory to what the documentation suggests it should be.
    // As a result, handling x/y seems unnecessary here, and works without it.
    // Otherwise I'd expect to have to set x to 0 and bump the y if needed, etc.

    return cellFrame;
    }

    - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView characterIndex:(NSUInteger)charIndex layoutManager:(NSLayoutManager *)layoutManager
    {
    if (self.view.superview != controlView) {
      [self.view removeFromSuperview];
      [controlView addSubview:self.view];
    }


    // I'm not really sure if moving the view when the cell should draw is good
    // enough, but it works in this quick example, anyway. You may need to change
    // the frame whenever the text storage changes (smartly).
    self.view.frame = NSMakeRect(cellFrame.origin.x, cellFrame.origin.y, self.view.frame.size.width, self.view.frame.size.height);
    }

    @end
previous month april 2013 next month
MTWTFSS
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30          
Go to today