9781449380373
_fundamentals.html

Chapter 2. Fundamentals

This chapter focuses on the fundamentals of GUI application development. It covers the concepts of run loops, callbacks and delegation, user inputs, outlets, and display. Finally, these basic concepts are illustrated in an example application.

Figure 2.1, “The way my mom sees herself using one of my applications” shows the simplified view that most users have when it comes to applications.

Figure 2.1. The way my mom sees herself using one of my applications

The way my mom sees herself using one of my applications

Figure 2.2. The details of a GUI app

The details of a GUI app

If we give it a closer look, we will notice some key elements my mom takes for granted. My mom has a good perspective that we don’t need to disagree with. From her perspective, she is simply having an interaction with her computer. She opens the application, clicks, types, and sees a result right away. However, what’s going on under the covers is a bit more complex, as shown in Figure 2.2, “The details of a GUI app”.

Run Loops

The first challenge is that we don’t want our code to execute and exit. We need our code to keep running to intercept my mom’s actions and react to them. We can’t pause or ask our code to sleep, because doing so might make us miss some events. Instead, we use what is called a run loop. Run loops are like threads that schedule work and coordinate the receipt of incoming events. By default, Cocoa applications have a main run loop that handles the events and keeps the application running. However, developers must be careful not to block this main loop, because that would prevent the user from interacting with the application and will cause the infamous spinning “pizza of death,” aka “beach ball of death,” to be displayed. To avoid that, you can use multiple run loops or, more simply, use asynchronous APIs.

Callbacks/Delegation

A callback is a function that you define but that is called by some other part of the system. Callbacks are the center of event-driven applications, which certainly include the ones you’ll write for the Mac (and which also includes Ruby on Rails applications, with which you may be familiar). The runtime loop calls one of your functions when an event takes place you need to handle (Figure 2.3, “A callback is triggered when a window closes”).

Asynchronous APIs also use callbacks. After you launch an asynchronous operation, your application continues while the operation takes place in the background. For instance, you may launch an operation that downloads a large file over the network while you allow the program to continue and handle other requests. This means that whatever code you want to execute at the successful completion of the operation—for instance, storing the file on the disk after it has been downloaded—has to be encapsulated in a callback, which the runtime invokes. Asynchronous operations also let you specify functions to be called in case of error and for other reasons. All these functions are called delegate callbacks.

Figure 2.3, “A callback is triggered when a window closes” shows a callback triggered when the user clicks on the Close button of a window. The callback is called only if you previously attached it to this instance of the window. You might have noticed that the callback selector ends with a colon, which means that it takes an argument (in this case a notification). We will discuss the callback/notification concepts a bit further in Chapter 5, Application Kit.

Figure 2.3. A callback is triggered when a window closes

A callback is triggered when a window closes

User Inputs

User inputs, such as keyboard or mouse events, need to be wired to actions so that you, the developer, can decide what happens when an event is triggered. When developing Cocoa applications, this is usually done in Xcode’s Interface Builder. The developer links an action received on a UI item to a controller’s action.

Figure 2.4, “Button connections in Xcode” shows the connections between a button and a controller. As you can see, the button’s sent action (the action triggered when the button is clicked) is associated with the controller object. What that means is that when the button is pressed, the method calls button_pressed on the Controller class.

Figure 2.4. Button connections in Xcode

Button connections in Xcode

Figure 2.5, “Controller connections in Xcode” shows how the controller handles the connection from a button to an action. In the Received Actions list, notice that the button object’s push button action is wired to the button_pressed action on the controller.

Figure 2.5. Controller connections in Xcode

Controller connections in Xcode

As a quick preview of what we will discuss later on, here is the only code required to implement a button action:

class Controller
  def button_pressed(sender)
    puts "The button was pressed"
  end
end

The wiring itself is done graphically in Interface Builder: you create a controller object that you connect to the Controller class, drag a button from the library, visually link its action to your controller object, and choose the action to trigger. But I am getting ahead of myself—you will see that in a later chapter.

The point is that connecting user inputs to code is trivial with MacRuby.

Outlets

