Setting radio button state without using Matrix

Ok, it used to be super easy with matrix but now that Matrix is being depreciated I'm not sure how to set the state of a radio button...


I've setup the radio button these ways:


IBOutlet NSButtonCell *pending;
IBOutlet NSButton *pending;
__weak IBOutlet NSButton *pending;


But the code below doesn't do anything:


if([p.status isEqualToString:@"Pending"]){
pending.state = 1; //Doesn't work
[NSButtonCell:pending [setstate:NSOnState]]; //Doesn't work
[(NSButtonCell *)pending.cell setstate(ON)]; //Doesn't work
}


There has to be a way to do it and I've searched high and low... Any help would be seriously appreciated.


Thanks in advance.

Accepted Reply

I think I know what your problem is. Did you connect those radio button outlets right to the row in the table view? That won't work because outlets are to-one relationships and cannot be used to point to multiple rows at the same time (And, most likely, the one reference you have is likely for some "prototype" row that isn't actually in the table.)


So to fix this, I'd recommend getting rid of those outlets. Open up IB and use the Identity inspector (⌥⌘3) to set custom identifiers for the three radio buttons. Then you could write code like this inside your if (p) { block (starting at line 9) to process each row's set of buttons:

for (NSView *subview in cell.subviews) {
    if (![subview isKindOfClass:[NSButton class]]) continue;

    if ([subview.identifier isEqualToString:@"PendingButton"])
          [(NSButton *)subview setState:[p.status isEqualToString: @"Pending"] ? NSOnState : NSOffState];
    else if ([subview.identifier isEqualToString:@"ApprovedButton"])
          [(NSButton *)subview setState:[p.status isEqualToString: @"Approved"] ? NSOnState : NSOffState];
      else if ([subview.identifier isEqualToString:@"RejectedButton"])
          [(NSButton *)subview setState:[p.status isEqualToString: @"Rejected"] ? NSOnState : NSOffState];
}

Notice that all of the buttons are covered and that any that shouldn't be selected are explicitly disabled. This covers any artifacts left over from if table rows are resused.


You might even want to use the exact values of the Proposal's status property. Then you could write it in an even simpler way:

for (NSView *subview in cell.subviews) {
    if ([subview isKindOfClass:[NSButton class]])
          [(NSButton *)subview setState:[p.status isEqualToString: subview.identifier] ? NSOnState : NSOffState];
}

Just be careful to make sure they match exactly or it won't work.


Edit: Fixed the fast-enumeration declarations in line 1 (I had accidentally used the Swift form, not the Obj-C form)

Replies

First of all, you can't have three variables named

pending
. Keep #3 and delete the rest. (And make sure you connect the outlet in IB!)


And now for the code: line 2 could work, but it would help if you use the NSOnState constant to make the intent of the code more clear. Lines 3 and 4 are completely wrong from a syntactic perspective. The correct form would be like this:

[pending setState:NSOnState];
[(NSButtonCell *)pending.cell setState:NSOnState];


Of course, you should only have to use one of these three forms of setting the state in order for it to have an effect. Personally, I recommend just using the dot-syntax approach (line 2) because it's way simpler and more compact. And make sure you double-check that IBOutlet.

The "new" way to link radio buttons is to give them all the same action method (and make them all children of the same superview). In that case, turning one on should turn off the others.

Hi, Thanks for taking a look-


I know about the variables and IB bindings, I meant to write that I had tried all three ways...😊


I have successuflly implemented the code in a test project (yay!) but in my actual app I'm running into a wall. I think it has to do with where the radio buttons are and when the code is called.


I have an NSTableView and in one of the columns (the "status" column) I have the three radio buttons that I put there in IB. Their state should be determined by the value of the variable "p.status" which will either be "Pending" "Approved" or "Rejected". I'm thinking that the code is called before the radio buttons are drawn and that's why it's not working...? What would be the best way to fix this?



- (NSView *)tableView:(NSTableView *)table_view viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
    Proposal *p = [list objectAtIndex:row];
    NSString *identifier = [tableColumn identifier];
    NSString *holdingValue;
   
    NSTableCellView *cell = [table_view makeViewWithIdentifier:identifier owner:self];
    if([identifier isEqualToString:@"status"]){
        if(p){
            if([p.status isEqualToString:@"Pending"]){
                [check setState:NSOnState]; //This code successfully executes- the "check" button is static and outside the NSTableView
                [(NSButtonCell *)pending.cell setState:NSOnState];  //This doesn't change the status of the radio button in the cell 
                NSLog(@"State: %@",p.status);
           
            }else if([p.status isEqualToString:@"Approved"]){
                [(NSButtonCell *)approved.cell setState:NSOnState];
                NSLog(@"State: %@",p.status);
           
            }else if([p.status isEqualToString:@"Rejected"]){
                [(NSButtonCell *)rejected.cell setState:NSOnState];
                NSLog(@"State: %@",p.status);
            }
        }
    }else if ([identifier isEqualToString:@"clientAccessPoint"]){
        holdingValue = [p valueForKey:identifier];
            if (!holdingValue){
                cell.textField.stringValue = @"N/A";
            }else{
                cell.textField.stringValue = [p valueForKey:identifier];
            }
    }else{
    cell.textField.stringValue = [p valueForKey:identifier];
    }
    return cell;
}

