Chapter 5. Application Kit
The Application Kit, also known as the AppKit framework, contains all the classes needed
to build OS X GUI applications. This is the framework we used in our “Hello
World!” example in the section called “Code Example”.
AppKit is a very large framework
and this book will not cover all the classes provided. I’ll introduce the
concepts you need to get started and illustrate a few of the classes that
every programmer uses for graphical interfaces.
Cocoa Key Principles
You need to understand a few key concepts before digging
further into Cocoa. So far, we have looked closely only at the Foundation classes, which map fairly intuitively
to concepts and library calls in other programming languages and
programming environments. With Cocoa, we jump into unique concepts that
are less intuitive.
Cocoa is a well-thought-out, well-designed, and very consistent API. To assure this consistency, a few key concepts underlie most of the API classes. By following the designed conventions, you will avoid spending a lot of time rewriting your code over and over.
Note
These Cocoa concepts and conventions are unfortunately not enforced in all the available frameworks. Some C-based frameworks were incorporated into Cocoa simply by adding an Objective-C wrapper, and these usually don’t follow the conventions as nicely as frameworks written directly in Objective-C.
Model-View-Controller Design Pattern
Ruby developers who have done any web development should be quite familiar with the model-view-controller (MVC) design pattern. It’s one of the key conventions enforced by the Ruby on Rails framework, and it goes back to the early 1980s when the Smalltalk community defined it, so it can also be found in plenty of other environments: GTK, Tcl Tk, Qt, Swing, WPF, and many others, including Cocoa. Basically, your code ends up divided in three parts: the model, which represents the data upon which the application operates, the view, which renders the model and potentially makes it interactive, and finally the controller, which is the glue between the model and the view and runs what many people call the “business logic” unique to your application.
When developing a Cocoa app, you will usually define your views in Interface Builder, Cocoa’s UI tool, while your models and controllers will be written in your editor/IDE (usually Xcode). We will go through these tools later in this chapter.
Protocols
As you have probably guessed, a Cocoa protocol has nothing to do with the international agreement aimed at ending child labor in the production of cocoa, known as the Cocoa Protocol and signed in September 2001.
Protocols are well-defined APIs that any class may choose to implement. In other words, protocols are a way to declare a convention via an API. Two classes implementing the same protocol can easily communicate with each other without having to be subclasses of the same superclass.
Protocols are basically just a set of well-defined methods adopted by different classes. Some APIs expect to receive certain objects to implement certain protocols. So if you are developing your own class, you will need to make sure to implement the expected API. Once you do, we say that your class conforms to the protocol.
Key-Value Coding
Often abbreviated KVC, the NSKeyValueCoding
protocol defines the mechanism to set and retrieve an object’s
attributes/properties by name instead of by invoking a method. This is a
fundamental value concept in Cocoa programming, and it essentially
allows you to access instances as if they were hash tables/dictionaries.
There are many advantages of such an approach. The most obvious are that
you don’t need to explicitly write getters and setters and that you can
implement UI bindings and serialization much more easily, since knowing
the name of a property is enough to get and set it.
Note
KVC is often mentioned with KVO (key value observing), which is a notification system using the observer pattern and based on KVC.
Here is a simple example in which we will pretend that Player is a KVC-compliant class:
matt = Player.new
matt.setPoints(42)
matt.valueForKey('points') # => 42
matt.setValue(442, forKey: 'points')
matt.valueForKey('points') # => 442The Player class doesn’t have
to implement a setPoints accessor
method. Because the class implements the NSKeyValueCoding protocol, it intercepts your
invocation of setPoints and sets the
points instance variable in your
instance to the value you supply. The other three methods shown here are
also interpreted according to the protocol.
The code you can write using this protocol is quite similar to Ruby’s accessor syntax:
matt = Player.new matt.points = 42 matt.points # => 42 matt.points = 442 matt.points # => 442
You can simply implement it like this:
class Player attr_accessor :points end
It turns out that MacRuby generates the KVC code for you when you add a Ruby accessor. This way, your classes can easily conform to the KVC protocol:
framework 'foundation'
class Player
attr_accessor :points
end
matt = Player.new
matt.points = 100 # Ruby syntax
matt.valueForKey('points') # => 100 (KVC protocol)
matt.setPoints(42) # KVC protocol
matt.points # => 42
matt.setValue(442, forKey: 'points') # KVC protocol
matt.points # => 442Here is another example using a class that’s KVC-compliant by
default: Hash and a KVC method named valueForKeyPath that we
haven’t seen yet:
framework "Cocoa"
class Me < Hash
def initialize
super
self["siblings"] = {"brothers" => ["Arnaud"], "sister" => ["Marjorie"]}
end
end
moi = Me.new
p moi.valueForKey('siblings')
# => {"brothers"=>["Arnaud"], "sister"=>["Marjorie"]}
p moi.valueForKeyPath("siblings.brothers")
# => ["Arnaud"]In this example, we create a new class called Me that inherits from Hash and therefore is KVC-compliant by
default. We define the initialize method to
set some default value. The method starts by calling super so the code defined by the Hash constructor can be triggered, then sets
the default values. Once my code is written, we create an instance of
Me called moi and use the previously discussed
valueForKey method,
which works as expected. Then we invoke another KVC protocol method,
called valueForKeyPath, which takes a
path to the object we are interested in. Notice that valueForKeyPath requires a scalar as its
argument; we can’t pass it an array index. For instance, passing
siblings.brothers[0] will result in a nil
value.
Bindings
When developing a Cocoa application, you can bind an attribute of one object to a property of another. For instance, imagine we are developing a music player. On the one hand, we have a player object defining a volume attribute, and on the other, we have a slider UI controller to allow the user to control the output volume. We also want to display the setting of the volume. To implement all this, we are going to load our player instance in the controller and bind its volume attribute to our slider and text field values.
Let’s start by defining our model.
Create a new Xcode MacRuby project (see the intro to Xcode to learn more
about Xcode) and add a new Ruby file called player.rb. Our model code is very simple—just
a KVC conform class with a volume attribute, as shown here:
class Player attr_accessor :volume end
Our AppDelegate class is
not much more complicated, we just need to add a new accessor for the
player and initialize an instance of the object when the class instance
is initialized:
class AppDelegate
attr_accessor :window
attr_accessor :player
def applicationDidFinishLaunching(a_notification)
# Insert code here to initialize your application
end
def initialize
@player = Player.new
end
endNow edit your view file (MainMenu.xib) by clicking on it in
Xcode.
The template already has an NSObject instance
called App Delegate, which represents an instance
of the AppDelegate class (Figure 5.1, “Details of the controller object viewed in the
Inspector”). Look at it via the Identity
Inspector and notice that the AppDelegate class is set as the object’s
class.
Now open the Object Library, look for the vertical slider, drop it on the UI inside the window’s view (Figure 5.2, “Vertical slider viewed from the Object Library”), then look for the text field icon and drop it inside the view, as shown in Figure 5.3, “Object hierarchy and UI”.
Once the UI objects are in place, you need to set the bindings for the slider. To do so, select the slider and open it in the Inspector. Then select the bindings tab in the Utility area and set the bindings settings as shown in Figure 5.4, “Slider’s bindings viewed in the Inspector”.
Set the bind to App Delegate and set the model key path to
player.volume. Do the same for the
text field. The slider will set the value while the text field will read
the value. Save your project and run the app from Xcode by clicking the
Run button or by using the Command + R shortcut. The end result should
look something like Figure 5.5, “View of the slider in the compiled app”.
When you move the slider, the text value changes, and vice versa. But, what’s even more interesting is that the model’s attribute value also changes at the same time. As you can see, bindings can save you a lot of development time.
Delegation
We used and discussed delegation when we worked on our
“Hello World!” example in Chapter 1, Introduction. In the
delegate class we created, we implemented two delegate methods. One was
an NSApplication delegate
named applicationDidFinishLaunching and the other
was an NSWindow delegate named
windowWillClose.
Delegation is a common pattern in
Cocoa. It often offers an elegant way to hook into events. For instance,
when an application is started, two delegates are triggered if they are
available. The applicationWillFinishLaunching delegate is
called as soon as the run loop is ready, but before the application is
started. applicationDidFinishLaunching is called after
the application is launched.
An NSWindow instance has a lot of delegates
available for implementation (see http://developer.apple.com/mac/library/documentation/cocoa/reference/NSWindowDelegate_Protocol/Reference/Reference.html).
A few of the simpler delegates include:
Look at the documentation to learn more about the delegates available for the classes you are using.
User Interface
Most of the Ul-related classes are provided by the Cocoa framework. Instead of going down the long list of UI classes, let’s talk about the main concepts and elements critical to MacRuby developers.
Windows, Views, and Cells
Windows, views, and cells are the visible units the user deals with. For instance, a button is usually a view, as is a text entry box.
Windows and panels
Windows are conceptually easy to understand and don’t
need much explanation. Technically, a Cocoa window is an instance of
the NSWindow class in
which a view (NSView instance)
displays its content. To group windows, an application can use panels
via the NSPanel class, which
is itself a subclass of NSWindow.
Views
Per Cocoa’s definition, “A view instance is responsible for drawing and responding to user actions in a rectangular region of a window.” In other words, views render some of the app content and handle user-initiated events.
Whenever you need to display content in a window, you will need a view. The view also handles keyboard and mouse events. Views often act as containers and contain one or more nested views.
The primary view class is called NSView, which
inherits directly from NSResponder. This
lets you easily implement keyboard/mouse event handling by overriding
NSView methods. This class
implements the fundamental view behavior, making it a perfect
candidate for creating a custom view.
Application Kit offers a set of great NSView subclasses to help with common tasks.
Here are a few examples:
Positioning
The position of an object is called its coordinates, and this refers to the x and y pixel positions of its bottom, leftmost corner. When using Cocoa views, it’s important to keep in mind that the coordinates of each object are always relative to the immediately enclosing view. Thus, each of the views you create will define and maintain its own coordinate systems. All the objects in each view will be relative to the view’s coordinate system.
By default, as shown in Figure 5.6, “Representation of the drawing origin”, the origin is at
(0.0, 0.0), which is located in the
lower left corner of the parent object.
Let’s take an example. Imagine that we want to display a web page in a window. The web page’s view should take up only a quarter of the window and we want it to be at the top left corner (Figure 5.7, “Window with a web preview on top left corner”).
You could just use Interface Builder to create such a simple UI, but for the sake of explaining how views work, I will show how to programmatically create a window and content view, and to place a web page view on the top left corner of the window. Here is the code:
framework 'Cocoa'
framework 'WebKit'
application = NSApplication.sharedApplication
# create the window
width = 800.0
height = 600.0
frame = [0.0, 0.0, width, height]
mask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask
window = NSWindow.alloc.initWithContentRect(frame,
styleMask:mask,
backing:NSBackingStoreBuffered,
defer:false)
# assign a content view instance
content_view = NSView.alloc.initWithFrame(frame)
window.contentView = content_view
# create a web view positioned in the top left quarter of the super view
web_view_frame = [0.0, height/2, width/2, height/2]
web_view = WebView.alloc.initWithFrame(web_view_frame, frameName: "Web Frame",
groupName: nil)
request = NSURLRequest.requestWithURL(NSURL.URLWithString("http://macruby.org"))
web_view.mainFrame.loadRequest(request)
content_view.addSubview(web_view)
# center the window
window.center
# show the window
window.display
window.makeKeyAndOrderFront(nil)
window.orderFrontRegardless
application.runYou can copy this code to a file and run it with the MacRuby executable. I will skip the part about requiring the frameworks and creating an application.
We start the code by creating a frame for our window:
width = 800.0 height = 600.0 frame = [0.0, 0.0, width, height]
Technically, a frame is just an array with four floating-point values representing the x-axis coordinate, y-axis coordinate, width, and height. An Objective-C code example would have the following syntax:
NSRect frame = NSMakeRect(0.0, 0.0, 800.0, 600.0);
Both code snippets just shown are equivalent, except that one is written in Ruby and the other in Objective-C.
A frame defines the size of its content and its position inside the containing views. A frame in Cocoa can be considered like a picture frame in real life: you position it on the wall and the frame dimensions define the maximum area available for the canvas inside.
Our example starts by creating a window that will hold our UI. We
define two variables, width and
height, that we use for the frame. We
will reuse these variables later on to position our web page view
relative to the window.
We also need to create a
content view that will hold our UI items. Remember that we want our web
page view to take only one-fourth of the space. For that to happen, we
need a placeholder. That’s what our NSView instance
offers:
content_view = NSView.alloc.initWithFrame(frame) window.contentView = content_view
Note
If you start a new Interface Builder Application project, a window and its content view instance will already be created for you.
Now we need to create a frame for our web view. This frame is positioned relative to the content view, which in turn is positioned relative to our window. Since we stored the window’s width and height, we can easily define our web view frame to be displayed only at the top left corner of our GUI:
web_view_frame = [0.0, height/2, width/2, height/2]
Our frame defines
the web view x-axis position as
0.0, which is the graphics
environment axis origin and located all the way to the left. Then we do
some math to define the y-axis
position, width, and height. We take the superview (content_view) height and divide it in two to
find the middle point to position our web view. Because we want our web
view to only take a fourth of the UI, we set the width and the height to
be half the corresponding width and the height of the containing
view.
Note
Remember that we are starting to draw from the bottom left and not from the top, as you do when designing a web page.
We then create
the WebView instance, set the URL to
load, and finally add it to the content view subviews:
content_view.addSubview(web_view)
At this point, we are finished placing our content. However, why not center our window on the user’s screen while we are at it?
window.center
Calling the center instance method
on a window places it exactly in the center horizontally and somewhat
above center vertically. Another way to center the window is to manually
locate its frame:
screen_frame = NSScreen.mainScreen.frame
window.frameOrigin = [(screen_frame.size.width - width)/2.0,
((screen_frame.size.height - height)/2.0)]This code starts by retrieving the screen frame. Then we position our window right in the middle of the screen by taking the width and height of the screen, subtracting our UI width and height, and dividing the sum by 2.
On my laptop, my resolution is set to 1280x800. So, to find the x
coordinate, our code takes the screen width (1280), subtracts our
content view’s width (800), and divides the result by 2, locating the
x coordinate at 240.0. We do the same
with the height and get a y
coordinate of 100.0.
Once we have the coordinates figured out, we move the window frame origin and display the window.
Great, our content displays properly—but what if we want to track keyboard and/or mouse events?
Events and the Responder Chain
When an event message is dispatched, the message is sent
down the window. The window itself has a responder chain, which is a linked series of responder
objects. If the first object in the chain doesn’t handle the message,
the message is passed to the next responder in the chain and so forth,
until a responder handles the message or the responder chain has been
entirely traversed (in which case the window’s responder noResponderFor method is dispatched).
Note
If you have multiple windows, only the window in which the associated user event occurred will forward the event message to its responder chain.
The default responder chain is constructed by the
Application Kit framework, but can be modified by developers. The
default responder chain for a key event begins with the first responder
in the window where the focus is when the key is pressed. In contrast,
the default responder chain for a mouse event begins with the view on
which the event occurred. If not handled, the mouse event message is
sent to the window’s first responder, which is usually the selected view
object. The next responder in the chain is the UI object’s containing
view (superview) and so on up to the NSWindow object.
You can insert responders
in the responder chain by using the setNextResponder method
or the nextResponder= alias on the
view object.
Let’s experiment with the
responder chain. Open Xcode and create a new project using the MacRuby
application template. Now create a new Ruby file and call it event_view.rb. Our new class will be a
View subclass that will handle our
events:
class EventView < NSView
def acceptsFirstResponder
true
end
def keyDown(event)
puts "key down"
characters = event.characters
if characters.length == 1 && !event.isARepeat
character = characters.characterAtIndex(0)
case character
when NSLeftArrowFunctionKey
puts "you pressed the left arrow"
when NSRightArrowFunctionKey
puts "you pressed the right arrow"
end
end
end
endThe subclass implements a keyDown method that
takes an event argument. This method is part of the responder chain. You
can see from the example that we can get the characters pressed by
calling characters on the event. We
inspect the characters pressed to log an appropriate message. We then
check that the event isn’t a repeated event, in the sense that if the
user leaves his or her finger on a key, we want to get only one event.
We extract the characters (NSString instance) from
the event (NSEvent). We then take
only the first character using characterAtindex and try to match it to some
keyboard constants.
Now click on the MainMenu.xib
file. The contents will be displayed in a format like that shown in
Figure 5.8, “MainMenu.xib with the window expanded”.
Select the View in the Identity Inspector and, in the Custom Class
section, change the class value to EventView (see Figure 5.9, “View with a custom class handling keystrokes”). Xcode automatically
detects the new class we created.
Drawing
Cocoa’s drawing environment is available via the Application Kit framework, which is itself based on Quartz, the Macintosh’s basic 2D graphics rendering software. The environment automatically takes advantage of graphics hardware wherever it can, making the drawing process as fast as possible.
If you come from a browser-oriented background, you might not think about how UI elements are rendered. After all, when using HTML, you just use UI tags and let the browser deal with rendering. In Cocoa, however, understanding how UI elements are drawn and how to interact with the drawing processing can be very beneficial. If you use only standard system controls, all the drawing is automatically handled by Cocoa. But as soon as you start using custom views and controls, managing the customized appearance of your elements become your responsibility. Also, you might want to use the drawing features to display images, PDFs, vector-based drawings, or videos, to render openGL code, or to custom print.
But before getting too deep into custom drawing, let’s talk more about how a normal control element is drawn.
A control element such as a button
is usually an instance of a custom NSView class. NSView instances are responsible for their own
drawing, but in most views the drawing is delegated to another class
called the cell (generally a subclass of NSCell).
In the case of NSButton, the drawing
is delegated to an NSButtonCell instance.
To customize the drawing of a button, you can subclass the cell and
override some of its properties or methods. The advantage of having the
drawing done in the cell instead of the view is that one cell instance
can be shared by multiple controls of the same kind. Also, a cell can be
given to a more specialized view to customize only a part of it. A good
example of this is customizing a table view column using a different
cell.
If you look at the Interface Builder library and search for NSButton, you will find a lot of different
buttons (Figure 5.11, “Choice of NSButton types offered by the Object Library”). That’s because
each button uses a different NSButtonCell under the covers to
implement its own UI. The NSButtonCell associated with the button will
set the background color, style, images, states, sounds, and events. It
will also take care of the button’s content drawing.
When the button is loaded, Cocoa draws it
in its initial state. When a mouse event occurs on the button or a
triggering key is pressed, the NSButtonCell state is changed and the object
is marked as needing to be redrawn. You can also force the redrawing of
an element by calling setNeedsDisplay or
setNeedsDisplayInRect
on it. When the run loop restarts its cycle, all the elements that need
redrawing are redrawn.
Note
For more information about buttons, refer to Apple’s documentation, "Introduction to Buttons" at http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/Button/Button.html.
You can also totally customize the drawing
of your views by reimplementing the drawRect method on your NSView class.
drawRect is called on a view when
something about your view has changed or can be called manually.
Graphics Context
In Cocoa, as in many GUIs, you don’t apply visual traits directly to objects; that is, you don’t say, “Draw this rectangle with a blue border” or “Write this text in italic.” Instead, you define the traits in general for the whole window, using an abstraction called the graphics context. A typical sequence is:
Set the color in the graphics context to red.
Draw the background.
Set the color in the graphics context to black.
Draw a rectangle.
Etc..
The procedure may seem awkward, but it makes the best use of the hardware capabilities. A graphics context is thus a representation of the area onto which you’re drawing. The context has a huge range of attributes, each of which takes on one value at a time, such as red or black. Its job is to set and hold all the information needed so a view can be drawn properly. It even saves its state before rendering so changes can be undone.
Most of the time, the graphics context you will interact with represents one of your application’s windows, but the drawing destination can also refer to an image, an output device (i.e., a printer), a file, or an OpenGL surface. Cocoa maintains a separate graphics context per window and per thread. So, an application can have multiple contexts.
In a Cocoa
application, graphics contexts for most types of canvas are represented
by the NSGraphicsContext
class. To access the current thread’s graphics context, you can simply
call NSGraphicsContext.currentContext. This is
usually something you do when you implement your own drawRect method. The problem is that if you
modify the current context, all other items drawn in the same thread
will be affected by this change. For instance, if you are changing the
fill color to red, all objects redrawn after the change
will be redrawn with a red fill color. Here is an illustration of the
problem:
framework 'Cocoa'
class CustomView < NSView
def drawRect(rect)
# draw a red background
NSColor.redColor.set
NSBezierPath.fillRect(rect)
# draw a rectangle
path = NSBezierPath.bezierPath
path.lineWidth = 2
# starting point
path.moveToPoint [100, 50]
# draw a rectangle
path.lineToPoint [100, 100]
path.lineToPoint [200, 100]
path.lineToPoint [200, 50]
# close the rectangle automatically
path.closePath
path.stroke
end
end
application = NSApplication.sharedApplication
# create the window
frame = [0.0, 0.0, 300, 200]
mask = NSTitledWindowMask | NSClosableWindowMask
window = NSWindow.alloc.initWithContentRect(frame,
styleMask:mask,
backing:NSBackingStoreBuffered,
defer:false)
# assign a content view instance
content_view = CustomView.alloc.initWithFrame(frame)
window.contentView = content_view
# show the window
window.display
window.makeKeyAndOrderFront(nil)
window.orderFrontRegardless
application.runThe result is shown in Figure 5.12, “Display with improperly drawn rectangle”. The problem is that the rectangle we drew isn’t visible.
The
source of the problem is quite easy to spot: we have switched the
context color from black to red by using NSColor.redColor.set,
but when we then draw the rectangle, the stroke is still red and we can’t distinguish the
rectangle.
An easy fix would obviously be to reset the color to black before drawing the stroke. While that would work for this simple example, imagine if you were changing more than two context attributes and you were drawing several items in the view. Having to manually reset the context every time would become quite challenging. A more robust approach is to save the current context, set a new context, draw what you want using the new context, and restore the old context. This is why Cocoa offers methods to save the context and then restore it:
framework 'Cocoa'
class CustomView < NSView
def drawRect(rect)
context = NSGraphicsContext.currentContext
context.saveGraphicsState
# draw a red background
NSColor.redColor.set
NSBezierPath.fillRect(rect)
context.restoreGraphicsState
# draw a rectangle
path = NSBezierPath.bezierPath
path.lineWidth = 2
# starting point
path.moveToPoint [100, 50]
# draw a rectangle
path.lineToPoint [100, 100]
path.lineToPoint [200, 100]
path.lineToPoint [200, 50]
# close the rectangle automatically
path.closePath
path.stroke
end
end
application = NSApplication.sharedApplication
# create the window
frame = [0.0, 0.0, 300, 200]
mask = NSTitledWindowMask | NSClosableWindowMask
window = NSWindow.alloc.initWithContentRect(frame,
styleMask:mask,
backing:NSBackingStoreBuffered,
defer:false)
# assign a content view instance
content_view = CustomView.alloc.initWithFrame(frame)
window.contentView = content_view
# show the window
window.display
window.makeKeyAndOrderFront(nil)
window.orderFrontRegardless
application.runThe result is shown in Figure 5.13, “Display with all objects visible”. This time, the rectangle is visible.
And here is a more complete example, where we extract the graphics context switch into a method that can then be invoked for each object we draw:
framework 'Cocoa'
class CustomView < NSView
# Using a method yielding a block, we can keep
# our code cleaner
def temp_context(&block)
context = NSGraphicsContext.currentContext
context.saveGraphicsState
yield
context.restoreGraphicsState
end
def drawRect(rect)
# draw a red background
temp_context do
NSColor.redColor.set
NSBezierPath.fillRect(rect)
end
path = NSBezierPath.bezierPath
path.lineWidth = 2
# starting point
path.moveToPoint [100, 50]
# draw a rectangle
path.lineToPoint [100, 100]
path.lineToPoint [200, 100]
path.lineToPoint [200, 50]
# close the path automatically
path.closePath
# fill the content of the path in transparent white
temp_context do
NSColor.colorWithCalibratedWhite(0.9, alpha: 0.5).set
NSBezierPath.fillRect([100, 50, 100, 50])
end
# draw the rectangle stroke after the content was filled
path.stroke
# draw some text, because we are changing the context shadow
# we are doing that in a temp context
temp_context do
shadow = NSShadow.alloc.init
shadow.shadowOffset = [4, -4]
shadow.set
font = NSFont.fontWithName("Helvetica", size:24)
attributes = {NSFontAttributeName => font,
NSForegroundColorAttributeName => NSColor.whiteColor}
"MacRuby Rocks".drawAtPoint([60, 120], withAttributes: attributes)
end
end
end
application = NSApplication.sharedApplication
# create the window
frame = [0.0, 0.0, 300, 200]
mask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask
window = NSWindow.alloc.initWithContentRect(frame,
styleMask:mask,
backing:NSBackingStoreBuffered,
defer:false)
# assign a content view instance
content_view = CustomView.alloc.initWithFrame(frame)
window.contentView = content_view
# show the window
window.display
window.makeKeyAndOrderFront(nil)
window.orderFrontRegardless
application.runThe result is shown in Figure 5.14, “Result of the sample using an extracted context switch”.
The temp_context method
takes, as its argument, a block of code that is executed after the
current context is saved and before it is reset. The end result allows
for a more expressive syntax and the code ends up being easier to
maintain.
As a side note, as shown in the previous
example, drawing text in a view is very simple. Probably the simplest
way is to use drawAtPoint, as
follows:
position = [60, 120] "MacRuby Rocks".drawAtPoint(position, withAttributes: nil)
Other ways to draw text are to use NSAttributedString or
NSTextView. I leave it
up to you to explore further.
Images
To manipulate images, the Application Kit offers the
NSImage class. With
this class, you can load existing images from disk, draw image data into
your views, create new images, scale and resize images, or convert
images to any of several different formats.
A typical method that draws a simple image follows:
def drawRect(rect)
NSColor.whiteColor.set
NSBezierPath.fillRect(rect)
img_url = NSURL.URLWithString('http://bit.ly/apple_logo_png')
img = NSImage.alloc.initWithContentsOfURL(img_url)
img.drawAtPoint([0,0],
fromRect: NSZeroRect,
operation: NSCompositeSourceOver,
fraction: 1)
endWe start by setting the color to white and filling the background,
then we specify the URL of the image to load. With the instance of
NSURL ready, we just
need to pass it to our NSImage
constructor and we get an instance representing our image. Finally, we
use an API similar to the one that drew text in the previous
section.
In the call to drawAtPoint, the
first parameter is the coordinate where we want to start drawing the
picture (bottom left). Then we pass an empty rectangle represented by
the NSZeroRect constant
(passing [0,0,0,0] would have done
the same thing). The operation parameter refers to the
composing operation: in other words, the way the image should be
composed. A list of operation constants is available in the NSImage class reference (http://developer.apple.com/mac/library/documentation/cocoa/Reference/ApplicationKit/Classes/NSImage_Class/Reference/Reference.html#//apple_ref/c/tdef/NSCompositingOperation).
Finally, the last parameter refers to the opacity. This ranges from 0
(totally transparent) to 1, totally opaque.
The code just shown appears near the top of the following example, which is a complete application using the image:
framework 'Cocoa'
class CustomView < NSView
def drawRect(rect)
NSColor.whiteColor.set
NSBezierPath.fillRect(rect)
img_url = NSURL.URLWithString('http://bit.ly/apple_logo_png')
img = NSImage.alloc.initWithContentsOfURL(img_url)
img.drawAtPoint([0,0], fromRect: NSZeroRect, operation: NSCompositeSourceOver,
fraction: 1)
end
end
application = NSApplication.sharedApplication
# create the window
frame = [100, 100, 152, 186]
mask = NSTitledWindowMask | NSClosableWindowMask
window = NSWindow.alloc.initWithContentRect(frame,
styleMask:mask,
backing:NSBackingStoreBuffered,
defer:false)
# assign a content view instance
content_view = CustomView.alloc.initWithFrame(frame)
window.contentView = content_view
# show the window
window.display
window.makeKeyAndOrderFront(nil)
window.orderFrontRegardless
application.runAnother way to display images is to use Interface Builder. Start by adding an image to your Xcode
project. Then open your .xib file in Interface
Builder and drag an instance of NSImageView (referred to as Image Well) from
the Library to your UI. Select the new UI element and open the
Inspector. In the Image View Attributes, use the scroll-down menu to
pick your image. Voilà, you’re done!



















Add a comment



Add a comment