You might have noticed in the previous connection screenshots that some outlets were mentioned. Outlets keep track of UI elements such as windows and widgets in your code. In Figure 2.5, “Controller connections in Xcode”, for instance, the controller defines four outlets to keep track of four UI elements used to display contact information.

Most of the time, you want to keep track of the application’s state programmatically and make some modifications to UI objects. To do that you need a way to reference events, and that’s what outlets are for. Creating an outlet in a class is very simple—just create an attribute accessor (technically a getter/setter to an instance variable):

class Controller
  attr_accessor :main_button
end

Once you have defined the outlet, Interface Builder automatically adds it to the list of available outlets, and you can connect a UI element to an outlet via Xcode’s Interface Builder.

Again, you will see much more about the use of outlets later on. What is important to remember is that keeping track of UI elements from controllers is straightforward. When an event is triggered, your callback receives the notification and can modify the state of any UI element.

Display

So far, you have seen that the code runs continuously in a run loop, and waits for events. You saw that events can trigger actions and that action code has access to UI elements. The last missing piece is the rendering or display of data on the screen. Cocoa UIs are usually designed using Xcode, which comes with a library of UI elements ready to be used.

Figure 2.6, “Xcode object library” shows a sample of the various UI objects available to OS X developers.

Figure 2.6. Xcode object library

Xcode object library

To build your application UI, you can drag and drop the elements and connect them visually to the icons representing your code. Most elements know how to redraw themselves, so you don’t need to do anything more. However, if you start doing anything advanced, such as developing video games or drawing on the screen, you might need to mark your elements as needing to be redrawn, or actually define how to redraw the elements. But we will keep that for later.

Example

If you have not used Cocoa previously, some of the concepts we’ve discussed might be a bit hard to grasp. Maybe the best way to bridge the gap is to jump ahead and take some time to just play with an application and look at how it works. We are going to write some example apps later on, but to get a feel for the meaning of everything we just discussed, download the demo application for this chapter. Open it with Xcode (double-click demo.xcodeproj) and press Command-R (or click the Run button). Figure 2.7, “MacRuby demo app” shows how the running demo app should look.

Figure 2.7. MacRuby demo app

MacRuby demo app

Warning

Make sure the “Demo” scheme is selected.

Take some time to play with the app: change the label text, turn on the mouse coordinate logger, apply some image filters, drag and drop another image, resize the window, and close the window or click the quit button. You might also want to look up the documentation for some of the APIs we are using, such as NSWindow.

Not all the concepts discussed here are shown in the demo app. My goal is to give you a quick idea of what you can do, how much code is needed, and how things connect to each other.

For a better understanding of how things work, focus on these two files:

  • controller.rb

  • MainMenu.xib

MainMenu.xib is the “view” file that defines all the UI elements and wires them to our code, which lives in the controller.rb file. At this point, I am not going to explain the code in detail, but the code can give you a feel for how things fit together.

Warning

This is just an example app, and it does not necessarily enforce best practices.

Start by opening the MainMenu.xib file. Now right-click the Window icon. You should see the window’s delegate pointing to the controller object. Right-click the controller object and you will see all the outlets and actions.

Next, start inspecting some of these objects by opening the Utilities bar (View Utilities Attributes Inspector) and selecting some items. In parallel, open controller.rb. Look at how the outlets map to the attribute accessors (lines starting by attr_accessor) and at how the actions map to the methods taking an argument, such as update_text_field. Delegate methods include windowShouldClose and windowDidResize. A timer runs without blocking the main loop.

Take some time to get familiar with the code. Try adding a new button and connecting it to a new action. Test the effect of blocking the main loop by adding a sleep call to an action, for instance, to the sepia action:

def sepia(sender)
  sleep(15) # block the main loop for 15 seconds
  # rest of the code
end

If you feel like playing and have some experience with Xcode, add more UI elements and figure out how to make them behave properly when the window is resized. Don’t worry if you are not fully getting it yet. The next few chapters focus on how things are designed, which tools to use, and how to map your existing knowledge to MacRuby and Cocoa. After that, we will go back to concrete examples and examine how to accomplish specific tasks. Feel free to jump ahead and then come back to the more technical explanations if that works best for you.

Site last updated on: November 9, 2011 at 10:00:57 AM PST