📜 ⬆️ ⬇️

Deep in cocoa

In this article I will try to tell a little more about Cocoa and its basic principles. I will say right away that the material will not be comprehensive, so we teach the hardware here .

Open Xcode and create a new Cocoa Application, name it DotView. What will our program do? It will draw a point on the NSView component. What is NSView? This is the base class for Cocoa graphic widgets, so if you want to create your own widget, then most likely it will be an NSView heir.

Now add a new class to the project. File => New File and in the opened window select “Objective-C NSView subclass”, click Next, set DotView as the name, do not forget to put a tick in front of “Also create DotView.h” and click Finish:
create a DotView class
Now look at the contents of the DotView.m file. The wizard has already added two functions: - (id) initWithFrame: (NSRect) frame and - (void) drawRect: (NSRect) rect. The first function is called when creating a new object of type DotView, and the second is called every time when our component needs to be redrawn, so it is logical to place the drawing commands there.

Add a couple of lines to the drawRect function:
NSRect bounds = [self bounds];
[[NSColor whiteColor] set];
NSRectFill (bounds);

the first command we get the current size of our component. NSRect is a common C structure.
With the second command, we first send the whiteColor message to the NSColor object, in which case it returns the white color to us, and then we send the set message that sets the Current Graphics Contents color (who knows how to translate it into Russian?) White.
The third command is a function call that will flood the area passed to it as a parameter by the color set in Current Graphics Contents. From this example it is clear that Cocoa is not only a collection of objects, it also has a bunch of useful, frequently used functions.
')
Well, now write the code to draw the point.
create a structure describing a rectangular area in which our point will be located
NSRect dotRect = NSMakeRect (50, 50, 100, 100)

this string should already be understood
[[NSColor blueColor] set];

create a new bezier curve that will be an oval in a given area (in our case it will be a circle) and immediately send the fill message to the created curve, which will draw the curve and fill it with the current color.
[[NSBezierPath bezierPathWithOvalInRect: dotRect] fill];

at the end, the drawRect function should look like this:
- (void) drawRect: (NSRect) rect {
NSRect bounds = [self bounds];
[[NSColor whiteColor] set];
NSRectFill (bounds);
NSRect dotRect = NSMakeRect (50, 50, 100, 100);
[[NSColor blueColor] set];
[[NSBezierPath bezierPathWithOvalInRect: dotRect] fill];
}


Save the file, click on MainMenu.nib and get into Interface Builder.
Place the CustomView component in the window, stretch it and attach it to the window borders (size tab of the Inspector panel). On the Identity tab of the Inspector panel in the Class box, select the DotView, now on the component there is an inscription not a Custom View, but a DotView. Save, return to Xcode, Build and Go and get the result:


Let's slightly increase the functionality of our application. Open the file DotView.h and add to the class DotView two fields NSPoint center - the center of our point, float radius - the radius of the point:
interface DotView: NSView {
NSPoint center;
float radius;
}

Now, when creating an instance of a class, we need to initialize these variables, so we will add to the initWithFrame function: set initial values.
- (id) initWithFrame: (NSRect) frame {
self = [super initWithFrame: frame];
if (self) {
center.x = center.y = 100.0;
radius = 50;
}
return self;
}

Essno, now you have to alter the point drawing function a little, namely, to change the line for creating a border:
NSRect dotRect = NSMakeRect (center.x-radius, center.y-radius, 2 * radius, 2 * radius);


Let's make it so that when you click the mouse button, the point moves to the point where the cursor was. To do this, we define the function - (void) mouseDown: (NSEvent *) event. This function is called whenever a mouse button is pressed on our component. event is a variable that stores all the necessary information about the event.
Inside the function, we write the following:
we get the coordinates of the point where we clicked
NSPoint point = [event locationInWindow];

Now we assign the center of our point to the coordinate that corresponds to the coordinate of the point we clicked on our widget.
center = [self convertPoint: point fromView: nil];

and now we need to redraw the widget for the changes to take effect. But instead of directly calling the drawRect function, we’ll tell the widget to redraw it:
[self setNeedsDisplay: YES];

this is where the function ends.

