Bill Hernandez
Author
: Bill Hernandez (Plano, Texas)
Updated
: [2010.01.05](1:44 AM)
Basename
: Three_Button_Confirm_Dialogs

Learning a little Hot Cocoa..

Using Interface Builder to create a Confirm Dialog with Xcode and Objective C...


OBJECTIVE :


  1. Create a simple confirm dialog (sheet) attached to a window using Xcode's Objective C. The purpose of the confirm dialog is to ask the user for a response when they attempt to close a window either by using the the window's red close button . window_close_button.png at the upper left hand corner of a window, or by clicking a button that triggers a performClose: action.

    We will use the NSBeginAlertSheet Method, which is part of the Applications Kit, for this purpose.
    Abstract: Creates and runs an alert sheet.

    The next step then is easy enough, which is to convert the example into a reusable method.
  2. In the next tutorial we will use the NSAlert Class to do something similar.
    Abstract: You use an NSAlert object to display an alert, either as an application-modal dialog or as a sheet attached to a document window. The methods of the NSAlert class allow you to specify alert level, icon, button titles, and alert text. The class also lets your alerts display help buttons and provides ways for applications to offer help specific to an alert. To display an alert as a sheet, invoke the beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo: method; to display one as an application-modal dialog, use the runModal method.

START...

Image ( 1 )

As before, create a new Xcode project.

confirm_dialog_01.png

Image ( 2 )

Cocoa it is...

confirm_dialog_02.png

Image ( 3 )

Call it whatever you want...

confirm_dialog_03.png

Image ( 4 )

We will use the NSBeginAlertSheet, which has a simple enough description :

Creates and runs an alert sheet on docWindow, with the title of title, the text of msg, and buttons with titles of defaultButton, alternateButton, and otherButton.
The NSBeginAlertSheet Method will require that we create two additional helper functions to process the outcome of the user's response to the confirm dialog (sheet). We will use slightly different functions. The documentation refers to these as Selectors, and they are of type SEL.

sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
sheetDidDismiss:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;


confirm_dialog_04.png

Image ( 5 )

Seeing that this is where we always start by creating a Controller Class, we will narrow the number of items displaying in the Classes tab, of the Library Palette by typing NSOb in the search field as shown. Once we have narrowed the selection, we CNTRL-Click on the NSObejct Class and create a New SubClass as shown.

confirm_dialog_05.png

Image ( 6 )

...and we give the SubClass a name. I happen to like AppController, because we are not doing anything complicated yet...

confirm_dialog_06.png

Image ( 7 )

Save the newly created (AppController.h) Header and (AppController.m) Class Files in the Project Folder.

confirm_dialog_07.png

Image ( 8 )

Click on Add, and we are off to the races...

confirm_dialog_08.png

Image ( 9 )

Once again we will narrow the number of items displaying in the Classes tab, of the Library Palette by typing AppC in the search field as shown. This time the AppController Class will be displayed, instead of the NSObject Class as before when we wanted to create the New SubClass.

Make sure that the Document (MainMenu.xib) Window is frontmost as shown and Double Click on the AppController Object Icon, and it will add the Icon (Instance of AppController) to the Document Window as shown.

If the Document (MainMenu.xib) Window is NOT frontmost when you Double Click on the AppController Object Icon, all that will happen, is that you will hear a beep, letting you know that you can't do what you are trying to do...

confirm_dialog_09.png

Image ( 10 )

If we CNTRL-Click on the Confirm Dialog Icon in the Document Window, the Black Dialog is made available for you, and you can see that the Application Window is associated with the Instance of the Confirm App Dialog Delegate that was created by Xcode when we gave the Project a name.

We will DISCONNECT that association by Clicking on the [x] as shown by the arrow cursor. The Confirm App Dialog Delegate Instance shown in the Document Window will no longer be the Delegate for the Window, the AppController will become the New Delegate for the Application Window.

confirm_dialog_10.png

Image ( 11 )

After Clicking on the [x] we now see that the Application Window is homeless, it is not associated with any Delegate, so if we were to run the Project now, any actions or events taking place within the window, would have no one (No Class) to respond.

We will fix that problem...

confirm_dialog_11.png
Image ( 12 )

We will now CNTRL-ClickAndDrag as shown from the Window Instance to the AppController Instance (see the dragging direction of the red arrow)

confirm_dialog_12.png
Image ( 13 )

Select delegate from the Black Dialog as shown, so that the AppController will become the Delegate for the Application Window. Once this step is done the AppController will respond to resizing, moving, clicking, etc. of the Application Window. (Any event that needs to be addressed, or responded to will require a Method to handle that event response within the AppController Class).