I think I know what your problem is. Did you connect those radio button outlets right to the row in the table view? That won't work because outlets are to-one relationships and cannot be used to point to multiple rows at the same time (And, most likely, the one reference you have is likely for some "prototype" row that isn't actually in the table.)


So to fix this, I'd recommend getting rid of those outlets. Open up IB and use the Identity inspector (⌥⌘3) to set custom identifiers for the three radio buttons. Then you could write code like this inside your if (p) { block (starting at line 9) to process each row's set of buttons:

for (NSView *subview in cell.subviews) {
    if (![subview isKindOfClass:[NSButton class]]) continue;

    if ([subview.identifier isEqualToString:@"PendingButton"])
          [(NSButton *)subview setState:[p.status isEqualToString: @"Pending"] ? NSOnState : NSOffState];
    else if ([subview.identifier isEqualToString:@"ApprovedButton"])
          [(NSButton *)subview setState:[p.status isEqualToString: @"Approved"] ? NSOnState : NSOffState];
      else if ([subview.identifier isEqualToString:@"RejectedButton"])
          [(NSButton *)subview setState:[p.status isEqualToString: @"Rejected"] ? NSOnState : NSOffState];
}

Notice that all of the buttons are covered and that any that shouldn't be selected are explicitly disabled. This covers any artifacts left over from if table rows are resused.


You might even want to use the exact values of the Proposal's status property. Then you could write it in an even simpler way:

for (NSView *subview in cell.subviews) {
    if ([subview isKindOfClass:[NSButton class]])
          [(NSButton *)subview setState:[p.status isEqualToString: subview.identifier] ? NSOnState : NSOffState];
}

Just be careful to make sure they match exactly or it won't work.


Edit: Fixed the fast-enumeration declarations in line 1 (I had accidentally used the Swift form, not the Obj-C form)

I don't think that works when modifying the states programmatically, though.

YAHTZEE!


I had setup unique identifiers but totally forgot that approach- I was still thinking outside the NSTableView "box" (fresh eyes always wins)

Thank you a TON. I hope this helps people. It'd be nice if Apple gave some better documentation on new approaches when they depreciate code like "matrix"


I had to modify line 9 from:

for subview in cell.subviews {

To:

for (NSView *subview in cell.subviews) {


Here's the code that worked:


- (NSView *)tableView:(NSTableView *)table_view viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
    Proposal *p = [list objectAtIndex:row];
    NSString *identifier = [tableColumn identifier];
    NSString *holdingValue;

    NSTableCellView *cell = [table_view makeViewWithIdentifier:identifier owner:self];
    if([identifier isEqualToString:@"status"]){
        if(p){
            for (NSView *subview in cell.subviews) {
                if (![subview isKindOfClass:[NSButton class]]) continue;
            
                if ([subview.identifier isEqualToString:@"pendingButton"])
                    [(NSButton *)subview setState:[p.status isEqualToString: @"Pending"] ? NSOnState : NSOffState];
                else if ([subview.identifier isEqualToString:@"approvedButton"])
                    [(NSButton *)subview setState:[p.status isEqualToString: @"Approved"] ? NSOnState : NSOffState];
                else if ([subview.identifier isEqualToString:@"rejectedButton"])
                    [(NSButton *)subview setState:[p.status isEqualToString: @"Rejected"] ? NSOnState : NSOffState];
            }
        }
    }else if ([identifier isEqualToString:@"clientAccessPoint"]){
        holdingValue = [p valueForKey:identifier];
            if (!holdingValue){
                cell.textField.stringValue = @"N/A";
            }else{
                cell.textField.stringValue = [p valueForKey:identifier];
            }
    }else{
    cell.textField.stringValue = [p valueForKey:identifier];
    }
    return cell;
}

Glad I was able to help. And sorry about misleading you with that line there—I pretty much use Swift exclusively now, so I had to think hard about how to use Objective-C again correctly, and I totally forgot about that part. 😊


Oh, one more thing: are you familiar with the ternary condition operator that I used in the example? Since it's in your code now, I kind of want to make sure it's clear to you how it works. 🙂

//condition ? valueToUseIfTrue : valueToUseIfFalse
[p.status isEqualToString: @"Approved"] ? NSOnState : NSOffState