NSTextView changes the data behind your back - objective-c

I have a very simple toy app with one single text view and one single button.
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.

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];.

Related

How to declare a variable from user input?

I am fairly new to programming and am working with Objective-C in Xcode 5.
I'm presently making an OSX application in Xcode that uses Cramer's Rule (this matrix math method to calculate the intersecting point of three lines).
I really need some help with this one concept- I need to be able to take the user's input from multiple text boxes (assign them all a variable), put them through cramer's rule, and feed the answer out through a label.
I've made the storyboard and assigned one of the 12 text boxes (to test it) as an outlet and the label as an outlet and a button as an action, and tried a few different ways to just take the user input and (unaltered) feed it back out through the label so I know what I'm working with before I get into the math, and it's been unsuccessful. Having major syntax problems.
I have attached my code below:
//
// NewClass.h
// Cramer's Rule
#import <Foundation/Foundation.h>
#interface NewClass : NSViewController <NSTextFieldDelegate> {
IBOutlet NSTextField *box_a;
IBOutlet NSTextField *coord;
NSString *string;
}
#property (nonatomic, retain) IBOutlet NSTextField *box_a;
#property (nonatomic, retain) IBOutlet NSTextField *coord;
- (IBAction)calculate:(id)sender;
#end
AND
//
// NewClass.m
// Cramer's Rule
#import "NewClass.h"
#implementation NewClass
#synthesize box_a;
#synthesize coord;
- (IBAction)calculate:(id)sender {
NSTextField * input=box_a;
coord =input;
}
#end
As far as I know, I have the most up to date version of Xcode, and there is no option for creating a storyboard for an OSX project. Storyboards are for iOS projects. And that would explain the reason why you're unable to hook any thing up from the storyboard to your code.
This isn't to say that a storyboard can't be put in an OSX project--it can't. But it can't be selected from the Cocoa section of new files to create--only the Cocoa Touch section, which is iOS stuff--not OSX.
You have to use NSTextFieldDelegate, it have callback methods like in iOS:
- (void)textDidBeginEditing:(NSNotification *)notification;
- (void)textDidEndEditing:(NSNotification *)notification;
- (void)textDidChange:(NSNotification *)notification;
- (BOOL)acceptsFirstResponder;
For example:
- (void)textDidChange:(NSNotification *)notification{
if ([notification object]== box_a)
{
// ...
}else if ([notification object]== box_b)
{
// ...
}
}
Your problem is more fundamental than syntactical, you need to go and study up on what various things are and how they behave, this includes: variables, properties, objects and object references.
To briefly introduce why you're going wrong: Think of an object as a building. What is "in" the building may change over time, but the address of the building (usually!) does not. An address refers you to a building, and that is what an object reference does.
A variable is a box which holds a value of some type, that value can change over time, but the box does not.
When you declare:
NSTextField *input;
You are requesting that a variable be created for you which can hold references to objects - it does not hold an object anymore than address is a building, it just tells you where to find an object.
When you then assign a value to your variable:
NSTextField *input = box_a;
You are requesting the the value in box_a be copied and placed (stored) in input. That value is an object reference, it is not an object. Whatever object was referenced by box_a is not altered in anyway by this statement - what is in the house doesn't change, you just write the house's address down somewhere else.
When you then do:
coord = input;
you are doing the same thing - copying addresses. No objects are altered. The objects you are referring to are of type NSTextField, they have a visual representation on the screen, copying their addresses doesn't alter that visual representation anymore than copying the address of a building changes what is in the building.
When it comes to properties your code suggests a confusion between a property, which is a piece of code which does something, and its backing variable, a variable which that piece of code operates on.
Understanding these concepts is vital. You need to go an study up some more on programming.
HTH

Access class variable from another class

I have a UITabBarController that manages two ViewControllers. The first is a UIViewController that allows the user to change game settings. The second is a GLKViewController that runs the game simulation.
I'm trying to enable the Game ViewController to fetch the settings from the Settings ViewController. I have a Slider on the Settings View that represents "Speed".
I have a reference to the other controller, but I'm unable to expose the variable that backs my Slider properly.
SecondViewController.h
#interface SecondViewController : UIViewController{
IBOutlet UISlider * mySlider;
}
property (nonatomic,retain) IBOutlet UISlider * mySlider;
#end
SecondViewController.m
- (IBAction) mySliderWasMoved:(id)sender;
#implementation SecondViewController
#synthesize mySlider;
- (IBAction) mySliderWasMoved:(id)sender{
};
ThirdViewController.m
NSArray *tmpVCs = self.parentViewController.tabBarController.viewControllers;
UIViewController *tmpVC = [tmpVCs objectAtIndex:1]; //obtain handle to SecondViewController
//NSNumber *mySpeed = tmpVC.mySlider; //doesn't see mySlider
NSNumber *mySpeed = [tmpVC mySlider]; //doesn't see mySlider
I'm new to this, and there are many aspects of my project to learn - so I'm not trying to learn how to manage data at this time. I just need to know how to access an instance variable
As mention on the comments,
Use NSDefault to save the value on slider changed. On the very first time of loading your application, you will want to set a default value.
Use Singleton Object to store value.
We understand that, quoting from you " not trying to learn data persistence at this time. Nor do I need architecture direction.", but the rule of thumb here is that you probably will be able to access the instance variable in some way or the other but i think having the best approach will benefit you greatly.
Just my 2 cent.
Fort the benefit of others: I grabbed a handle to the other class, but I hadn't declared the return type as the correct type of class.
Replace:
UIViewController *tmpVC = [tmpVCs objectAtIndex:1];
With:
SecondViewController *tmpVC = [tmpVCs objectAtIndex:1];
Now I have access to the properties that are specific to the SecondViewController.

