NSCell's unexpected behavior - objective-c

I'm implementing a subclass of NSActionCell (inside an NSTableView), and noticing something unusual. If I set a property (isEditing) when a user clicks a cell, the value of that property is lost, because NSCell is released shortly thereafter. I assumed that this was happening because I wasn't handling copying correctly, so I added copyWithZone. Now I am seeing copyWithZone called - but it's called on an unexpected instance - and the property on that instance is NO - the default value. Each time copyWithZone is called, it's called on this same instance.
Can anyone shed a light on this behavior? I'm attaching the subclass in question, and the output I'm getting. Exactly what do I need to do for cell's properties to be retained when the user clicks on different cells?
#interface MyCell : NSActionCell <NSCoding, NSCopying>
{
}
#property (nonatomic, assign) BOOL isEditing;
#end
#implementation MyCell
- (id)init
{
if ((self = [super init]))
{
[self initializeCell];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super initWithCoder:aDecoder]))
{
[self initializeCell];
self.isEditing = [[aDecoder decodeObjectForKey:#"isEditing"] boolValue];
NSLog(#"initWithCoder %ld %i", (NSInteger)self, self.isEditing);
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[super encodeWithCoder: aCoder];
NSLog(#"encode %i", self.isEditing);
[aCoder encodeObject:[NSNumber numberWithBool:self.isEditing] forKey:#"isEditing"];
}
- (void)dealloc
{
NSLog(#"dealloc %ld %i", (NSInteger)self, self.isEditing);
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone
{
MyCell *copy;
if ((copy = [[MyCell allocWithZone:zone] init]))
{
copy.isEditing = self.isEditing;
}
NSLog(#"copy %ld %i new: %ld", (NSInteger)self, self.isEditing, (NSInteger)copy);
return copy;
}
- (void)initializeCell
{
self.isEditing = NO;
}
- (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView
{
return YES;
}
- (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag
{
if (flag)
{
self.isEditing = YES;
NSLog(#"stopTracking %ld %i", (NSInteger)self, self.isEditing);
}
}
#end
Output (produced when a user clicks the cell):
2012-11-21 08:17:59.544 SomeApp[2778:303] copy 4310435936 0 new: 4310152512
2012-11-21 08:18:00.136 SomeApp[2778:303] stopTracking 4310152512 1
2012-11-21 08:18:00.136 SomeApp[2778:303] dealloc 4310152512 1
and another click (on a different cell):
2012-11-21 08:19:24.994 SomeApp[2778:303] copy 4310435936 0 new: 4310372672
2012-11-21 08:19:25.114 SomeApp[2778:303] stopTracking 4310372672 1
2012-11-21 08:19:25.114 SomeApp[2778:303] dealloc 4310372672 1

It sounds like you want to persist the properties – is that right?
You'll probably have an easier time if you adjust your design by storing the cell properties in your model objects instead of the NSCell, and having the cell or table view delegate fetch the value from the model.
What particular behavior are you trying to achieve using this property?

Related

Cannot add CCNode to Scene

I have the IntroScene, and I wanna add a node, but it doesn't seem to work. Here are two different ways I tried doing it, and BOTH failed.
First way, failed:
in hearts2.h
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface Hearts2 : CCNode {
}
#end
in hearts2.m
#import "Hearts2.h"
#implementation Hearts2
#end
in IntroLayer.m
- (id)init
{
// Apple recommend assigning self with supers return value
self = [super init];
if (!self) return(nil);
heart2 *heart;
[self addChild:heart z:2];
// done
return self;
}
I didn't expect that to work (actually I was desperate and tried it that way as the second way just to see if it would work). The actual first attempt I tried to do was this, and it also Failed:
in hearts1.h
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface Hearts1 : CCNode
+ (Hearts1 *)node;
- (id)init;
-(void)selfAnimate;
#end
in hearts1.m
#import "Hearts1.h"
#implementation Hearts1 {
}
+ (Hearts1 *)node
{
return [[self alloc] init];
}
- (id)init
{
self = [super init];
if (!self) return(nil);
return self;
}
- (void)dealloc
{
}
- (void)onEnter
{
[super onEnter];
}
- (void)onExit
{
// always call super onExit last
[super onExit];
}
- (void)selfAnimate
{
}
#end
in IntroLayer.m
- (id)init
{
// Apple recommend assigning self with supers return value
self = [super init];
if (!self) return(nil);
heart1 *heart;
[self addChild:heart z:2];
// done
return self;
}
Please, I would do anything if someone could help me figure this out thanks everyone very much. I always get the SigABRT so I have no idea what is going wrong. I'm sure I'm just stupid and don't know how to code and missing something simple.
heart2 *heart;
You named your class Hearts2 so use the exact same name, including uppercase.
Secondly you created a variable but this will be nil. If you aren't using ARC (which you should) this will create an uninitialized object.
This will create an instance of Hearts2, assign it to the local var heart and add it as a child:
Hearts2 *heart = [Hearts2 node];
[self addChild:heart z:2];

Redefintition of SidebarDelegate as different kind of symbol

I have the following Objective C code that keeps error complaining I am redefining it when I not!! I am trying to implement a NSTableViewDelegate thing but xcode keep complaining
#import "AppDelegate.h"
#import <AppKit/AppKit.h>
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// add Hello World to sidebar
[self.Sidebar.tableView insertValue:0 atIndex:0 inPropertyWithKey:#"Hello World!"];
[self.Sidebar.tableView setDelegate: SidebarDelegate()]
// reload data
[self.Sidebar.tableView reloadData];
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
return YES;
}
#end
// Sidebar Delage
#implementation SidebarDelegate : NSObject <NSTableViewDelegate, NSTableViewDataSource>;
// setting data
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
anObject = #"Apples";
}
// height
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
{
return 20;
}
- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex
{
NSLog(#"%ld", (long)rowIndex);
return ((long)rowIndex % 2) == 0;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
return 4;
}
#end
replace
[self.Sidebar.tableView setDelegate: SidebarDelegate()];
with
SidebarDelegate *sidebarDelegate = [[SidebarDelegate alloc] init];
[self.Sidebar.tableView setDelegate:sidebarDelegate];
This line:
[self.Sidebar.tableView setDelegate: SidebarDelegate()];
implicitly creates a function named SidebarDelegate() of the form
int SidebarDelegate(void)
Thus, when you try to define a class of the same name, you overwrite the symbol in an unacceptable way. You need to initialize an instance of SidebarDelegate and pass that as the TableView's delegate not, I will presume, just pass the name of the class.

Objective C - Adding a new row to empty NSTableView

I have a View-Based NSTableView which should be initially empty. I also have "add" and "remove" buttons for adding and deleting rows from same NSTableView.
My delegate methods and method for adding new row look like this:
#import "PreferencesApiKeysViewController.h"
#import "UserKeys.h"
#interface PreferencesApiKeysViewController ()
#property (weak) IBOutlet NSTableView *keysTableView;
#end
#implementation PreferencesApiKeysViewController
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// Get a new ViewCell
NSTableCellView *cellView = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
if([tableColumn.identifier isEqualToString:#"userKeysColumn"]) {
UserKeys *allKeys = [self.allKeys objectAtIndex:row];
cellView.textField.stringValue = allKeys.userKeyName;
return cellView;
}
return cellView;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
NSLog(#"Initial rows: %li", (unsigned long)[self.allKeys count]);
return [self.allKeys count];
}
- (IBAction)addKey:(id)sender {
UserKeys *newKey = [[UserKeys alloc] initWithKeyName:#""
apiID:#""
apiCode:#"" ];
[self.allKeys addObject:newKey];
NSLog(#"Total rows: %li", (unsigned long)self.allKeys.count);
NSInteger newRowIndex = self.allKeys.count;
if (newRowIndex == 0) {
NSLog(#"No rows.");
newRowIndex = 0;
} else {
NSLog(#"Has rows.");
newRowIndex = self.allKeys.count-1;
}
NSLog(#"New Index: %ld", (long)newRowIndex);
[self.keysTableView insertRowsAtIndexes:[NSIndexSet indexSetWithIndex:newRowIndex] withAnimation:NSTableViewAnimationSlideDown];
[self.keysTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRowIndex] byExtendingSelection:NO];
[self.keysTableView scrollRowToVisible:newRowIndex];
}
#end
and in my AppDelegate.m, I'm calling my view like this:
#import "AppDelegate.h"
#import "UserKeys.h"
#include "PreferencesApiKeysViewController.h"
#interface AppDelegate()
#property (nonatomic,strong) IBOutlet PreferencesApiKeysViewController *prefsViewController;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.prefsViewController = [[PreferencesApiKeysViewController alloc] initWithNibName:#"PreferencesApiKeysViewController" bundle:nil];
// Create few rows as dummy data
/*
UserKeys *key1 = [[UserKeys alloc] initWithKeyName:#"key one"
apiID:#"123"
apiCode:#"xxx" ];
UserKeys *key2 = [[UserKeys alloc] initWithKeyName:#"key two"
apiID:#"456"
apiCode:#"yyy" ];
NSMutableArray *tempKeys = [NSMutableArray arrayWithObjects:key1, key2, nil];
self.prefsViewController.allKeys = tempKeys;
*/
// done.
[self.window.contentView addSubview:self.prefsViewController.view];
self.prefsViewController.view.frame = ((NSView*)self.window.contentView).bounds;
}
#end
Now, the thing here is that if I uncomment those few lines for adding dummy data in AppDelegate and launch my app, everything works fine. I can add/remove rows without any problem, I can even delete all of them and add a new one after that.
But, if I comment those lines again, my app starts with an empty table (which is what I need), and when I want to add new row I get an error:
*** Assertion failure in -[NSTextFieldCell _objectValue:forString:errorDescription:], /SourceCache/AppKit/AppKit-1187.34/AppKit.subproj/NSCell.m
Looking further the thread, I see only one line referencing to my app:
0x0000000100003116 -[PreferencesApiKeysViewController tableView:viewForTableColumn:row:] + 406
I'm guessing that my TableView is not properly instantiated if allKeys array doesn't contain any single object really, but I'm not sure how to fix that? How to create an empty NSTableView and have the ability to add the first row by myself when "add" button is clicked (without adding any dummy data to allKeys)?
Add this to the implementation of PreferencesApiKeysViewController:
#implementation PreferencesApiKeysViewController
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle {
if ((self = [super initWithNibName:nibName bundle:bundle])) {
self.allKeys = [NSMutableArray array];
}
return self;
}
// add the following only if not using ARC
- (void)dealloc {
[_allKeys release];
[super dealloc];
}
#end
What's happening when you allow that commented-out code to be compiled in is that you assign an NSMutableArray instance to the allKeys instance variable. As a result, adding and removing items works as you would expect.
Generally, in a case like this, the class that manages the NSMutableArray should override the init method to make sure the array is properly initialized to an empty array. (By default, non-IBOutlet instance variables are initialized to nil, which won't allow you to properly add or remove items from it).

Set value of NSTextField

I am trying to set the value of an NSTextField, but it's not working properly.
I have a button linked to an IBAction, and when I set it using self, it works fine:
#import <Foundation/Foundation.h>
#interface TestMessage : NSObject {
IBOutlet NSTextField *text;
}
- (IBAction) setMessage: (id) controller;
- (void) Message:(NSString *) myMessage;
#end
#import "TestMessage.h"
#implementation TestMessage
- (IBAction) setMessage: (id) controller {
// This works
[self Message:#"Hello"];
// but this doesn't
TestMessage * messageTest= [TestMessage new];
[messageTest Message:#"Hi"];
}
- (void) Message: (NSString *) myMessage {
[text setStringValue: myMessage];
NSLog(#"Message Was Called");
// This returns <NSTextField: 0x1001355b0> when called
// using self, but null when called the other way.
NSLog(#"%#", text);
}
#end
I've searched for a while, but still can't find the answer.
I guess it has something to do with the delegate, but I'm not sure.
Thanks in advance.
Are you sure message is called when you call it from anotherFuntion? If anotherFuntion is a method of another class, calling [self message:] won't work as you expected to...
I know this is an old post, but I have been fiddling with the same issue today.
You have to return string value in textfield:
[textField stringValue];
The code
TestMessage * messageTest = [TestMessage new];
is unusual, specifically new. I'm going to assume that new is just a class method does normal alloc/init equivalent to
TestMessage * messageTest = [[TestMessage alloc] init];
The main problem is that IBOutlet NSTextField *text will be initialized only if the class TestMessage is loaded with a Nib file. It would have to be named as the class of an object in Interface Builder, like so
and you would have to implement initWithCoder and encodeWithCoder something like this in order to extract your field value from the IB encoding:
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
self.text = [coder decodeObjectForKey:#"text"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)coder
{
[super encodeWithCoder:coder];
[coder encodeObject:self.text forKey:#"text"];
}
Fundamentally, IBOutlet fields do not get wired up wherever you create an instance of that class. If they did, how would you express that field A should be wired to UI object A and field B should be wired to UI object B? The connection is established only in the context of loading a class from a Nib file.

How to assert a UILabel.text property is equal to an instance of NSString in objective-c

I'm new to objective-c and I'm finding that I don't know how to correctly assert that a text property on some given label is equal to a raw string value. I'm not sure if I just need to cast the label as NSString or if I need to modify my assert statement directly.
#interface MoreTest : SenTestCase {
MagiczzTestingViewController* controller;
}
- (void) testObj;
#end
#implementation MoreTest
- (void) setUp
{
controller = [[MagiczzTestingViewController alloc] init];
}
- (void) tearDown
{
[controller release];
}
- (void) testObj
{
controller.doMagic;
STAssertEquals(#"hehe", controller.label.text, #"should be hehe, was %d instead", valtxt);
}
#end
The implementation of my doMagic method is below
#interface MagiczzTestingViewController : UIViewController {
IBOutlet UILabel *label;
}
#property (nonatomic, retain) UILabel *label;
- (void) doMagic;
#end
#implementation MagiczzTestingViewController
#synthesize label;
- (void) doMagic
{
label.text = #"hehe";
}
- (void)dealloc {
[label release];
[super dealloc];
}
#end
The build is fine when I modify the assert to compare a raw NSString to another but when I try to capture the text value (assuming it's of type NSString) it fails. Any help would be much appreciated!
STAssertEquals() checks for identity of the two values provided, so it's equivalent to doing this:
STAssertTrue(#"hehe" == controller.label.text, ...);
Instead, you want STAssertEqualObjects(), which will actually run an isEqual: check like the following:
STAssertTrue([#"hehe" isEqual:controller.label.text], ...);
You need to load the nib of the view controller. Otherwise there won't be any objects for the label outlet to be hooked up to.
One way to do this is to add an ivar for the view controller's view to your test case:
#interface MoreTest : SenTestCase {
MagiczzTestingViewController *controller;
UIView *view;
}
#end
#implementation MoreTest
- (void)setUp
{
[super setUp];
controller = [[MagiczzTestingViewController alloc] init];
view = controller.view; // owned by controller
}
- (void)tearDown
{
view = nil; // owned by controller
[controller release];
[super tearDown];
}
- (void)testViewExists
{
STAssertNotNil(view,
#"The view controller should have an associated view.");
}
- (void)testObj
{
[controller doMagic];
STAssertEqualObjects(#"hehe", controller.label.text,
#"The label should contain the appropriate text after magic.");
}
#end
Note that you also need to invoke super's -setUp and -tearDown methods appropriately from within yours.
Finally, do not use dot syntax for method invocation, it is not a generic replacement for bracket syntax in message expressions. Use dot syntax only for getting and setting object state.