Programmatically defining NSSegmentedCell - objective-c

I am a little stumped as to how to instruct a programmatically created NSSegmentedControl to use a subclass instance of an NSSegmentedCell.
If I want to use a subclasses NSSegmentedCell on an NSSegmentedControl built using IB it would be as simple as doing the following:
Drag an NSSegmentedControl into the NSView
Click through to the NSSegmentedCell
In the inspector assign the class definition to the subclass (e.g. myCustomCell)
Job done.
However, when programmatically creating an NSSegmentedControl as in the following simplified example, I don't see how to subclass the cell...
-(void)creatSegmentControl {
if (!mySegmentControl)
mySegmentControl = [[NSSegmentedControl alloc]
initWithFrame:NSMakeRect(0,0 400,20)];
[mySegmentControl setSegmentCount:2];
[mySegmentControl setLabel:#"First" forSegment:0];
[mySegmentControl setLabel:#"Second" forSegment:0];
[mySegmentControl setTarget:self];
[mySegmentControl setAction:#selector(segmentClicked:)];
}
NSSegmentedControl does not appear to have a method for defining the class to use for it's segment cell instances.
As usual, any and all help appreciated.
Update
Tried implementing [mySegmentControl setCellClass:[myCustomCell class] but that didn't work either. I was thinking that maybe it inherited the ability to set it's cell class like other AppKit controls. :-(
This must be possible though... somehow...

Kinda late, but wouldn't overwrite cellClass work?
+ (Class)cellClass
{
return [YourCustomCellClass class];
}

The property cellClass is in the deprecated category.
You need to make an instance of your custom class and set NSControl's cell property, before anything else (yes NSSegmentedControl inherits from NSControl)
NSSegmentedControl* oSegment = [[NSSegmentedControl alloc] init];
QPDFSegmentedCell* csell = [[QPDFSegmentedCell alloc] init];
oSegment.cell = csell;

Related

NSBrowser selection of row and action of NSBrowserCell

I am having an NSBrowser in one of my windows. It has a checkbox, image and textbox as you see in the screenshot.
I am struggling to do two things:
Change the row selection color. By default it is blue.
Action on the checkbox
The checkbox + image + textbox is added to the subclass of NSBrowserCell like this:
- (instancetype)init
{
self = [super init];
if (self) {
_buttonCell = [[NSButtonCell alloc] init];
[_buttonCell setButtonType:NSButtonTypeSwitch];
[_buttonCell setTarget:self];
[_buttonCell setAction:#selector(cellButtonClick:)];
[_buttonCell setTitle:#""];
_imageCell = [[NSImageCell alloc] init];
_textCell = [[NSTextFieldCell alloc] init];
}
return self;
}
I've added target and Action too, but it is not called.
How do I achieve these two things?
Any guidance will be appreciated.
Caveat: customizing NSBrowser/NSBrowserCell is usually hard. It's a legacy view class that's been essentially abandoned for years, and it has a lot of undocumented quirks.
Having said that, try overriding -highlightColorWithFrame:inView: to use a different color for highlighting the row. It that's all you need, then I think that should do it.
As for the action, NSCell (unlike views and controls) is not a subclass of NSResponder and doesn't perform any automatic mouse event processing. (Cells are just helper objects to draw the view of a control/element.)
You'll probably have to catch the event at the browser view level, then perform hit testing to find the column/row that contains your check box cell. If you find a hit, then post the action message yourself (-sendAction:to:from:)—which is literally what a NSControl view does.

Creating an instance of an instance of another class

I'm trying to break some of my "super objects" into more manageable classes that have single (or at least limited) responsibility.
One problem I've just run into is making an object of a specific instance of a UIBarButtonItem. In the class it is in now I first define a UIButton, and then all of the images that act as icons for that button as subviews (for instance the button represents access/control to a device, and I use the button image to show the current signal strength of that device). Also that button is listening for NSNotifications from the device object to represent the signal strength changing, or if the device disconnects. And pressing the button sends a message to the device to disconnect. All of this code works perfectly fine now as a property of the RootViewController. However, I want to pull it out into its own class as the button is shared by several classes, and it just clutters up the controller with unnecessary methods.
I tried making an separate class with an init like below. However, this doesn't work as the self used for the button isn't the same self that is ultimately created by [UIBarButtonItem alloc] and when either the NSNotification or the button press try to send a message to the selector of "self", that object has already been dealloced. The problem is, I'm not sure how to create a object (as defined by the class) that is just an instance of another class, as opposed to a property of an object (as it currently is for the RootViewController).
Edit and additional explanation of my problem
MyClass is currently subclass of UIBarButtonItem. However, I'm not trying to use it like this: [[MyClass alloc] initWithCustomView:]. I want [MyClass alloc] init] by itself to completely create the custom view - in other words the whole point of this class is to completely contain all that is necessary for this button to create itself, manage its subviews, and take the appropriate action when it is pressed. (I could easily make MyClass an NSObject with a public method like [MyClass setupButton] and a public property of type UIBarButtonItem. However, I think that looks wrong because then the class is only there to create the button, but it is not the button itself.)
#interface MyClass : UIBarButtonItem
#end
#implementation MyClass
- (id)init {
if (self = [super init]) {
UIImage *defaultButton = [[UIImage imageNamed:#"...
UIImage *defaultButtonPressed = [[UIImage imageNamed:#"....
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 40, 30)];
[button setBackgroundImage:defaultButton forState:UIControlStateNormal];
[button setBackgroundImage:defaultButtonPressed forState:UIControlStateHighlighted];
[button addTarget:self action:#selector(deviceButtonPressed) forControlEvents:UIControlEventTouchUpInside];
//Then several UIImageViews that are added as subviews of the button, initially hidden
//Then set up the NSNotification listener
//Finally
self = [[UIBarButtonItem alloc] initWithCustomView:button];
}
return self;
}
//Then several functions to handle hiding and unhiding the subviews depending on the received device notifications, and a function to handle the button press and sending the message back to the device.
This is not how initialization works in Cocoa. Please read "Initialization" in the Cocoa Core Competencies guide.
Your object has already been allocated when this init method is run. You should not be reassigning the self pointer to another allocation.
Your class should first call its superclass's designated initializer self = [super initWithWhatever:obj];, then set up its own properties.
It seems to me that you want to extend UIBarButtonItem, not create an instance of it in your init method. Try changing your class declaration (in your class's .h file) from this:
#interface MyClass : NSObject
to this:
#interface MyClass : UIBarButtonItem
Then just return self in your init method. Setting self to a value is usually a bad idea.
If you're unsure about what's going on here, you're creating a subclass of UIBarButtonItem. This lets your subclass extend the superclass's functionality. If you're confused, you should take a look at subclassing/class inheritance in object-oriented languages to understand what's going on. This guide documents how classes work in Objective-C.

Programmatically create NSButton and set custom NSButtonCell

Ok, this seems like it should very simple, but I'm coming from iOS and and I must be missing something very obvious. So I have a custom subclass of NSButtonCell. If I create a NSButton in IB and assign the NSButtonCell in IB, it works no problem.
However, I need to programmatically create this button. I've looked at a couple of examples and have the following within awakeFromNib, however, it creates what appears to be a regular button with my custom button cell underneath (I can tell when I click on the button).
NSButton* button = [[NSButton alloc] initWithFrame:NSMakeRect(100, 100, 60, 30)];
CT_CaptureButtonCell* captureCell = [[CT_CaptureButtonCell alloc] init];
[button setCell:captureCell];
[captureCell release];
[self.view addSubview:button];
So what dumb thing am I missing / not understanding? Thanks.
If you look at the documentation for "setCell:", it states:
Discussion
Use this method with great care as it can irrevocably
damage the affected control; specifically, you should only use this
method in initializers for subclasses of NSControl.
The way I read this is that you should be calling your "setCell" from within the init method for a subclass of "NSButton". In other words, write your own NSButton subclass (e.g. "ICButton", for InfalibleCoinage) and then stuff the "setCell" call into the init method there.
And while we're on the subject, what's the big picture of what you are exactly trying to accomplish with "CT_CaptureButtonCell" and can you do it without using a custom button cell?

When is it safe to manipulate screen controls through code in iOS/Objective-C

I have a question regarding iOS (or perhaps more accurately Objective-C) and properties. I have a UIView with a UISegmentedControl, by default it has 3 segments. I have a message which accepts a parameter and based on this parameter I may want to remove one of the segments. In UIView A I do this:
MyViewController *myview = [[[MyViewController alloc] initWithNibName:#"MyViewController" nib:nil] autorelease];
[[self navigationController] pushViewController:myview animate:YES];
[myview showItem:item];
In UIView B this happens in showItem:
-(void) showItem:(Item*)item{
if (item.removeSegment){
[segmentControl removeSegmentAtIndex:0 animate:NO];
}
}
I have noticed that the segment only gets removed when I call showItem after I have pushed it on the navigation controller. When I swap those two line, so I first call showItem and then push the view, the UISegmentedControl still has three segments instead of two.
This just feels wrong, it seems like bad practice that my code will break if someone doesn't call two messages in the right order. Is there a better way to do this? I've been looking at some sort of a property lifecyle that I can use, I am very familiar with this from ActionScript 3, but I have been unable to find anything on the subject.
(as an aside: in AS3 I would make a property, in the setter I don't manipulate any screen controls but call InvalideProperties. My overriden methode CommitProperties will be called once the entire object and child controls have been created. In CommitProperties I check if my property value has changed and this is where I would remove the segment.)
A common way of doing something like this is to create an Item *item property in MyViewController and set that when myview is created. So, your code becomes:
MyViewController *myview = [[[MyViewController alloc] initWithNibName:#"MyViewController" nib:nil] autorelease];
myview.item = item;
[[self navigationController] pushViewController:myview animate:YES];
MyViewController would then use that property in its viewWillAppear: method to configure its own segment control.
I think what you are falling prey to is myview->segmentControl doesn't exist until myview.view is referenced because of the lazy load of the view.
-(void) showItem:(Item*)item{
[self view]; // NO OP TO FORCE LOAD!!
if (item.removeSegment){
[segmentControl removeSegmentAtIndex:0 animate:NO];
}
}
Should work for you. Hope this helps!

Using the same NIB with multiple View Controllers

Basically I want to use a nib file and view controller as a template for a view that I plan to create a number of times. This nib will have a couple of labels and custom views.
The idea is that I will iterate through an array of objects and for each I will create an instance of this controller and set a property to that of the object from the array.
This all works nicely at the moment except for one thing - the labels won't update when I call setStringValue: !!!
I'm using a method within the view controller's code to make the change but it just doesn't work, I'm guessing that the IBOutlet isn't being hooked up properly, which is strange because the custom views are hooking up perfectly.
Any ideas?
Set a breakpoint on awakeFromNib and look in the debugger what the value of the label outlet is. All outlets should have been connected before awakeFromNib is being called. If it is still nil, you have your answer. Calling setStringValue: on nil does exactly "nothing". In that case you have not correctly bound the outlet or maybe you once had it bound correctly and later on changed the name, in that case there should be a yellow warning triangle in Xcode4 or interface builder indicating that something is wrong; however it will not prevent your app from building or running, the outlet will simply keep its initial value after object creation (which is nil).
When you alloc your NSViewControllers, just init with name of NIB:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
Thanks for the replies, they were helpful but not quite what I was getting at.
I ended up solving it by creating an empty NIB and filling it with just a custom NSView and a few other controls. I created an NSView subclass with IBOutlets for those controls and set the custom view's identity to my subclass in interface builder.
The trick in getting it to work each time I wanted to draw it was by making a class method in my subclass that would load the nib and return the view set up the way I wanted.
Code below:
+(id)todoViewFromNibWithFrame:(NSRect)frameRect todoList:(TodoList *)aTodoList
{
NSNib *todoViewNib = [[NSNib alloc] initWithNibNamed:#"TodoView" bundle:nil];
NSArray *objects = nil;
id todoView = nil;
[todoViewNib instantiateNibWithOwner:nil topLevelObjects:&objects];
for (id object in objects) {
if ([object isKindOfClass:[self class]]) {
todoView = object;
[todoView setTodoList:aTodoList];
break;
}
}
[todoViewNib release];
return todoView;
}
Thanks again for the replies!
Steve