in classA i have :
classB *classBI=[[classB alloc]init];
bits=[classBI data]; //bits has a property here in classA,it gets data from B/
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(38.f, 20.f, 244.f, 43.f)];
label.text = bits;
in classB i have :
#property (nonatomic,retain) NSString *data; //in .h file
data=#"no data"; //at the init method of classB .
//then after a while when something is happen in classB , data is changed to :
data=[NSString stringWithFormat:#"data:%f,%f,%f,%f,%f,%f",
dataBits[0],dataBits[1],dataBits[2],dataBits[3],dataBits[4],dataBits[5] ];
classA is the main scene (cocos2d but it does not matter). at the start i can really see in the label that in classA the word "no data", but when data is changed in classB, i cant see that change in the label that is on screen. it stay with the word: "no data "
why is that ?
why i lost data ?
if i NSLOG data in classB ,right after it changed in there,i can see that its not null,and it has the new value. something is wrong with the instance of B ,in A, that get this string.
When you set the label's text to the string pointed to by bits, it is storing a reference to that string. Later when you change data, it is creating a new string which data is pointing to, but bits and the label both still have the original pointer which is looking at the original string. You want to update them whenever data is changed:
There are several approaches to take in this situation, but one of the easiest would be to observe the data property of classBI for changes, and update both bits and the label whenever it changes:
[classBI addObserver:self
forKeyPath:#"data"
options:NSKeyValueObservingOptionNew
context:NULL];
Then, whenever data changes, this method will be called:
// Note that you will need to get a reference to 'label' in order for this to work:
- (void) observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
if ([keyPath isEqual:#"data"]) {
bits = [change objectForKey:NSKeyValueChangeNewKey];
label.text = bits;
}
}
Related
I'm trying to add a bindable property to a custom NSPopUpButton subclass.
I've created a "selectedKey" property, which is meant to store a NSString associated with selected menu item.
In control init, I set self as button target and an action for the button (valueChanged:), which in turn sets "selectedKey" in accordance with user selection:
#interface MyPopUpButton : NSPopUpButton {
NSMutableDictionary *_items;
NSString *_selectedKey;
}
#property(nonatomic, readwrite, copy) NSString* selectedKey;
- (void)addItemWithTitle:(NSString *)title andKey:(NSString *)key;
#end
#implementation MyPopUpButton
- (instancetype)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
if (self) {
_items = [NSMutableDictionary new];
[NSObject exposeBinding:#"selectedKey"];
[super setTarget:self];
[super setAction:#selector(valueChanged:)];
}
return self;
}
- (void)addItemWithTitle:(NSString *)title andKey:(NSString *)key {
[super addItemWithTitle:title];
[_items setValue:title forKey:key];
}
- (void)valueChanged:(id)sender {
for (NSString *aKey in [_items allKeys]) {
if ([[_items valueForKey:aKey] isEqualToString:[self titleOfSelectedItem]]) {
self.selectedKey = aKey;
}
}
}
- (void)setSelectedKey:(NSString *)selectedKey {
[self willChangeValueForKey:#"selectedKey"];
_selectedKey = selectedKey;
[self didChangeValueForKey:#"selectedKey"];
[self selectItemWithTitle:[_items valueForKey:selectedKey]];
}
#end
This seems to work as expected: "selectedKey" property is changed when user changes PopUpButton selection.
Unfortunately, trying to bind this property, doesn't work.
[selectButton bind:#"selectedKey" toObject:savingDictionary withKeyPath:key options:#{NSContinuouslyUpdatesValueBindingOption : #YES }]
When selection is changed bind object is not updated accordingly.
What am I doing wrong?
I've created a "selectedKey" property, which is meant to store an NSString associated with selected menu item.
Bindings is definitely the way to go here, but your use of bind:toObject:withKeyPath:options is incorrect.
The value that you pass to the first argument must be one of the predefined values made available by Apple for that particular control. For NSPopUpButton objects, the available values are documented in the NSPopUpButton Bindings Reference. When you look through this document you'll see that there is no selectedKey option. There is however a selectedValue which has the following description:
An NSString that specifies the title of the selected item in the NSPopUpButton.
Thus the correct way to set up the binding is as follows:
[self.btn bind:#"selectedValue"
toObject:self
withKeyPath:#"mySelectedString"
options:nil];
This is all you need to do: when the action selector is fired the property stored at the keyPath you passed in as the third argument will already have been updated. This means that you can (i) get rid of the setSelectedKey method entirely, (ii) remove exposeBinding line, and (iii) remove the code within valueChanged: - Cocoa has already done this bit.
The example below implements just two methods, but, if I've understood your intentions, they should be all you need:
- (void)awakeFromNib {
self.btn.target = self;
self.btn.action = #selector(popUpActivity:);
[self.btn bind:#"selectedValue"
toObject:self
withKeyPath:#"mySelectedString"
options:nil];
// I've added a couple of additional bindings here; they're
// not required, but I thought they'd be instructive.
[self.btn bind:#"content"
toObject:self
withKeyPath:#"myItems"
options:nil];
[self.btn bind:#"selectedIndex"
toObject:self
withKeyPath:#"mySelectedIndex"
options:nil];
// Now that you've set the bindings up, use them!
self.myItems = #[#"Snow", #"Falling", #"On", #"Cedars"];
self.mySelectedIndex = #3; // "Cedars" will be selected on startup
// no need to set value of mySelectedString, because it will be
// updated automatically by the selectedIndex binding.
NSLog("%#", self.mySelectedString) // -> "Cedars"
}
- (void)popUpActivity:(id)sender {
NSLog(#"value of <selectedIndex> -> %#", self.mySelectedIndex);
NSLog(#"value of <selectedString> -> %#", self.mySelectedString);
}
A final point worth making is that none of the above should be a part of an NSPopUpButton subclass. It looks like you can - and therefore should - do everything you need to do without a custom subclass of this control. In my demo-app the code above belongs to the ViewController class, you should try doing this also.
I'm having trouble getting KVO working with text fields that are bound together in a Cocoa app. I have gotten this to work when setting strings in NSTextFields with buttons but it is not working with bindings. As always, any help from Stack Overflow would be greatly appreciated.
Purpose of my code is to:
bind several text fields together
when a number is input in one field, have the other fields automatically update
observe the changes in the text fields
Here's my code for MainClass which is an NSObject subclass:
#import "MainClass.h"
#interface MainClass ()
#property (weak) IBOutlet NSTextField *fieldA;
#property (weak) IBOutlet NSTextField *fieldB;
#property (weak) IBOutlet NSTextField *fieldC;
#property double numA, numB, numC;
#end
#implementation MainClass
static int MainClassKVOContext = 0;
- (void)awakeFromNib {
[self.fieldA addObserver:self forKeyPath:#"numA" options:0 context:&MainClassKVOContext];
[self.fieldB addObserver:self forKeyPath:#"numB" options:0 context:&MainClassKVOContext];
[self.fieldC addObserver:self forKeyPath:#"numC" options:0 context:&MainClassKVOContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context != &MainClassKVOContext) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}
if (object == self.fieldA) {
if ([keyPath isEqualToString:#"numA"]) {
NSLog(#"fieldA length = %ld", [_fieldA.stringValue length]);
}
}
if (object == self.fieldB) {
if ([keyPath isEqualToString:#"numB"]) {
NSLog(#"fieldB length = %ld", [_fieldB.stringValue length]);
}
}
if (object == self.fieldC) {
if ([keyPath isEqualToString:#"numC"]) {
NSLog(#"fieldC length = %ld", [_fieldC.stringValue length]);
}
}
}
+ (NSSet *)keyPathsForValuesAffectingNumB {
return [NSSet setWithObject:#"numA"];
}
+ (NSSet *)keyPathsForValuesAffectingNumC {
return [NSSet setWithObject:#"numA"];
}
- (void)setNumB:(double)theNumB {
[self setNumA:theNumB * 1000];
}
- (double)numB {
return [self numA] / 1000;
}
- (void)setNumC:(double)theNumC {
[self setNumA:theNumC * 1000000];
}
- (double)numC {
return [self numA] / 1000000;
}
- (void)setNilValueForKey:(NSString*)key {
if ([key isEqualToString:#"numA"]) return [self setNumA: 0];
if ([key isEqualToString:#"numB"]) return [self setNumB: 0];
if ([key isEqualToString:#"numC"]) return [self setNumC: 0];
[super setNilValueForKey:key];
}
#end
And here is the binding for one of the text fields:
Key-Value Observing on NSTextFields
In your -awakeFromNib method's implementation, you've written
[self.fieldA addObserver:self
forKeyPath:#"numA"
options:0
context:&MainClassKVOContext];
This doesn't do what you're hoping it will: self.fieldA is not key-value coding compliant for the key numA: if you try sending -valueForKey: or -setValue:forKey: with the key #"numA" to self.fieldA, you'll get the following exceptions:
[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key numA.
and
[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key numA.
As a result, the NSTextField instances are not key-value observing compliant for #"numA", either: the first requirement to be KVO-compliant for some key is to be KVC-compliant for that key.
It is, however, KVO-compliant for, among other things, stringValue. This allows you to do what I described earlier.
Note: None of this is altered by the way that you've set up bindings in Interface Builder. More on that later.
The Trouble With Key-Value Observing on NSTextField's stringValue
Observing an NSTextField's value for #"stringValue" works when -setStringValue: gets called on the NSTextField. This is a result of the internals of KVO.
A Brief Trip Into KVO Internals
When you begin observing an key-value observing an object for the first time, the object's class is changed--its isa pointer is changed. You can see this happening by overriding -addObserver:forKeyPath:options:context:
- (void)addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context
{
NSLog(#"%p, %#", self->isa, NSStringFromClass(self->isa));
[super addObserver:observer
forKeyPath:keyPath
options:options
context:context];
NSLog(#"%p, %#", self->isa, NSStringFromClass(self->isa));
}
In general, the name of the class changes from Object to NSKVONotifying_Object.
If we had called -addObserver:forKeyPath:options:context: on an instance of Object with with the key path #"property"--a key for which instances of Object are KVC-compliant--when next we call -setProperty: on our instance of Object (in fact, now an instance of NSKVONotifying_Object), the following messages will be sent to the object
-willChangeValueForKey: passing #"property"
-setProperty: passing #"property"
-didChangeValueForKey: passing #"property"
Breaking within any of these methods reveal that they're called from the undocumented function _NSSetObjectValueAndNotify.
The relevance of all of this is that the method -observeValueForKeyPath:ofObject:change:context: is called on the observer that we added to our instance of Object for the key path #"property" from -didChangeValueForKey:. Here's the top of the stack trace:
-[Observer observeValueForKeyPath:ofObject:change:context:]
NSKeyValueNotifyObserver ()
NSKeyValueDidChange ()
-[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] ()
How does this relate to NSTextField and #"stringValue"?
In your previous setup, you were adding an observer to your text field on -awakeFromNib. This meant that your text field was already an instance of NSKVONotifying_NSTextField.
You would then press one or another button which in turn would call -setStringValue on your text field. You were able to observe this change because--as an instance of NSKVONotifying_NSTextField--your text field, upon receiving setStringValue:value actually received
willChangeValueForKey:#"stringValue"
setStringValue:value
didChangeValueForKey:#"stringValue"
As above, from within didChangeValueForKey:#"stringValue", all the objects which are observing the text field's value for #"stringValue" are notified that the value for this key has changed in their own implementations of -observeValueForKeyPath:ofObject:change:context:. In particular, this is true for the the object which you added as an observer for the text field in -awakeFromNib.
In summary, you were able to observe the change in the text field's value for #"stringValue" because you added yourself as an observer of the text field for that key and because -setStringValue was called on the text field.
So What's The Problem?
So far under the guise of discussing "The Trouble With Key-Value Observing on NSTextFields" we've only actually made sense of the opening sentence
Observing an NSTextField's value for #"stringValue" works when -setStringValue: gets called on the NSTextField.
And that sounds great! So what's the problem?
The problem is that -setStringValue: does not get called on the text field as the user is typing into it OR even after the user has ended editing (by tabbing out of the text field, for example). (Furthermore, -willChangeValueForKey: and -didChangeValueForKey: are not called manually. If they were, our KVO would work; but it doesn't.) This means that while our KVO on #"stringValue" works when -setStringValue: is called on the text field, it does NOT work when the user herself enters text.
TL;DR: KVO on the #"stringValue" of an NSTextField isn't good enough since it doesn't work for user input.
Binding An NSTextField's Value To A String
Let's try using bindings.
Initial Setup
Create an example project with a separate window controller (I've used the creative name WindowController) complete with XIB. (Here's the project I'm starting from on GitHub.) In WindowController.m added a property stringA in a class extension:
#interface WindowController ()
#property (nonatomic) NSString *stringA;
#end
In Interface Builder, create a text field and open the Bindings Inspector:
Under the "Value" header, expand the "Value" item:
The pop-up button next to the "Bind to" checkbox presently has "Shared User Defaults Controller" selected. We want to bind the text field's value to our WindowController instance., so select "File's Owner" instead. When this happens, the "Controller Key" field will be emptied and the "Model Key Path" field will be changed to "self".
We want to bind this text field's value to our WindowController instance's property stringA so change the "Model Key Path" to self.stringA:
At this point, we are done. (Progress so far on GitHub.) We have successfully bound the text field's value to our WindowController's property stringA.
Testing It Out
If we set stringA to some value in -init, that value will show up in the text field when the window loads:
- (id)init
{
self = [super initWithWindowNibName:#"WindowController"];
if (self) {
self.stringA = #"hello world";
}
return self;
}
And already, we have set up bindings in the other direction as well; upon ending editing in the text field, the our window controller's property stringA is set. We can check this by overriding it's setter:
- (void)setStringA:(NSString *)stringA
{
NSLog(#"%s: stringA: <<%#>> => <<%#>>", __PRETTY_FUNCTION__, _stringA, stringA);
_stringA = stringA;
}
Reply Hazy, Try Again
After typing some text into the text field and pressing tab, we'll see printed out
-[WindowController setStringA:]: stringA: <<(null)>> => <<some text>>
This looks great. Why haven't we been talking about this all along??? There's a bit of a hitch here: the pesky pressing tab thing. Binding a text field's value to a string does not set the string value until editing has ended in the text field.
A New Hope
However, there is still hope! The Cocoa Binding Documentation for NSTextField states that one binding option available for an NSTextField is NSContinuouslyUpdatesValueBindingOption. And lo and behold, there is a checkbox corresponding to this very option in the Bindings Inspector for NSTextField's value. Go ahead and check that box.
With this change in place, as we type things in, the update to the window controller's stringA property is continuously logged out:
-[WindowController setStringA:]: stringA: <<(null)>> => <<t>>
-[WindowController setStringA:]: stringA: <<t>> => <<th>>
-[WindowController setStringA:]: stringA: <<th>> => <<thi>>
-[WindowController setStringA:]: stringA: <<thi>> => <<thin>>
-[WindowController setStringA:]: stringA: <<thin>> => <<thing>>
-[WindowController setStringA:]: stringA: <<thing>> => <<things>>
-[WindowController setStringA:]: stringA: <<things>> => <<things >>
-[WindowController setStringA:]: stringA: <<things >> => <<things i>>
-[WindowController setStringA:]: stringA: <<things i>> => <<things in>>
Finally, we're continuously updating the window controller's string from the text field. The rest is easy. As a quick proof of concept, add a couple more text fields to the window, bind them to stringA and set them to update continuously. You at this point have three synchronized NSTextFields! Here's the project with three synchronized text fields.
The Rest of the Way
You're wanting to setup three textfields that display numbers that have some relationship to each other. Since we're dealing with numbers now, we'll remove the property stringA from WindowController and replace it with numberA, numberB and numberC:
#interface WindowController ()
#property (nonatomic) NSNumber *numberA;
#property (nonatomic) NSNumber *numberB;
#property (nonatomic) NSNumber *numberC;
#end
Next we'll bind the first text field to numberA on File's Owner, the second to numberB, and so on. Finally we just need to add a property which is the quantity which is being represented in these different ways. Let's call that value quantity.
#interface WindowController ()
#property (nonatomic) NSNumber *quantity;
#property (nonatomic) NSNumber *numberA;
#property (nonatomic) NSNumber *numberB;
#property (nonatomic) NSNumber *numberC;
#end
We'll need the constant conversion factors to transform from the units of quantity to the units of numberA and so forth, so add
static float convertToA = 1000.0f;
static float convertToB = 573.0f;
static float convertToC = 720.0f;
(Of course, use the numbers that are relevant to your situation.) With this much, we can implement the accessors for each of the numbers:
- (NSNumber *)numberA
{
return [NSNumber numberWithFloat:self.quantity.floatValue * convertToA];
}
- (void)setNumberA:(NSNumber *)numberA
{
self.quantity = [NSNumber numberWithFloat:numberA.floatValue * 1.0f/convertToA];
}
- (NSNumber *)numberB
{
return [NSNumber numberWithFloat:self.quantity.floatValue * convertToB];
}
- (void)setNumberB:(NSNumber *)numberB
{
self.quantity = [NSNumber numberWithFloat:numberB.floatValue * 1.0f/convertToB];
}
- (NSNumber *)numberC
{
return [NSNumber numberWithFloat:self.quantity.floatValue * convertToC];
}
- (void)setNumberC:(NSNumber *)numberC
{
self.quantity = [NSNumber numberWithFloat:numberC.floatValue * 1.0f/convertToC];
}
All of the different number accessors are now just indirect mechanisms for accessing quantity, and are perfect for bindings. There is only one additional thing that remains to be done: we need to make sure that observers repoll all of the numbers whenever quantity is changed:
+ (NSSet *)keyPathsForValuesAffectingNumberA
{
return [NSSet setWithObject:#"quantity"];
}
+ (NSSet *)keyPathsForValuesAffectingNumberB
{
return [NSSet setWithObject:#"quantity"];
}
+ (NSSet *)keyPathsForValuesAffectingNumberC
{
return [NSSet setWithObject:#"quantity"];
}
Now, whenever you type into one of the textfields, the others are updated accordingly. Here's the final version of the project on GitHub.
Have mercy, newbie here.
I have a NSString value that I construct on the fly, which is the name of a UILabel instance. I want to send the label a message to update its text. But, the two data types don't match. Here's enough code (I think):
In header file:
IBOutlet UILabel *Clue1; // IBOutlet and IBAction are IDE flags
IBOutlet UILabel *Clue2; // IB = interface builder
IBOutlet UILabel *Clue3;
In implementation file:
- (IBAction) newPuzzle:(id)sender { // Clear all fields & get new clue
[Clue1 setText:#""]; // Clear the fields
[Clue2 setText:#""];
[Clue3 setText:#""];
// Send up a randomly chosen new clue
NSArray *clues = [NSArray arrayWithObjects:#"222", #"333", nil];
NSInteger randomIndex = arc4random()%[clues count];
NSString *aClue = [clues objectAtIndex:randomIndex];
// The clue will be split into component digits and each piece sent to a different label
for (NSInteger charIdx = 0; charIdx < aClue.length; charIdx++) {
NSString *cluePos = [NSString stringWithFormat:#"Clue%d", charIdx + 1];
NSLog(#"%#", cluePos); // works
[cluePos setText:#"test"]; // Xcode notes the type mismatch
}
}
There are some similar questions on SO, but none are close enough for me to recognize that they apply to my case, at least as far as I can tell. Using the terminology of another language (R), I need to "coerce" the class of cluePos from NSString to UILabel. I'm on Xcode 4.2.1 and OSX 10.7.2.
TIA.
You can't coerce a string into a label because they are fundamentally different. The string doesn't have any knowledge of your view controller class or it's properties (some of which happen to be labels).
You can however use the valueForKey: method to get a property of an object by name, where the name is specified as a string. So to get a property called Clue1 on my view controller I'd say:
UILabel *label = [self valueForKey:#"Clue1"];
Or in your case, this:
NSString *cluePos = [NSString stringWithFormat:#"Clue%d", charIdx + 1];
UILabel *label = [self valueForKey:cluePos];
label.text = #"test";
(I'm assuming 'self' in this case refers to the view controller, but you can call this on any object that has properties.)
Another way to do this is to turn your string into a selector using NSSelectorFromString. That would look like this:
SEL selector = NSSelectorFromString(#"Clue1");
UILabel *label = [self performSelector:selector];
For your purposes either solution works equally well, however the advantage of using the selector is that you can pass arguments to the method call (so you could call a method that returns an object, not just access a property or IBOutlet).
Note that both of these methods will raise an exception if you try to access a property or call a method that doesn't exist. You can test if the property exists before calling it by saying:
SEL selector = NSSelectorFromString(#"Clue1");
BOOL labelExists = [self respondsToSelector:selector];
if (labelExists)
{
UILabel *label = [self performSelector:selector];
label.text = #"test";
}
else
{
//do something else
}
I've added the following image to help illustrate the problem better:
Hi,
I'm looking for the best starting point to alter the data stored my core data model directly - speaking as someone who's new to the area. From my reading I'm pretty confident I shouldn't touch my NSArrayController, which was my natural instinct, and that I should always tackle the model. This makes sense but because I've used bindings and core data, xcode has generated everything for me and I don't have a sense of building up a class from scratch myself.
For my initial task, I have a 'jobs' entity and NSArrayController. It has a jobTotalHours attribute that's a string in the 00:00:00 format and has a corresponding 'Hours' column for each job in an NSTableView. Separate to this, I have a stopwatch button that's linked to a text field next to it, displaying time as a 00:00:00 string. I have a class working that starts and stops a timer counting and displays it in increments of hours, minutes and seconds.
What I need to do is to make the timer add time onto the jobTotalHours attribute for the current job highlighted in the NSTableView. The separate textfield has now been bound to display the time of the current highlighted hours column so that part's taken care of. In other words, the timer was originally adding time to a test variable and displaying it in an autonomous text field for testing reasons. Now I need it to add time onto whatever job is highlighted in a table view and I need to access the model programmatically without being sure of what step to take first.
Thanks in advance for any advice. I'll include the timer class below if it's any use. I'm pretty sure it's rough and bad but it works:
timerController.h:
#import <Cocoa/Cocoa.h>
BOOL timerStarted;
int timerCount;
int timerSeconds;
int timerMinutes;
int timerHours;
NSString *timerString;
NSString *timerFieldSeconds;
NSString *timerFieldMinutes;
NSString *timerFieldHours;
#interface timerController : NSObject <NSApplicationDelegate> {
NSWindow *window;
NSTimer *timerNoOne;
IBOutlet NSCell *timerOneOutputLabel;
IBOutlet id timerClockField;
}
-(IBAction)toggleTimerClock:(id)sender;
#property (assign) IBOutlet NSWindow *window;
#end
timerController.m:
#import "timerController.h"
#implementation timerController
-(IBAction)toggleTimerClock:(id)sender
{
if (timerStarted==FALSE) {
timerStarted = TRUE;
} else {
timerStarted = FALSE;
}
}
#synthesize window;
- (void) awakeFromNib {
// clear timer
[timerClockField setStringValue:#"00:00:00"];
// initialize timer to count each second
timerNoOne = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(updateTimerNoOne:)
userInfo:nil
repeats:YES];
}
- (void) updateTimerNoOne:(NSTimer *) timer {
if (timerStarted==FALSE) {
// do nothing. Timer is switched off.
} else {
timerCount = timerCount + 1;
timerSeconds = fmod(timerCount, 60);
timerMinutes = floor(timerCount / 60);
timerHours = floor(timerCount / 3600);
if (timerSeconds < 10) { // add a leading 0 for formatting reasons.
timerFieldSeconds = [NSString stringWithFormat:#"0%d",timerSeconds];
} else {
timerFieldSeconds = [NSString stringWithFormat:#"%d",timerSeconds];
}
if (timerMinutes < 10) {
timerFieldMinutes = [NSString stringWithFormat:#"0%d",timerMinutes];
} else {
timerFieldMinutes = [NSString stringWithFormat:#"%d",timerMinutes];
}
if (timerHours < 10) {
timerFieldHours = [NSString stringWithFormat:#"0%d",timerHours];
} else {
timerFieldHours = [NSString stringWithFormat:#"%d",timerHours];
}
NSString *timerString = [NSString stringWithFormat:#"%#:%#:%#",timerFieldHours,timerFieldMinutes,timerFieldSeconds];
//[timerClockField setStringValue:timerString];
}
}
#end
Update:
From reading some more, I'm wondering if it's a better approach for me to update the string in the textcell itself on each second of timer change and then only commit changes to the model on the timer finishing (e.g. the clock was stopped). Previously I was thinking of saving the model's jobTotalHours string second by second as this was directly altering the model and avoiding controllers, which I thought was the advised route to take.
Update:
I had a subclass set up for NSTableView and NSArrayController. I was able to use them to detect selection changes to the rows in the table and print them out to the console. The subclass was called:
#interface modelUtilController : NSObject
Which performed the above tasks fine. I now wanted an outlet to the NSManagedObject so that I could directly manipulate assets in it while keeping outlets to the NSTableView to detect changed in row selection. I read that the subclass should be
#interface modelUtilController : NSManagedObject
which I changed it to and included an outlet to the data model. This crashes the original detection for changes in row selection, so I'm doing something wrong now. Perhaps I have to separate the subclass into 2?
Update : Possibly Complete
Ok I think I've solved this after 3 days at it. As far as I can see it's working but I haven't put it fully to work yet. Basically I created a separate function that I call from my timer once every second:
void amendTotalHours(id anObject)
This function uses my jobs NSArrayController and then finds the current value in the hours column using:
NSArray *selectedObjectsArray = [anObject selectedObjects];
NSManagedObjectModel *firstSelectedObject = [selectedObjectsArray objectAtIndex:0];
NSString *readCurrentTime = [firstSelectedObject valueForKey:#"jobTotalHours"];
I then convert the string of time formatted into 00:00:00 to an integer of the total seconds. I add one onto this for each call from the timer and then convert the seconds back into a string in the 00:00:00 format. Finally, I send this back to the NSArrayController using:
[firstSelectedObject setValue:[NSString stringWithFormat:#"%#", timeValue] forKey:#"jobTotalHours"];
And cry a (maybe temporary) sigh of relief.
Ok I think I've solved this after 3 days at it. As far as I can see it's working but I haven't put it fully to work yet. Basically I created a separate function that I call from my timer once every second:
void amendTotalHours(id anObject)
This function uses my jobs NSArrayController and then finds the current value in the hours column using:
NSArray *selectedObjectsArray = [anObject selectedObjects];
NSManagedObjectModel *firstSelectedObject = [selectedObjectsArray objectAtIndex:0];
NSString *readCurrentTime = [firstSelectedObject valueForKey:#"jobTotalHours"];
I then convert the string of time formatted into 00:00:00 to an integer of the total seconds. I add one onto this for each call from the timer and then convert the seconds back into a string in the 00:00:00 format. Finally, I send this back to the NSArrayController using:
[firstSelectedObject setValue:[NSString stringWithFormat:#"%#", timeValue] forKey:#"jobTotalHours"];
And cry a (maybe temporary) sigh of relief.
First, I'm very new with objective-c and memory management, pointers, etc. No doubt my problem lies in a simple point I'm missing.
I've got a class which contains a property for an integer:
// Device.H file
#interface Device : NSObject {
#private int nodeLevel;
}
#property (readwrite, assign, nonatomic) int nodeLevel;
// Device.m file
#implementation Device
#synthesize nodeLevel;
- (id)init {
self.nodeLevel = 0;
return self;
}
I create an NSMutableArray of many Device objects, assigning the node Id:
-(NSMutableArray *)getDevices {
...
NSMutableArray *devices = [[NSMutableArray alloc] initWithCapacity:[rDevices count]];
for (NSDictionary *d in rDevices) {
Device *newDevice = [[Device alloc] init] autorelease];
newDevice.nodeLevel = d.nodeLevel;
[devices addObject: newDevice];
}
return [devices autorelease];
}
My devices array is stored on the main app delegate where I've got a property assigned to hold it:
#property (nonatomic, retain) NSMutableArray *devices;
Now here's where my problem is manifest. I'm using a tableView in another controller class to access my app delegate, pull a device from its array then set values with the int, but 'strange' things happen:
EDIT: Min/Max values for the slider are set in another part of the code to 0 and 100 respectively.
// In method cellForRowAtIndex
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
Device *d = (Device *)[[appDelegate.devices objectAtIndex:indexPath.row]];
// cell is a custom cell with a UISlider object
cell.sliderLevel.value = [d nodeLevel];
When I assign value to the device's nodeLevel, the slider is always maxed out, even if nodeLevel only equals 1 or 2.
If I do this instead, the slider is at the correct position, but I'll eventually get a "EXC_BAD_ACCESS" signal when scrolling up and down through my tableView:
// cell is a custom cell with a UISlider object
cell.sliderLevel.value = [[d nodeLevel] intValue];
I suspect that I must be assigning the value to a memory location in the first instance? In the second case it works, but I assume that my BAD_ACCESS is a result of the nodeLevel becoming "released" or something? One final note, I've also got an NSString object associated with the Device class. I access the string and assign it to a label in my cell and it never causes me problems.
Thanks in advance for taking a look.
What type is returned by the nodeLevel property in this line: "newDevice.nodeLevel = d.nodeLevel;"? The nodeLevel property in Device is an int, so you need to ensure that d.nodeLevel is returning an int, and not an NSNumber object.
If d.nodeLevel is returning an NSNumber, that would explain why calling intValue on it gets you a reasonable value, and you get a huge number if you don't call intValue on it (the huge value would be the pointer value for the NSNumber object). It would also explain why you get an EXC_BAD_ACCESS crash later on, because your NSNumber object isn't being retained.
You should probably just change this line:
newDevice.nodeLevel = d.nodeLevel;
to
newDevice.nodeLevel = [d.nodeLevel intValue];
and don't call intValue on it later on, so you would change this:
cell.sliderLevel.value = [[d nodeLevel] intValue];
to this:
cell.sliderLevel.value = [d nodeLevel];
[d nodeLevel] returns an integer, a primitive type, not an Objective-C object. Therefore, you cannot call -intValue on it, and that's why you get an EXC_BAD_ACCESS.
Further, the reason your slider is maxed out is because you haven't set its maximum value to 2.0. Because it defaults to 1.0, when you set it to any value 1.0 or higher, it will appear maxed out (in your case, both 1 and 2 appear the same). At some point, you need to call cell.sliderLevel.maximumValue = 2.0; to make the maximum value possible high enough.