11

Following the apple documentation I am trying to set up a simple NSTextView via its two constructor methods.

I am placing the below code inside the viewDidAppear method of the view controller of the content view. textView is an instance of NSTextView, frameRect is the frame of the content view.

The following Swift code works (gives me an editable textView with the text showing on the screen):

    textView = NSTextView(frame: frameRect!)
    self.view.addSubview(textView)
    textView.textStorage?.appendAttributedString(NSAttributedString(string: "Hello"))

The following does NOT work (text view is not editable and no text shown on the screen):

    var textStorage = NSTextStorage()
    var layoutManager = NSLayoutManager()
    textStorage.addLayoutManager(layoutManager)
    var textContainer = NSTextContainer(containerSize: frameRect!.size)
    layoutManager.addTextContainer(textContainer)
    textView = NSTextView(frame: frameRect!, textContainer: textContainer)

    textView.editable = true
    textView.selectable = true
    self.view.addSubview(textView)

    textView.textStorage?.appendAttributedString(NSAttributedString(string: "Hello more complex"))

What am I doing wrong in the second example? I am trying to follow the example given in Apple's "Cocoa Text Architecture Guide" where they discuss setting up an NSTextView by explicitly instantiating its web of helper objects.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Sam
  • 2,745
  • 3
  • 20
  • 42
  • 1
    Are you keeping a reference to the ``textStorage`` variable? – Paul Patterson Mar 13 '15 at 15:03
  • @PaulPatterson spot on! Just changed the declaration of textStorage to be at the class level instead of local and then I was in business. Please pop the answer below so I can accept it. I presume the explanation is that the local variable gets destroyed on exiting the method and then you have a textView that has a pointer to a storage location that kinda doesn't exist? – Sam Mar 13 '15 at 15:09
  • 1
    Exactly. Funnily enough I spent about an hour or two a couple of months ago trying to figure out this exact problem - Apple should be a bit more explicit in their guide. – Paul Patterson Mar 13 '15 at 15:15

3 Answers3

12

You need to keep a reference to the NSTextStorage variable you create. I'm not quite sure about the mechanics of it all, but it looks like the text view only keeps a weak reference to its text storage object. Once this object goes out of scope, it's no longer available to the text view. I guess this is in keeping with the MVC design pattern, where views (the NSTextView in this case) are meant to be independent of their models (the NSTextStorage object).

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!

    var textView: NSTextView!
    var textStorage: NSTextStorage! // STORE A REFERENCE

    func applicationDidFinishLaunching(aNotification: NSNotification) {
        var view = window.contentView as NSView
        textStorage = NSTextStorage()
        var layoutManager = NSLayoutManager()
        textStorage.addLayoutManager(layoutManager)
        var textContainer = NSTextContainer(containerSize: view.bounds.size)
        layoutManager.addTextContainer(textContainer)
        textView = NSTextView(frame: view.bounds, textContainer: textContainer)

        textView.editable = true
        textView.selectable = true
        view.addSubview(textView)

        textView.textStorage?.appendAttributedString(NSAttributedString(string: "Hello more complex"))
    }
}
charles
  • 11,212
  • 3
  • 31
  • 46
Paul Patterson
  • 6,840
  • 3
  • 42
  • 56
  • Thanks. On a separate note of something that has been driving me crazy lately: I see in your code for AppDelegate an @IBOutlet to your main window... Recently I am finding that Xcode won't let my AppDelegate have outlets to windows & views - is it just me? I control-drag... and nothing. – Sam Mar 13 '15 at 15:28
  • Are you using Storyboards or Xibs? – Paul Patterson Mar 13 '15 at 15:37
  • I think that's the problem. You can only create outlets to objects that exist in the same scene. Since the ``AppDelegate`` resides in the Application Scene, I don't think there's a way to create an outlet to it from say a button in a View Controller Scene. This restriction is one of the reasons I've stuck with Xibs (the code in my question is from a Xib-based app, hence the outlet). – Paul Patterson Mar 13 '15 at 15:44
  • Thanks for the code! Note that you have to set the `frame` of the `textView` to the `bounds` of the `view`, not its `frame`, because the `textView` is a subview of `view`. If `view` is itself a subview of another view, its `frame` will not be at origin (0,0) and the `textView` will be offset and potentially not even visible. I will try to edit your answer to reflect that. – charles Jul 20 '18 at 09:53
  • Add my 2cents: Couldn't figure out why I couldn't see or type any text when adding text view programatically. The problem was that the textview frame has to match or be smaller than the view being added (frame) – Marek H Aug 04 '19 at 05:55
4

Tested under Xcode 12.4. in Playgrounds:

import Cocoa
import AppKit

let textViewFrame = CGRect(x: 0, y: 0, width: 250, height: 90)
let textStorage = NSTextStorage()
var layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
var textContainer = NSTextContainer(containerSize: textViewFrame.size)
layoutManager.addTextContainer(textContainer)
let textView = NSTextView(frame: textViewFrame, textContainer: textContainer)
textView.isEditable = true
textView.isSelectable = true
textView.textColor = NSColor.red
textView.string = "Why is this so complicated..."
Darkwonder
  • 1,149
  • 1
  • 13
  • 26
-1
#import <Cocoa/Cocoa.h>

@interface TextViewController : NSObject {

    NSLayoutManager *secondLayout;

    IBOutlet NSSplitView *columnView;
    IBOutlet NSTextView *bottomView;

}

- (IBAction) addColumn: (id)sender;

@end
#import "TextViewController.h"

@implementation TextViewController

- (void)awakeFromNib
{
    NSTextStorage *storage = [bottomView textStorage];
    secondLayout = [NSLayoutManager new];
    [storage addLayoutManager: secondLayout];
    [secondLayout release];
    [self addColumn: nil];
    [self addColumn: nil];
}


- (IBAction) addColumn: (id)sender
{
    NSRect frame = [columnView frame];

    NSTextContainer *container = [[NSTextContainer alloc]
                                  initWithContainerSize: frame.size];
    [container setHeightTracksTextView: YES];
    [container setWidthTracksTextView: YES];

    [secondLayout addTextContainer: container];
    [container release];
    NSTextView *newView = [[NSTextView alloc] initWithFrame: frame
                                              textContainer: container];
    [columnView addSubview: newView];
    [newView release];
}

@end
Durul Dalkanat
  • 7,266
  • 4
  • 35
  • 36
  • 2
    What does this code do, could you add some description of how it is related to the question? – koen Apr 16 '18 at 12:38