void star games

* play * code * learn *


Objective C Tips

puce How to create alternating menu items

How to create alternating menu items in Cocoa such that when one is activated the other one is turned off.

Go to the interface builder first. For each of the menu items, in the Attributes tab of the properties setting window, make the state to be On for the default option. I haven't experimented with the Alternate box yet, that might also work. Make sure both of your menu items are enabled.

Go back to Xcode and to the controller class for your application. Add a boolean attribute/field in the class that will represent which alternative was chosen (or integer or enum for more than two alternatives). In the init function set its value to be the one corresponding to the menu item that is On by default.

For each menu item, add a function in the controller class returning an (IBAction) and taking as parameter an (id) sender, like
- (IBAction) setFirst: (id) sender;

In the implementation of this function, set the state of this menu as On, represented by the number 1:
[sender setState:1];

Then you need to set the state of the other menu item as Off. You can locate this menu item by going to its parent menu first ([sender menu]), which is of a class derived from NSArray. The different menus items are children of this menu at indexes starting from 0 for the first one. So for example, if the menu to be turned off is the second one from the top (not counting the title of the menu itself), you'd do
[[[sender menu] itemAtIndex:1] setState: 0];

Do not forget to set the value of your boolean/int/enum attribute appropriately and perform any other action associated with this option.

Go back to the interface builder and create a connexion from each menu item to the controller object in MainMenu.xib (hold the control down while dragging the mouse from the menu item to the controller object) and associate the action with the name of the function you just wrote for it.

puce How to solve circular class references

Suppose that class A needs a reference to class B, and class B needs a reference to class A. One of them can be included or imported in the other, for example, A.h could contain
import "B.h"

Objective C has a mechanism to avoid circular references, so if you try to import A.h inside B.h, you'll get an error. So in the header for B you need to declare A as a forward class:
@class A;

This solves almost all the problems, except that calling methods from class A that have parameters will result in a warning. To get rid of it - and you should because most warnings are errors that don't prevent the program execution - you can include or import the header A.h not in the header file for B as you normally would, but in the implementation file B.m:
#include "A.h"

puce How to find interface widgets in the code and change their attributes

In the Interface Builder, in the Attributes tab, set the tag of all the widgets that you need to control from the code to a unique number. You need at least one of them in the window that is connected as an outlet to the controller object of your application. Suppose the known IBOutlet objects is knownButton.

Find the parent of the button with
NSView *frame = [knownButton superview];

This is normally of a class inheriting from NSArray. Find the number of children it has with
count = [[frame subviews] count];

Then you need a loop with say i going from 0 to count-1 where you check the tag being equal to the unique value widgetTag that you've set for the widget you're trying to identify:
if ([[[part subviews] objectAtIndex: i] tag] == widgetTag)
  newWidget = [[part subviews] objectAtIndex: i];

Then depending on the widget type, you can change some of the information it displays. For example, the label on an action button can be changed with
[newWidget setTitle: @"New Title"];

puce How to make an application quit when you close the window

First, you need to create a delegate connexion from the object called File Owner in the .xib file and the object standing in for the controller class in your app with Ctrl-drag from the first to the second and then choosing delegate.

Then you need to add the following function to your controller class:
- (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *) theApplication {
  return true;
}