I have a Model Class like this:
Header:
#interface RTSecurityModel : NSObject
{
NSString *code;
}
#property NSString *code;
#end
Implementation:
#implementation RTSecurityModel
#synthesize code;
#end
Then I have my App Delegate:
Header:
#interface RTAppDelegate : NSObject <NSApplicationDelegate>
{
RTSecurityModel *security;
}
#property (assign) IBOutlet NSWindow *window;
#property RTSecurityModel *security;
#end
Implementation:
#implementation RTAppDelegate
#synthesize security;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
security = [[RTSecurityModel alloc] init];
security.code = #"test";
}
Then in my MainMenu.xib I've create a label and in the Bindings Inspector set "Bind To: App Delegate" with "Model Key Path: security.code".
But nothing is showing when I'm starting my application.
I tried soooo many ways to bind this variable, but no one gave success.
Please help me not to hate XCode and Cocoa!
UPD: http://www.experts-exchange.com/Programming/Languages/C/A_3381-Simple-Binding-Cocoa-GUI-Application-without-Outlets.html
Here is the sample how to set Property and Label value by editing the Text Field
But is there a way to edit Label without editing the Text Field? Or without Text Field at all?
UPD2:
You must not create another instance of Object
security = [[RTSecurityModel alloc] init]; // Kill this
Many many thanks to Viktor Lexington
Instead of using security.code as the model path use code. Use the class RTSecurityModel in the value section of the bindings tab instead of the AppDelegate.
Here is a demo project.
Do not bind the Text Field Cell, use the Text Field.
You can check if a value is null if you fill the Null Placeholder with text, will it show that text instead? Then in time of binding the value it null.
To see your RTSecurityModel in the Interface Builder you must let it know your class, it won't look for it.
Add an Object and then set the custom class of it to RTSecurityModel.
Then you can choose this object and set the referencing outlet to the property in the App Delegate.
Assignment will now be directly reflected in the label.
I can think of two ways to solve this programmatically without Interface Builder:
Key Value Coding
// add an observer for the value on the object that has the method below implemented
[self addObserver: self forKeyPath: #"security.code" options: NSKeyValueObservingOptionNew context: NULL];
// method will be called when the observer has 'seen' a value change
-(void) observeValueForKeyPath: (NSString *)keyPath ofObject: (id) object change: (NSDictionary *) change context: (void *) context {
label.text = ...
}
Use a custom setter for code (#synthesize will still create the getter for you)
- (void)setCode:(NSString *)aString {
label.text = aString;
}
Related
I am a newbie in using Bindings. I started developing a sample application which has a table view and arraycontroller. My data is a array of strings.
I am able to display the strings in the array after doing all the necessary binding using interface builder. But when I try to edit any row of the table view I get the following exception.
I have no idea why this is coming.
I used following code -
.h file
import
interface AppDelegate : NSObject <NSApplicationDelegate>
property (assign) IBOutlet NSWindow *window;
property (nonatomic,weak) IBOutlet NSTableView *tableView;
property (nonatomic,strong) NSMutableArray *arrayContents;
end
.m file
implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { }
-(id)init
{
self = [super init] ;
if (nil!=self)
{
_arrayContents = [[NSMutableArray alloc] init];
[_arrayContents addObject:#"asdasdasd"];
[_arrayContents addObject:#"asdasdasdsasdc"];
}
return self;
}
end
Can any one help me in solving this and point out the mistake?
Thnx.
2013-06-26 11:15:56.055 sample[4643:303] Exception detected while handling key input.
2013-06-26 11:15:56.068 sample[4643:303] Error setting value for key path of object asdasdasdsasdc (from bound object identifier: (null)): [<__NSCFConstantString 0x100002a18> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key .
I have a singleton that I'd like to use to manage the onscreen animation of my views. Here's my.
#import <Foundation/Foundation.h>
#interface OAI_AnimationManager : NSObject {
NSMutableDictionary* sectionData;
}
#property (nonatomic, retain) NSMutableDictionary* sectionData;
+(OAI_AnimationManager* )sharedAnimationManager;
- (void) checkToggleStatus : (UIView* ) thisSection;
#end
.m file
#import "OAI_AnimationManager.h"
#implementation OAI_AnimationManager
#synthesize sectionData;
+(OAI_AnimationManager *)sharedAnimationManager {
static OAI_AnimationManager* sharedAnimationManager;
#synchronized(self) {
if (!sharedAnimationManager)
sharedAnimationManager = [[OAI_AnimationManager alloc] init];
return sharedAnimationManager;
}
}
- (void) checkToggleStatus : (UIView* ) thisSection {
//get the section data dictionary
NSLog(#"%#", sectionData);
}
#end
You'll see in the .h file I added a NSMutableDictionary and am using #property/#synthesize for it's getter and setter.
In my ViewController I instantiate the animation manager as well as a series of subclasses of UIView called Section. With each one I store the data (x/y w/h, title, etc.) in a dictionary and pass that to the dictionary delcared in animation manager. In the Section class I also instantiate animation manager and add a UITapGestureRecognizer which calls a method, which passes along which section was tapped to a method (checkToggleStatus) in animation manager.
As you can I see in the method I am just logging sectionData. Problem is I am getting null for the value.
Maybe my understanding of singletons is wrong. My assumption was the class would only be instantiated once, if it was already instantiated then that existing object would be returned.
I do need all the other Section classes data as if one animates others animate in response and I can get around it by passing the tapped Section to the animation manager and doing [[Section superview] subviews] and then looping and getting the data from each that way but it seems redundant since that data is available in the ViewController when they are created.
Am I doing something wrong in trying to transfer that data? Is there a better solution? I am open to suggestions and criticisms.
Thanks
h file
#interface OAI_AnimationManager : NSObject
#property (nonatomic, retain) NSMutableDictionary* sectionData;
+(OAI_AnimationManager* )sharedAnimationManager;
- (void) checkToggleStatus : (UIView* ) thisSection;
#end
m file
static OAI_AnimationManager* _sharedAnimationManager;
#implementation OAI_AnimationManager
#synthesize sectionData = _sectionData;
+(OAI_AnimationManager *)sharedAnimationManager {
#synchronized(self) {
if (!_sharedAnimationManager) {
_sharedAnimationManager = [[OAI_AnimationManager alloc] init];
}
}
return _sharedAnimationManager;
}
- (void) checkToggleStatus : (UIView* ) thisSection {
//get the section data dictionary
NSLog(#"%#", _sectionData);
}
#end
Notice I moved your sectionData variable from the header and moved it to the implementation file. A while back, they changed it to where you can synthesize properties and specify their instance variable names along side it... hence:
sectionData = _sectionData;
I also added and underscore to the instance variable... this is a universal convention for private variables and it also will throw a compile error now if you try to type just sectionData as you did in the return statement of checkToggleStatus:. Now you either have to type self.sectionData or _sectionData.
You didn't include the code that creates an instance of your dictionary but I bet you didn't set it as self.sectionData = [[NSDictionary alloc] init] which means it would not retain the value and you would get null the next time you called it. Classic memory management mistake... I know it well because I learned the hard way hehehe
I am making an application that uses a webService to get data in a JSON format... I get the data I parse them into a object NSArray ... and i use it .. it works fine ...
Now, if the user clicks a button I need to send him to an other Uiview ... which contains more data about the clicked object ..
The problem is here ... I don't want to request again and download the result from the server ... because i already did ... All I want is to have access to that NSArray that I have in the first UIViewController.
You can add on AnotherView.h another property:
#property (nonatomic, retain) NSArray *jsonData;
On AnotherView.m synthesize it. When you are going to to call AnotherView from InitialView, you can set jsonData with the data you retrieved on InitialView.
Create a custom initializer in your other view controller like so:
#import <UIKit/UIKit.h>
#interface OtherViewController : UIViewController
#property (nonatomic, strong) NSArray *myArray;
- (id)initWithArray:(NSArray *)anArray;
#end
Then implement it like so:
#import "OtherViewController.h"
#implementation OtherViewController
#synthesize myArray=_myArray;
- (id)initWithArray:(NSArray *)anArray {
if (!(self = [self initWithNibName:#"OtherViewController" bundle:nil]))
return nil;
if (!anArray) {
#throw [NSException exceptionWithName:#"OtherViewControllerBadInitCall" reason:#"array is nil" userInfo:nil];
}
_myArray = anArray;
return self;
}
//...
#end
You can then init and display your controller like so:
OtherViewController *otherViewController = [[OtherViewController alloc] initWithArray:greatJSONArray];
[self.navigationController pushViewController:otherViewController animated:YES];
There you go.
You can set the array as the property. You can either create a new class and set the array as the property and after you fetch the array, set the property. Or, you can create a property of the existing UIVIewController Class and pass the object.
Either way, you have to set property.
You could define a new property in your second ViewController that holds an NSArray and pass the firt array to the second ViewController before show it.
Well you have not outlined whether you send the data forward or backward. In the later case you will need to implement protocol and delegate(Define your own protocol) but for the prior case you just need to create the property of the Object you want to access in any other class. In case of web-services it is better to use protocol and delegates if u abide by the norms of MVC architecture.
I'm teaching myself cocoa and enjoying the experience most of the time. I have been struggling all day with a simple problem that google has let me down on. I have read the Cocoa Bindings Program Topics and think I grok it but still can't solve my issue.
I have a very simple class called MTSong that has various properties. I have used #synthesize to create getter/setters and can use KVC to change properties. i.e in my app controller the following works:
mySong = [[MTSong alloc]init];
[mySong setValue:#"2" forKey:#"version"];
In case I am doing something noddy in my class code MTSong.h is:
#import <Foundation/Foundation.h>
#interface MTSong : NSObject {
NSNumber *version;
NSString *name;
}
#property(readwrite, assign) NSNumber *version;
#property(readwrite, assign) NSString *name;
#end
and MTSong.m is:
#import "MTSong.h"
#implementation MTSong
- (id)init
{
[super init];
return self;
}
- (void)dealloc
{
[super dealloc];
}
#synthesize version;
#synthesize name;
#end
In Interface Builder I have a label (NSTextField) that I want to update whenever I use KVC to change the version of the song. I do the following:
Drag NSObjectController object into the doc window and in the Inspector->Attributes I set:
Mode: Class
Class Name: MTSong
Add a key called version and another called name
Go to Inspector->Bindings->Controller Content
Bind To: File's Owner (Not sure this is right...)
Model Key Path: version
Select the cell of the label and go to Inspector
Bind to: Object Controller
Controller Key: mySong
Model Key Path: version
I have attempted changing the Model Key Path in step 2 to "mySong" which makes more sense but the compiler complains. Any suggestions would be greatly appreciated.
Scott
Update Post Comments
I wasn't exposing mySong property so have changed my AppController.h to be:
#import <Cocoa/Cocoa.h>
#class MTSong;
#interface AppController : NSObject {
IBOutlet NSButton *start;
IBOutlet NSTextField *tf;
MTSong *mySong;
}
-(IBAction)convertFile:(id)sender;
#end
I suspect File's owner was wrong as I am not using a document based application and I need to bind to the AppController, so step 2 is now:
Go to Inspector->Bindings->Controller Content
Bind To: App Controller
Model Key Path: mySong
I needed to change 3. to
Select the cell of the label and go to Inspector
Bind to: Object Controller
Controller Key: selection
Model Key Path: version
All compiles and is playing nice!
You want to bind the controller's content to the mySong key path as you suggested. What you are perhaps not doing is exposing mySong as a property or instance method in the File's Owner (typically your application delegate).
I'm trying to set up an NSCollectionView (I have done this successfully in the past, but for some reason it fails this time).
I have a model class called "TestModel", and it has an NSString property that just returns a string (just for testing purposes right now). I then have an NSMutableArray property declaration in my main app delegate class, and to this array I add instances of the TestModel object.
I then have an Array Controller that has its Content Array bound the app delegate's NSMutableArray. I can confirm that everything up to here is working fine; NSLogging:
[[[arrayController arrangedObjects] objectAtIndex:0] teststring]
worked fine.
I then have all the appropriate bindings for the collection view set up, (itemPrototype and content), and for the Collection View Item (view). I then have a text field in the collection item view that is bound to Collection View Item.representedObject.teststring. However NOTHING displays in the collection view when I start the app, just a blank white screen. What am I missing?
UPDATE: Here is the code I use (requested by wil shipley):
// App delegate class
#interface AppController : NSObject {
NSMutableArray *objectArray;
}
#property (readwrite, retain) NSMutableArray *objectArray;
#end
#implementation AppController
#synthesize objectArray;
- (id)init
{
if (self = [super init]) {
objectArray = [[NSMutableArray alloc] init];
}
return self;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
TestModel *test = [[[TestModel alloc] initWithString:#"somerandomstring"] autorelease];
if (test) [objectArray addObject:test];
}
#end
// The model class (TestModel)
#interface TestModel : NSObject {
NSString *teststring;
}
#property (readwrite, retain) NSString *teststring;
- (id)initWithString:(NSString*)customString;
#end
#implementation TestModel
#synthesize teststring;
- (id)initWithString:(NSString*)customString
{
[self setTeststring:customString];
}
- (void)dealloc
{
[teststring release];
}
#end
And then like I said the content array of the Array Controller is bound to this "objectArray", and the Content of the NSCollectionView is bound to Array Controller.arrangedObjects. I can verify that the Array Controller has the objects in it by NSLogging [arrayController arrangedObjects], and it returns the correct object. Its just that nothing displays in the NSCollectionView.
UPDATE 2: If I log [collectionView content] I get nothing:
2009-10-21 08:02:42.385 CollViewTest[743:a0f] (
)
The problem is probably there.
UPDATE 3: As requested here is the Xcode project:
http://www.mediafire.com/?mjgdzgjjfzw
Its a menubar app, so it has no window. When you build and run the app you'll see a menubar item that says "test", this opens the view that contains the NSCollectionView.
Thanks
The problem is that your not correctly using KVC. There is two things you can do.
Method 1: Simple but not so elegant
Use the following code to add the object to the array
[[self mutableArrayValueForKey:#"objectArray"] addObject:test];
This isn't so elegant as you have to specify the variable using a string value, so you will not get compiler warnings when spelt incorrectly.
Method 2: Generate the KVO methods needed for the array "objectArray".
Select the property in your interface declaration
Select Scripts (the script icon in the menubar) > Code > Place
accessor decls on Clipboard
Paste the declarations in the
appropriate spot in your interface file
Select Scripts > Code > Place
accessor defs on Clipboard
Paste the definitions in the
appropriate spot in your implementation file
You can then use a method that looks like
[self insertObject:test inObjectArrayAtIndex:0];