To make it more interesting, we will make it so that the position of the point can be changed not only by clicking, but also by dragging, for this we define the function - (void) mouseDragged: (NSEvent *) event:
- (void) mouseDragged: (NSEvent *) event {
[self mouseDown: event];
}

At the moment, the DotView class looks like this:

Save, run and enjoy the result.

Target / Action


Every Cocoa widget has a target and an action. A target is an object to which a message is sent when something predetermined happens to the widget. For example, when we change the position of the slider, it sends a message to its target. An action is the actual message it sends. This message always has one argument.

Now we need to add the ability to resize the point in the program. To do this, we define a function in our class - (void) changeSize: (id) sender. The argument sender is the widget that sent this message. The function will look like this:
- (void) changeSize: (id) sender {
radius = [sender floatValue];
[self setNeedsDisplay: YES];
}

Since this method does not belong to the NSView class, you need to write its definition in DotView.h. We need IB to see this method, so we should write IBAction as the return type. The content of DotView.h will be as follows:
#import <Cocoa / Cocoa.h>

interface DotView: NSView {
NSPoint center;
float radius;
}
- (IBAction) changeSize: (id) sender;
end

Save everything and go to IB. Add a Slider to the form and set it as an Action changeSize from our DotView component (drag the mouse pointer while holding the control from the slider on the DotView and after releasing the mouse button in the drop-down list select changeSize:). Also on the Attributes tab of the Inspector for the slider, check the “continouos” box so that it sends a message when the value is changed, and not after releasing the mouse button.
Save, go to Xcode, compile, run the project and get what we wanted: the point moves, the size changes


Now we need to figure out what really happened. The slider has two fields id target and SEL action. When we connected the slider and DotView using IB, IB set the target object to DotView, and SEL - selector (changeSize :). IB simplified our work and we don’t need to write a lot of code that doesn’t represent anything special (although any Cocoa application can be written without the help of IB).

Memory management


Each object that is an NSObject descendant has a reference count field. What does this field mean? This field shows how many objects are interested in this. When an object is created, its reference count is 1. If we are interested in an object, then we must send it a retain message, then its reference count will increase by 1. If we are no longer interested in the object, we must send it a release message, which will reduce its reference count by 1 and check if the reference count has become zero, but will send the object a message dealloc.

In Cocoa, it is assumed that the one who creates the object deletes it. If you need an object, send him a retain message, but then do not forget to send a release message.

We will improve our application. Now the dot will change color. To do this, in the DotView.h file, add a new instance variable NSColor * color. We will also add a new method to the class - (IBAction) changeColor: (id) sender ;, save the file and return to DotView.m.

Now we describe the function changeColor:
- (void) changeColor: (id) sender {
NSColor * newColor = [sender color];
[self setColor: newColor];
}

notice that we do nothing with the new color, we simply take a pointer to the current color from the sender and pass it as a parameter to the setColor function.

Now we will describe the setColor function:
- (void) setColor: (NSColor *) newColor {
if (newColor! = color) {
[color release];
color = [newColor retain];
[self setNeedsDisplay: YES];
}
}

In this method, the idea of ​​memory management in Cocoa is clearly visible. The old value is sent a release message, and the new value is a retain. To not include the setColor function in the header file, describe it earlier than changeColor :.

You also need to remember to initialize the color, i.e. add the following line to the initWithFrame function:
color = [[NSColor blueColor] retain];


Now it is necessary to describe the dealloc function:
- (void) dealloc {
[color release];
[super dealloc];
}

We notice that the color variable is not sent by dealloc, but by release, since It is possible that someone is interested in a variable. You must also send a message to the delloc super class.

Now you need to correct the drawRect: function. Instead
[[NSColor blueColor] set];

write
[color set];

save all files.

Next you need to put on the form component, with which we will change the color. To do this, go to IB and transfer the Color Well component to the form (standard color selection component) and set the action to it as changeColor: from DotView.

Save, go to Xcode, start and admire: move the slider - the size of the point changes, change the color - the color changes.

In the next part I will talk about Model-View-Controller, Key-Value Coding and Key-Value Observing in Cocoa.

If you are very interested, make sure that the dot cannot go beyond the component as a homework.

PS: if anything, the project file is here

Source: https://habr.com/ru/post/26049/


All Articles