confirm_dialog_13.png


Image ( 14 )

This shows an alternate way of linking, or associating Events within the Application Window with AppController Class Response Methods.

Instead of the steps taken in Images 12 and 13, we could have CNTRL-Clicked on the Instance of the Window (Confirm Dialog) as shown, and then Dragged from the Black Dialog delegate (*) to the AppController Class. This would accomplish the same thing, which is to tell the Application that if any Events as are detected within the Application Window, they are to be processed by the (Delegate) AppController Class.

confirm_dialog_14.png

Image ( 15 )

This image shows that we are Dragging the Header and Class Files from the location where Xcode placed them, when they were created, to the Classes Group Folder

The Classes Group Folder is a virtual folder, it doesn't really exist, though we could create one if we wanted to do that. It doesn't matter, this move was done because it makes it easier to find similar items, particularly when we start adding many more files in larger projects.

confirm_dialog_15.png

Image ( 16 )

Well, here is the Header File. It declares three Methods, the first one windowShouldClose: is triggered when the user clicks on the window's red close button. window_close_button.png

The windowShouldClose: Method is really a System Event Response Method, not a SUPPORT METHOD as shown in the image as a comment. The other two methods, acceptedSheet: and rejectedSheet: are support methods that will be called by the Application Kit Function (Method) NSBeginAlertSheet: as defined in the AppController.m (Class ) File.

confirm_dialog_16.png

Image ( 17 )

I could not find anywhere in the documentation what the last parameter to NSBeginAlertSheet: was supposed to be, all I found were three dots for the last parameter, and I am sure that must have some special meaning but I am at a loss on that question. If I find out what it is I will correct this document. That having been said, nil worked fine. Poor excuse, I know...

You can see that instead of the two methods mentioned in the documentation sheetDidEnd: and sheetDidDismiss:, I used acceptedSheet: and rejectedSheet:, which work just fine. I could just as soon have called them acceptance, and rejection, but I didn't.