NSArrayController adding categories

I'm trying to add new categories to the NSArrayController class: it can select the first and the last item. I did so:
#import "NSArrayController+selectEnds.h"
#implementation NSArrayController (selectEnds)
- (void)selectFirst:(id)sender {
if (self.arrangedObjects !=nil){ BOOL ignore = [self setSelectionIndex:0];}
}
- (void)selectLast:(id)sender {
if (self.arrangedObjects !=nil){
NSUInteger lastItem = [self.arrangedObjects count]-1;
BOOL ignore = [self setSelectionIndex:lastItem];}
}
#end
I get no errors, but I would like to put this object in IB, using a blue cube and binding buttons to its "selectFirst" and "selectLast" methods.
But I'm a bit lost: which standard object to start with? A standard ArrayController? And then, which class name to choose to have the new methods listed?
Thanks for your help…
Since you didn't show NSArrayController+selectEnds.h (which is what IB actually looks at), just NSArrayController+selectEnds.m, it's hard to know exactly what you got wrong, but there's two plausible guesses.
First, if you want these new methods to be part of the interface of the class NSArrayController, you have to add them to the interface declaration, not just to the implementation.
Second, if you want Xcode (or IB) to know that these new methods are actions, you have to label them as such: in the interface, instead of marking them plain void methods, mark them IBAction methods. (In the implementation, you can do either; it doesn't matter.)
So, NSArrayController+selectEnds.h should be:
#import <Cocoa/Cocoa.h>
#interface NSArrayController (selectEnds)
- (IBAction)selectFirst:(id)sender;
- (IBAction)selectLast:(id)sender;
#end

Load custom class properly

I have a custom class which I want to "load" inside the firstViewController and then access it from other classes by segues. My Problem is, I can't even access and change the instance variable inside the firstViewController. Somehow I'm "loading" it wrong. Here is the code I used until now:
inside viewController.h
#property (strong, nonatomic) myClass *newClass;
inside viewController.m
#synthesize newClass;
I then try to access it by:
self.newClass.string = #"myString";
if(newClass.string == #"myString"){
NSLog(#"didn't work");
}
Well, I get "didn't work". Why is that?
When I write
myClass *newClass = [myClass new];
It does work. But the class and its properties gets overwritten every time the ViewController loads again.
What would you recommend? Thank you very much.
Like Kaan said, you forgot to initialize your class, You have only declared and created a pointer for it but not the actual object, on your ViewDidLoad add
self.newClass = [[myClass alloc] init];
It does work. But the class and its properties gets overwritten every
time the ViewController loads again.
That's because every time that specific Viewcontroller loads you are reinitializing the class.
If you want a persistent class through all your program look for the singleton pattern.
This is used in the case when you want to have only 1 instance of a certain object, if you try to initialize another instance of that object you will just receive the one you already have.
PD: newClass.string == #"myString" is wrong.
Use the isEqualToString method when comparing strings.

How to share information across controllers?

I recently started programming my first Cocoa app. I have ran into a problem i hope you can help me with.
I have a MainController who controls the user browsing his computer and sets some textfield = the chosen folder.
I need to retrieve that chosen folder in my AnalyzeController in order to do some work. How do i pass the textfield objectValue from the MainController to the AnalyzeController?
Thanks
Alright this it what i came up with:
MainController.h:
#import "AnalyzeController.h"
#interface MainController : NSObject {
AnalyzeController* analyzeControl;
}
MainController.c:
analyzeControl = [[AnalyzeController alloc]init];
[analyzeControl setDevelopmentPath:filename];
AnalyzeController.h:
#interface AnalyzeController : NSObject {
NSString* developmentPath;
}
#property(assign) NSString* developmentPath;
AnalyzeController.c:
#synthesize developmentPath;
NSLog(#"FINAL TEST: %#", developmentPath);
But i end up with the test returning NULL. I was a bit unsure about what the property parameter should be. Can you help? Or did i get it all wrong?
How do i pass the textfield objectValue from the MainController to the AnalyzeController?
Do that.
[analyzeController setFolderPath:self.mainFolderPath];
I assume that either you bound the text field to the mainFolderPath property, or you assigned to the property when the field's value changed.
I also assume that, in writing the AnalyzeController, you gave it a property named folderPath or at least a setter named setFolderPath:.