2
votes

I have a very simple toy app with one single text view and one single button.

xib

These are controlled by the AppController class that looks like this

// AppController.h
#import <Cocoa/Cocoa.h>

@interface AppController : NSObject <NSTextViewDelegate>

@property IBOutlet NSTextView *textView;
-(IBAction)apply:(id)sender;

@end

// AppController.m
#import "AppController.h"

@implementation AppController {
    NSString *name;
}

-(void)awakeFromNib {
    name = @"Bob";
    _textView.string = name;
}

-(IBAction)apply:(id)sender {
    name = _textView.string;
    NSLog(@"%@", name);
}

-(void)textDidChange:(NSNotification *)notification {
    NSLog(@"%@", name);
}

@end

When the user enters a new value in the textView and clicks on the apply button, I want the instance variable name to get this new value. And this is what happens the first time I click on the Apply button.

But the next time I change the value in the textView the instance variable gets automatically changed without the user clicking on the Apply Button!

This is an unwanted behavior since I did not make any bindings. And indeed if I change the textView to a textField the strange behavior does not happen.

So it seems that NSTextView does some strange involuntary binding. Can someone explain this strange behavior and how to come around it. I do not want the textView to change my data behind my back.

I have made a video on YouTube that shows the difference in behavior between the textField and the textView which you can se at https://youtu.be/qenGKp_L4qs.

I have checked on both Xcode6 and Xcode5 and the strange behavior happens on both.

If you want to test this out on your own machine, do not forget to connect the delegate property of the NSTextView with the AppController in the IB.

1

1 Answers

4
votes

This line:

name = _textView.string;

makes your instance variable refer to the same object that the text view is using internally. From the documentation for the string property of NSText (which NSTextView inherits from):

For performance reasons, this method returns the current backing store of the text object. If you want to maintain a snapshot of this as you manipulate the text storage, you should make a copy of the appropriate substring.

When you say "I do not want the textView to change my data behind my back", you've got it wrong. The text view is changing its data and you are (unwisely) treating that as your own.

You should a) make a (possibly private) property for your internal state, b) use the copy attribute on that property, and c) use self.name = _textView.string; to assign to your internal state.

If you don't want to do that, you have to at least copy the string manually, using name = [_textView.string copy];.