The dialog, as you will see in Image ( 19 ) has three buttons {Cancel, Don't Save, OK}, but there are only two methods required to process the outcome of the user interaction with the confirm dialog. There can only be two outcomes {OK, NOT_OK} where :
  1. acceptedSheet: is called if the outcome is {OK}
  2. rejectedSheet: is called if the outcome is {NOT_OK} = {Cancel, Don't Save}, such that the Method that will handle the {NOT_OK} will have two deal with two possibilities.
    • {Cancel} will do nothing, just allow things to stay as they are.
    • {Don't Save} will close the Application Window and Quit out of the Application
    We can do that...
confirm_dialog_17.png

Image ( 18 )

You can see that we are about to Click on the Application Window's close button.

confirm_dialog_18.png

Image ( 19 )

window_close_button.png When the user clicks on the window's red close button a performClose: Action is triggered, which further triggers a windowShouldClose:. The ConfirmDialog Sheet shows that NSBeginAlertSheet: has been called by windowShouldClose: and the user is presented with a choice {Cancel, Don't Save, OK}, but confirm dialog has not been confirmed, or denied, so there is only one log entry...

confirm_dialog_19.png

Image ( 20 )

After we Click on {Cancel} we notice that the returnCode is NSAlertAlternateReturn which is a Constant with a value of 0. We also notice that nothing changed, the Confirm Dialog is still there, so basically it is as if the user cancelled and continued working...

The really interesting thing when you look at the log, is that even though there are two Methods with selectors, one in case the user Accepts, and one in case the user Rejects, both are actually called. That being the case all we really need was one Method with a three part if statement.

SEL sel_ok = @selector(acceptedSheet:returnCode:contextInfo:);
SEL sel_not_ok = @selector(rejectedSheet:returnCode:contextInfo:);

So the big question then becomes, why does NSBeginAlertSheet need two SEL's, if it is not going to do anything with that knowledge.

If it is going to use two SEL's then that makes you think that it is going to determine whether the user's response fell into the {OK} or {NOT_OK} category, but it doesn't do that, it calls them both.

Redundant ?

One SEL would have worked fine...

window_close_button.png Now we need to Click on the Application Window's close button one more time to trigger the close event again.

confirm_dialog_20.png

Image ( 21 )

This time we are going to Click on the {Don't Save} button, we'll see what happens on the next image.

confirm_dialog_21.png

Image ( 22 )

OPEN Image ( 17 ) in another window so you can see it at the same time, then move it to one side or the other...

After we Click on {Don't Save} we notice that the returnCode is NSAlertOtherReturn which is a Constant with a value of -1. We also notice on Image ( 17 ) that the Confirm Dialog is now gone, the contents of the Application Window, or perhaps even the Applications Documents, if there are any others have not been saved, and the Application has Quit....

You can see that on Image ( 17 ) the rejectedSheet: Method shows that:
  • The returnCode equal to NSAlertAlternateButton which is Constant with a value of 0, means that the user has clicked on the {Cancel} Button (alternateButton).
  • The returnCode equal to NSAlertOtherButton which is Constant with a value of -1, means that the user has clicked on the {Don't Save} Button (otherButton).
Likewise, Image ( 17 ) the acceptedSheet: Method shows that:
  • The returnCode equal to NSAlertDefaultButton which is Constant with a value of 1, means that the user has clicked on the {OK} Button (defaultButton).
Using these Constants {NSAlertDefaultButton, NSAlertAlternateButton, NSAlertOtherButton} makes it easy to swap button labels around, such that if you want to make the {Cancel} button be the defaultButton, it is a simple matter...

The NSLog entries in the Console Window makes it easy to see how the user responds to the Confirm Dialog (sheet).

One last thing while we are looking at the Log entries, notice that any entries that I create contain a four digit number in brackets. By using four digits that are unique within a document, I don't have to wonder, or look at the code to see what line caused a particular entry. I can immediately look for the number (for example [4674]) and go right to the line of code that created the entry. This technique simplifies considerably tracking down errors, or messages. I use this same type of thing no matter what language I am using with on error, or with exceptions. I hate error messages that say things like "Could not open a file" which is pretty much useless. Anyway this method is very handy...

confirm_dialog_22.png

Image ( 23 )

window_close_button.png This time after Clicking on the Application Window's close button, we are going to Click on the {OK} button, and we'll see what the Console shows...

confirm_dialog_23.png

Image ( 24 )

Sure enough the return value is 1.

This time the returnCode equal to NSAlertDefaultButton which is Constant with a value of 1, means that the user has clicked on the {OK} Button (defaultButton). This means that if the user Clicks on {OK} the acceptedSheet: Method can Save whatever needs to be saved, Close the Application Window and Quit out of the Application.

confirm_dialog_24.png

Image ( 25 )

Next thing we want to do is Create a Button that triggers the same type of behavior that the Application Window's close button triggered. So we'll fetch an Instance of the NSButton Class from the Library Palette and drag it over to the Application Window. Double Click on the Button to put it in the Edit Mode, and set the Button Label to "Done".

confirm_dialog_25.png

Image ( 26 )

Now we are going to CNTRL-Drag from the Done Button to the Application Window's close button, this will allow us to send a message to the Application Window's close button.

confirm_dialog_26.png

Image ( 27 )

We want the Done Button to send a performClose: message to the Application Window's close button, so we will select performClose:.

confirm_dialog_27.png

Image ( 28 )

We tell Xcode to Build and Run again, and we are presented with the Application Window, but this time we are going to Click on the Done Button, to see if our performClose: message works...

confirm_dialog_28.png

Image ( 29 )

And sure enough it works...

If we were ambitious, we could convert the Confirm Dialog into a reusable method that was not hardwired, but we'll leave that for another day...

confirm_dialog_29.png

Image ( 30 )

A little handy info from the Documentation about windowShouldClose:.

confirm_dialog_30.png

Image ( 31 )

A little more info worth looking at...

confirm_dialog_31.png

Image ( 32 )

performClose:

If the window's delegate or the window itself implements windowShouldClose:, that message is sent with the window as the argument. (Only one such message is sent; if both the delegate and the NSWindow object implement the method, only the delegate receives the message.) If the windowShouldClose: method returns NO, the window isn't closed. If it returns YES, or if it isn't implemented, performClose: invokes the close method to close the window.



confirm_dialog_32.png


The zip file contains two identical projects, except for the support methods referred to in the Documentation. Both run identically, and it doesn't make any difference what they are named, as long as they are called by the correct name.

The NSBeginAlertSheet: Method required us to create two additional helper functions to process the outcome of the user's response to the confirm dialog (sheet). We used slightly different Method Names. The documentation refers to these as Selectors, and they are of type SEL as defined in the AppController.m (Class ) File.

ConfirmDialog_01 uses :

acceptedSheet:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
rejectedSheet:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;

ConfirmDialog_02 uses :

sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
sheetDidDismiss:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;


DOWNLOAD Xcode Project : Xcode_0032_Three_Button_Confirm_Dialogs.zip


I hope this is helpful, best of luck,

Bill Hernandez
Plano, Texas