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.
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.
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.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.
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
endThe 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.
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.
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.rbMainMenu.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.












Add a comment



Add a comment