NSPopUpButton

Discussion in 'Mac Programming' started by larswik, Jun 26, 2011.

  1. macrumors 68000

    Joined:
    Sep 8, 2006
    #1
    First time trying to use this popUpButton. I read through some documentation but I can't seem to figure out the code I need to add to give it a value. I use this code to populate the pull-down with a list of things. I have a variable set up so if they select d100 it should assign the integer value of 100 to the variable? I know it is in front of my eyes in the documentation but I can't find it.


    Code:
    -(void)awakeFromNib
    {
        [sidedDiceSelection addItemWithTitle:@"d100"];
        [sidedDiceSelection addItemWithTitle:@"d20"];
        [sidedDiceSelection addItemWithTitle:@"d12"];
        [sidedDiceSelection addItemWithTitle:@"d10"];
        [sidedDiceSelection addItemWithTitle:@"d8"];
        [sidedDiceSelection addItemWithTitle:@"d6"];
        [sidedDiceSelection addItemWithTitle:@"d4"];
        [sidedDiceSelection addItemWithTitle:@"d2 "];
    }
    I was thinking I could do something like this
    Code:
    [sidedDiceSelection addItemWithTitle:@"d100" setObjectValue:100];
    
    But I im getting an error saying "Return type defaults to id". I am setting the "d100" and it works fine. Is it because I am trying to assign an integer to an object?
     
  2. macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #2
    There is no addItemWithTitle:setObjectValue: in NSPopUpButton, that why you're getting the compiler warning.

    What you might want to do is use the representedObject property of NSMenuItem. Basically an NSPopUpButton is button that menu triggered will show an NSMenu. An NSMenu is made up of NSMenuItem objects.

    So you could create the NSPopUp menu with something like this. (Entered directly, untested).
    Code:
    -(void)awakeFromNib
    {
        NSArray *sides =
          [NSArray arrayWithObjects:
              [NSNumber numberWithInt:100],
              [NSNumber numberWithInt:20],
              [NSNumber numberWithInt:10],
              [NSNumber numberWithInt:8],
              [NSNumber numberWithInt:6],
              [NSNumber numberWithInt:4],
              [NSNumber numberWithInt:2],
              nil
          ];
    
        for (NSNumber *side in sides) {
           // Append an item to the popup's menu
           NSString *title = [NSString stringWithFormat:@"d%@", side];
           [sidedDiceSelection addItemWithTitle:title];
           // Get the newly appended menu item
           NSMenuItem *menuItem = [sidedDiceSelection lastItem];
           // Set the representedObject property so the menu item
           // carries the number of sides
           [menuItem setRepresentedObject:side];
        }
    }
    
    Then in an action, you get the number of sides for the selection with something like:
    Code:
    NSMenuItem *selectedMenuItem = [sidedDiceSelection selectedItem];
    NSNumber *sides = [selectedMenuItem representedObject];
    
     
  3. macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #3
    I'm back on my Mac now (ahhh). I've found a complication.

    I thought the NSPopUpButton's action would trigger when a menu item was selected. Unfortunately it doesn't. It seems we are required to setup a connect between each menu item and the controller.

    Assuming the IBOutlet is called selectDice:, then setup code becomes.
    Code:
    - (void)awakeFromNib
    {
        NSArray *sides =
        [NSArray arrayWithObjects:
         [NSNumber numberWithInt:100],
         [NSNumber numberWithInt:20],
         [NSNumber numberWithInt:10],
         [NSNumber numberWithInt:8],
         [NSNumber numberWithInt:6],
         [NSNumber numberWithInt:4],
         [NSNumber numberWithInt:2],
         nil
         ];
        
        for (NSNumber *side in sides) {
            // Append an item to the popup's menu
            NSString *title = [NSString stringWithFormat:@"d%@", side];
            [sidedDiceSelection addItemWithTitle:title];
            // Get the newly appended menu item
            NSMenuItem *menuItem = [sidedDiceSelection lastItem];
            // Set the representedObject property so the menu item
            // carries the number of sides
            [menuItem setRepresentedObject:side];
            [COLOR=blue]// Connect menu item
            [menuItem setAction:@selector(selectDice:)];
            [menuItem setTarget:self];[/COLOR]
        }
    }
    
    BTW This the programming equivalent of drawing a line between an object and outlet in IB.
     
  4. thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #4
    It seems overly complicated to set these up with values for a NSPopUpButton :)

    The array that you set up, is that just an array of integers that seems separate from the pull down?

    It would seem smart to me (Still learning of course) that an NSPopUpButton would utilize a mutable dictionary scheme. Object being the value stored and key being the the printed type on the pull down that you select. I learned about dictionary's the other day :)

    -Lars
     
  5. macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #5
    The array just drives the loop. If you're not getting that, here's another way.

    Code:
    - (void)addSidedDiceSelection:(int)numberOfSides
    {
      NSNumber *side = [NSNumber numberWithInt:numberOfSides];
      // Append an item to the popup's menu
      NSString *title = [NSString stringWithFormat:@"d%@", side];
      [sidedDiceSelection addItemWithTitle:title];
      // Get the newly appended menu item
      NSMenuItem *menuItem = [sidedDiceSelection lastItem];
      // Set the representedObject property so the menu item
      // carries the number of sides
      [menuItem setRepresentedObject:side];
      // Connect menu item
      [menuItem setAction:@selector(selectDice:)];
      [menuItem setTarget:self];
    }
    
    - (void)awakeFromNib
    {
      [self addSidedDiceSelection:100];
      [self addSidedDiceSelection:20];
      [self addSidedDiceSelection:10];
      [self addSidedDiceSelection:8];
      [self addSidedDiceSelection:6];
      [self addSidedDiceSelection:4];
      [self addSidedDiceSelection:2];
    }
    
     
  6. thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #6
    The last version you showed me was for some reason easier to understand. I can see what is happening now. Thanks again for your help.
     
  7. thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #7
    While I am waiting for my oil change and on my iPad I had an Ahh-hah moment. The developer library is kind of confusing if you don't know what you are doing or looking for. Also looking for a terminology check up.

    Jim wrote this line : NSNumber *side = [NSNumber numberWithInt:numberOfSides];

    'NSNumber' is a class. '*side' is the pointer variable pointing to the new (object or instance object?) set up in memory. ’=' my assignment operator. Now the next one 'NSNumber' shows up again, is this just written again to show the path to the (class methods) that a class has? Now the next line 'numberWithInt' is a class method which is listed in the developer help under that class. ':' this is an (assignment operator?) that allows the class method to take the argument which is 'numberOfSides'.

    Now, these 2 methods 'numberWithInt' and 'initWithInt'. These 2 methods seem to do the same thing but 1 is a class method and the other is an
    instance method.

    If my code is to build an object with that value
    Code:
    NSNumber *side = [NSNumber numberWithInt:numberOfSides];
    
    And later I want to change that value I would do something like this
    Code:
    [side intValue: 5];
    
    Would this be accessing the same value that was set by 'numberWithInt'.

    Thanks.
     
  8. macrumors 603

    Joined:
    Aug 9, 2009
    #8
    It's written again to name the exact class that numberWithInt will be sent to. If you don't name the exact class, you can't send a message to it. If you name a different class, then that class will be sent the message.

    The : is part of the method name. It definitely IS NOT an assignment operator. It's not an operator at all. It marks the point in the method where a parameter must be placed.


    They do similar things. They are not the same.

    You already noted one is a class method, the other an instance method. Given that difference, they can't be the same. Also, one returns an object you own, the other returns an object you don't own. Go back and read the Memory Management Guide if necessary.


    First, an instance of NSNumber is immutable. You can't change its value. Having an immutable class is a recurring design pattern in Cocoa: NSArray, NSDictionary, NSString, and others are all immutable. There is often a mutable version of the base class (e.g. NSMutableArray, NSMutableString, etc.). Not so for NSNumber.

    Second, there is no method named intValue: (note I've included the colon in the name. There is a method named intValue (no colon), but it takes no parameters. You can't just throw in colons wherever you want. There must be an actual method with identical name and colons. That's one of the things wrong with the first post in this thread. You just randomly stuck together method-name parts and colons:
    Code:
    addItemWithTitle:@"d100" setObjectValue:100
    
    There is no method whose name is addItemWithTitle:setObjectValue: so it fails.

    Go to the NSNumber class reference doc:
    http://developer.apple.com/library/...asses/NSNumber_Class/Reference/Reference.html

    Look in the lefthand column. At the bottom there is a Companion Guides heading. The first item is "Number and Value Programming Topics". Read that.

    You should also read the class reference doc for NSValue, which is the superclass of NSNumber. There's a link for it on the NSNumber class reference doc page, at the top, labeled "Inherits from".
     
  9. thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #9
    Thanks Chown33,

    I can see that. So 'NSNumber *side' is the first step and it's just saying " I want the pointer variable 'side' to point to an object created from the class NSNumber. The next part '= [NSNumber numberWithInt:numberOfSides];' is saying, "and by the way, when you create the object I want it initialized with these settings as default".

    haha, yep, that's a noob approach keep tweaking it till it works :)

    So what I am pretty much taking away from your explanation is that once an object is created, it's created with values already initialized to be used but not changed because they default to immutable.

    I noticed that I never need to alloc or init classes that are not mine, this must be class methods built into the classes. So I would never write code like this
    Code:
    NSNumber *myNumber = [NSNumber alloc]init];
    Because once I did I created myNumber which is immutable. That is why I always initialize immutable objects, with values, when they are created, yes?

    Thanks again Chown33. I will read the memory management again too

    -Lars
     
  10. macrumors 603

    Joined:
    Aug 9, 2009
    #10
    Mostly right.

    It's saying "I declare a variable named 'side'. Its type is 'pointer to NSNumber'." That may seem the same as what you wrote, but it's subtly different.

    Notice that I didn't say "created from the class NSNumber". That's because there are subclasses of NSNumber, and it is perfectly valid (and sometimes intentional) to declare the variable's type as the superclass, but then create the instance using a subclass. Here's a contrived example:
    Code:
    NSNumber *side = [NSDecimalNumber decimalNumberWithString:@"2.71"];
    
    I say "contrived" because I have no real reason for doing this here, it's just to show what's possible.

    It's not really "and by the way". It's a very specific and compulsory request. It's not optional in any way.

    Neither classes nor objects "default to immutable". That's only true here because this particular class represents immutable objects. It's never because this is accidental or optional. It's always because the class itself is intentionally designed to represent immutable objects.

    It's not built into classes. The classes are designed and implemented that way. You can write class methods in your classes, if you want them and intend to use them.

    Well first, I have to fix the missing [ :
    Code:
    NSNumber *myNumber = [[NSNumber alloc]init];
    Now, from a designer's point of view, can you think of a reason to have an NSNumber that contains no number?

    Some classes it makes sense that you might want an empty representation, such as an empty NSArray or NSDictionary. Others it might be debatable. But does it ever make sense to have an "empty number" object? I'm not even sure what such a thing would represent. It's not zero, because 0 is clearly a number.

    Second, from a designer's point of view, if you can't think of a reason to have an "empty" instance of a class, then you would not have a plain -init method. As a designer and implementor, this is an intentional design decision, because it's what makes sense for the way the class and it's instances are intended to be used.

    If you want to make a design argument that an "empty" NSNumber is the NaN value, then maybe you allow a plain -init. But NSNumber's designers clearly didn't do that, so there isn't a plain -init method.

    Third, there isn't a plain -init method in NSNumber, so you can't use this code [[NSNumber alloc]init] even if you wanted to.
     
  11. thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #11
    Cool, it seems that when you answer my questions I am let with more questions from your responses :)

    I can see why an empty object is useless for the most part. I think for the most part I understand what I am doing, I am just getting my syntax messed describing it to you, but it does make a difference.

    So when I use my iPhone contacts list and what to add a new contact I hit the '+'. I then enter the name and number and hit done. I hope I explain this right: hitting the '+' does not initialize a new object. It prompts me to fill in the information (for a class method) and then when I hit 'done' an object is created with the contact information to access. This object would have to be mutable because if he gets a new, or additional number I need to modify that object, right?

    Before you started mentioning this I thought that hitting the '+' created (there is that word again, created) a new object. Then the information was added to it after it was created, but that was wrong way of thinking of it.

    Thanks again for taking the time Chown33!!!!

    -Lars

    -Lars
     
  12. macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #12
    There always is an init method, because NSObject provides one and you can't remove a method. What NSNumber does in the face of a plain init I can't say because I can't test it right now.

    In my own classes, when I don't want parameter-less initialization, I override init to log a message and return nil.
     
  13. macrumors 603

    Joined:
    Aug 9, 2009
    #13
    You're right; I temporarily forgot that.

    Code:
    #import <Foundation/Foundation.h>
    
    int main(int arcgc, char *argv[])
    {
    	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    	NSNumber *foo = [[NSNumber alloc] init];
    
    	NSLog( @"foo: %@", foo );
    	
    	[pool drain];
    	return 0;
    }
    
    Output:
    Code:
    2011-06-27 16:38:50.320 a.out[9624] foo: (null)
    
    Personally, I wouldn't do it this way. Using the plain -init is clearly a serious programmer error. The Cocoa convention is that serious programmer errors throw exceptions. There may be some mitigating reason, but I don't have an "NSNumber Design Rationale" document, so I don't know.
     
  14. macrumors 6502

    Joined:
    Feb 7, 2008
    #14
    One can only guess, but here is a possible rationale.

    The -init method of any class should never throw an exception. If any real value were assigned, say 0, to the NSNumber, then the program would continue with unexpected results.

    By leaving the value null, an exception might be thrown as soon as you tried to use it where a real number was required.

    Only a guess...
     
  15. macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #15
    I too have an aversion to throwing an exception during initialization. But I can't come up with a clear justification for that aversion in Objective-C. Perhaps it's a C++ thing I've carried over to Objective-C.
     
  16. chown33, Jun 27, 2011
    Last edited: Jun 27, 2011

    macrumors 603

    Joined:
    Aug 9, 2009
    #16
    It won't. In Objective-C, nil is a valid message receiver. So sending messages like intValue will return 0.

    A recent thread on cocoa-dev list: -(id)init methods, NSExceptions, and returning nil.

    I have seen init methods that contain NSAssert()'s, which will throw an exception if the assertion fails.
     
  17. macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #17
    So what I got was: throw exception for programmer error, return nil for runtime error.

    If you don't want to support plain init in your class, through an exception.

    If you do want to support plain init, but it fails to actually execute successfully, return nil, after releasing self.
     

Share This Page