-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
283 lines (193 loc) · 17.7 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[Category: mac os | Cocoa Factory]]></title>
<link href="http://cocoa-factory.github.com/blog/categories/mac-os/atom.xml" rel="self"/>
<link href="http://cocoa-factory.github.com/"/>
<updated>2013-03-23T13:54:52-05:00</updated>
<id>http://cocoa-factory.github.com/</id>
<author>
<name><![CDATA[Alan Duncan]]></name>
</author>
<generator uri="http://octopress.org/">Octopress</generator>
<entry>
<title type="html"><![CDATA[Introducing CCFBrowserTextField]]></title>
<link href="http://cocoa-factory.github.com/blog/2012/09/21/introducing-ccfbrowsertextfield/"/>
<updated>2012-09-21T06:10:00-05:00</updated>
<id>http://cocoa-factory.github.com/blog/2012/09/21/introducing-ccfbrowsertextfield</id>
<content type="html"><![CDATA[<p>We just released <code>CCFBrowserTextField</code> for public consumption. Take a look at <a href="https://github.com/cocoa-factory/CCFBrowserTextField">our github repository</a> to get started using it. Here's what this <code>NSTextField</code> subclass looks like:</p>
<p><img class="right" src="http://i46.tinypic.com/2nlz5ah.jpg"></p>
<p>That's it. A text field with an embedded document-like icon. The intent is to provide a way of browsing to a path that should be contained in the text field without an external button. It was inspired both a desire for a more compact, connected UI and by seeing someone else who had done something similar. In the amazing <a href="https://github.com/omnigroup/OmniGroup">Omni Frameworks</a> in OmniAppKit framework, you'll find a number of interesting widgets. They created a text field with a calendar button that popups up a calendar picker. We liked the idea and learned a lot about how text fields work, then adapted it to meet our needs. By the way, Omni writes great software besides putting their frameworks out for public use. I use at least one of their pieces of software every day.</p>
<h3>Using <code>CCFBrowserTextField</code></h3>
<p>If you want to use <code>CCFBrowserTextField</code> just go to the <a href="https://github.com/cocoa-factory/CCFBrowserTextField">github repository</a> and download it. When you want to use it, just insert an <code>NSTextField</code> object into your interface and set its class type to <code>CCFBrowserTextField</code>. For the button to be active, you just need to provide it with an action block:</p>
<p>``` objc
- (void)awakeFromNib {</p>
<pre><code>[[self browserTextField] setActionBlock:^{
// do something here like launch NSOpenPanel etc.
}];
</code></pre>
<p>}
```</p>
<p>Here, <code>[self browserTextField]</code> is a property with an IBOutlet to your custom text field.</p>
<h3>Creating a custom <code>NSTextField</code></h3>
<p>This part is a bit like the "Making of ..." that comes with DVD's. If you want to get under the hood and learn a little about developing for Mac OS, here's your chance. (We've been developing so much for iOS that some of the older parts of Mac, like <code>NSCell</code> are a little foreign to us.)</p>
<p>Let's start from the inside out.</p>
<h4>Drawing into an image</h4>
<p>Where possible we favor drawing into an <code>NSView</code> or in this case <code>NSImage</code> rather than loading images from the bundle. This makes adapting to resolution changes like the Retina transition easier for us. We use <a href="http://www.paintcodeapp.com/">PaintCode</a> to generate the drawing code from vector drawings on-screen. It's a great piece of software.</p>
<p>Let's take a look at the process of drawing into an image. I'm going to focus on just that aspect rather than the Core Graphics details. In the <code>CCFBrowserTextFieldButtonImage</code> implementation we have one public method <code>init</code>:</p>
<p>``` objc
- (id)init {</p>
<pre><code>self = [super init];
if( !self ) return nil;
self.size = NSMakeSize(18, 18);
_drawIcon(self);
return self;
</code></pre>
<p>}
<code>``
The important thing to remember here is to set the size of the image before attempting to draw into it</code>self.size = ...<code>. Then we call</code>_drawIcon:` on ourself.</p>
<p>Drawing into the <code>NSImage</code> is a three step process. First we lock focus on ourself. This prepares the image for drawing. More specifically, it sets the current graphics context to the area of the offscreen window that is used to cache the <code>NSImage</code> contents. All of the drawing until we call <code>unlockFocus</code> is composited to the offscreen window. In step 2, we draw the image. I'm not going to go through each shape in this post - but you can get a feel for how the image is built up. Finally, we unlock focus which resotores the focus to the previous owner. That's it for drawing the image. But the image has to go somewhere, right? Next up, <code>CCFBrowserTextFieldButton</code></p>
<h3>An embeddable button</h3>
<p>Next up in our inside-out tour is <code>CCFBrowserTextFieldButton</code>. Starting with the class interface, we see block type definition:</p>
<p><code>objc
typedef void(^CCFBrowserButtonBlock)(void);
</code>
This is the typedef for the block used as an action for the button. More on this in a minute. Next up the class method(s):</p>
<p><code>objc
+ (NSSize)browserImageSize;
</code>
Class users will use this method to get information about the size of the image in the button. Some of the upstream users will need to know how large our image is to provide for cursor changes and text formatting in the fields.</p>
<p>Finally we have a couple methods to set the frame and/or the action handler. On to the implementation, let's look at the <code>initWithFrame:</code> method:</p>
<p>```objc
- (id)initWithFrame:(NSRect)frame
{</p>
<pre><code>self = [super initWithFrame:frame];
if( !self ) return nil;
[self setButtonType:NSMomentaryPushInButton];
[self setBordered:NO];
[self setImage:browserImage];
[self setImagePosition:NSImageOnly];
[self setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin | NSViewMaxYMargin];
return self;
</code></pre>
<p>}
```
Here we're just defining the basic properties of the button. We want the button to be an ordinary momentary push in button, without borders, with our specified image, and positioning the image as an image only button. So where does the image come from? We declare some static variables in the implementation:</p>
<p><code>objc
static NSImage *browserImage;
static NSSize browserImageSize;
</code></p>
<p>and we set them up in the <code>initialize</code> method:</p>
<p>``` objc
+ (void)initialize {</p>
<pre><code>browserImage = [[CCFBrowserTextFieldButtonImage alloc] init];
browserImageSize = browserImage.size;
</code></pre>
<p>}
```</p>
<p>And by the way, we're using ARC throughout. So should you. We were reference counting gurus before ARC; but ARC is too good to ignore.</p>
<p>Finally, we have a pair of methods that set the <code>frame</code> and the <code>_actionBlock</code> Because <code>NSButton</code> doesn't have a blocks-based action handler, we extended it by setting its <code>target</code> to <code>self</code> and the <code>action</code> to a method that just calls the action block. On to the cell.</p>
<h3>A digression on why controls have cells</h3>
<p>Newcomers to Mac OS X are often perplexed by the existence of <code>NSCell</code>. It's like <code>NSView</code>, but not really. It's associated with <code>NSControl</code>, but why?</p>
<p>Basiclaly <code>NSCell</code> is just a lightweight view. Take the example of <code>NSTableView</code>, in the old days, its cells were always made up of <code>NSCell</code> because the size of its instance was less than a view; and using a lighter weight object made tremendous difference in memory savings. So, most controls are backed by an <code>NSCell</code> that handles much of the lower-level work of the control.</p>
<h3>So, our cell</h3>
<p>To take care of some drawing bits, we subclass <code>NSTextFieldCell</code>. Here's how it works: By overriding <code>-titleRectForBounds:</code> we have the opportunity to modify the region in which our text can be drawn. Ordinarily the cell takes the insert area of the field to draw the text minus a little inset. In our case, we want to give plenty of room for the button. So we do this:</p>
<p>``` objc
- (NSRect)titleRectForBounds:(NSRect)bounds;
{</p>
<pre><code>NSRect buttonRect = [[self class] rectForBrowserFrame:bounds];
float horizontalEdgeGap = 0.0f;
bounds.size.width -= NSWidth(buttonRect) + horizontalEdgeGap;
return bounds;
</code></pre>
<p>}
```
We get the frame of the button in our bounds, then move the width accordingly.</p>
<h3>And, last but not least, <code>CCFBrowserTextField</code></h3>
<p>The outermost component is the field itself. There are a few things we need to take care of here. For example we have to add the button itself:</p>
<p>```objc
- (id)_initTextFieldCompletion {</p>
<pre><code>if( !_browserButton ) {
NSSize browserImageSize = [CCFBrowserTextFieldButton browserImageSize];
NSRect buttonFrame = NSMakeRect(0.0f, 0.0f, browserImageSize.width, browserImageSize.height);
_browserButton = [[CCFBrowserTextFieldButton alloc] initWithFrame:buttonFrame];
buttonFrame = [CCFBrowserTextFieldCell rectForBrowserFrame:self.bounds];
[self _setCellClass];
[self addSubview:_browserButton];
self.autoresizesSubviews = YES;
[_browserButton setFrame:buttonFrame actionHandler:^{
NSLog(@"pushed");
}];
}
return self;
</code></pre>
<p>}
```</p>
<p>We instantiate the <code>_browserButton</code> lazily here, first getting the size of its image, then setting its frame accordingly. We set the class of the cell our field uses directly. More on that in a moment. Then we add the button and set a silly default handler. If you want the button to do something meaningful, you'll have to set the field's action handler.</p>
<p>Now we have to tell our custom field to use <strong>our</strong> cell and not the default cell for the superclass. This is what we're doing here:</p>
<p>``` objc
- (void)_setCellClass {</p>
<pre><code>Class customClass = [CCFBrowserTextFieldCell class];
// since we are switching the isa pointer, we need to guarantee that the class layout in memory is the same
NSAssert(class_getInstanceSize(customClass) == class_getInstanceSize(class_getSuperclass(customClass)), @"Incompatible class assignment");
// switch classes if we are not already switched
NSCell *cell = [self cell];
if( ![cell isKindOfClass:[CCFBrowserTextFieldCell class]] ) {
object_setClass(cell, customClass);
}
</code></pre>
<p>}</p>
<p>```</p>
<p>This private method makes use of our access to the Objective-C runtime. In the assertion, we make sure that the instance size of our custom cell is the same as <code>NSTextFieldCell</code>. This is just a protection against us screwing something up badly when we switch classes. Next we do the switch using <code>object_setClass()</code>.</p>
<p>Enough of the swizzly stuff. Let's look add some of the subtleties that we could have easily forgotten, like the cursor. Over text fields, we are accustomed to seeing the I beam cursor. But over a button, it looks silly. So we override <code>resetCursorRect</code>. The documentation succinctly says what it does: "Overridden by subclasses to define their default cursor rectangles." So, we just add the two rects with the correct cursors to the two parts of our field using <code>addCursorRect:cursor:</code>.</p>
<p>That's about it for the internals of <code>CCFBrowserTextField</code>; hopefully you can put it to use in your projects.</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Not window dressing]]></title>
<link href="http://cocoa-factory.github.com/blog/2012/09/19/not-window-dressing/"/>
<updated>2012-09-19T10:00:00-05:00</updated>
<id>http://cocoa-factory.github.com/blog/2012/09/19/not-window-dressing</id>
<content type="html"><![CDATA[<p>After 3 years of full-time development on iOS and Mac (and many years of dabbling), I've had the chance to write and read a lot of code. The exponential growth of these platforms has been amazing; but it has brought a number of challenges. One of the challenges has been how to bring developers new to Objective-C and Cocoa to the community in a way that helps us all write better code. Code that's readable and code that us mortals can maintain long after it was written.</p>
<h2>Broken windows</h2>
<p>The broken windows theory is a construct in the social sciences that deals with the establishment of norms around vandalism and other criminal behavior. The idea is that communities that fail to care for their physical appearance are subject to more decline as the outward appearances signal an area of low value.</p>
<p>James Wilson and George Kelling, in the article "Broken Windows" (The Atlantic Monthly, March 1982) described this example:</p>
<blockquote><blockquote><p><em>"Consider a building with a few broken windows. If the windows are not repaired, the tendency is for vandals to break a few more windows. Eventually, they may even break into the building, and if it's unoccupied, perhaps become squatters or light fires inside. Or consider a sidewalk. Some litter accumulates. Soon, more litter accumulates. Eventually, people even start leaving bags of trash from take-out restaurants there or breaking into cars."</em></p></blockquote></blockquote>
<p>Does code suffer from the same social phenomena? Who knows - but what developers do is a craft. And it is a craft that contributes to a body of work, whether formalized in a discrete project or whether implicitly in the way we post code on Stack Overflow. Ultimately, code finds itself in a body of literature that sets the standards for the community. As craftspeople, we have a responsibility to the community to set standards that encourage clear thinking.</p>
<h2>Foolish thoughts</h2>
<p>The famous British author George Orwell wrote an essay entitled "Politics and the English Language" in 1946. In this brief work, he describes several problems with the use of his native language and how it obscures meaning. But the most powerful hypothesis that he puts forward is that the sloppy use of language leads to flawed reasoning:</p>
<blockquote><blockquote><p><em>"Now, it is clear that the decline of a language must ultimately have political and economic causes: it is not due simply to the bad influence of this or that individual writer. But an effect can become a cause, reinforcing the original cause and producing the same effect in an intensified form, and so on indefinitely. A man may take to drink because he feels himself to be a failure, and then fail all the more completely because he drinks. It is rather the same thing that is happening to the English language. It becomes ugly and inaccurate because our thoughts are foolish, but the slovenliness of our language makes it easier for us to have foolish thoughts."</em> (George Orwell, "Politics and the English Language", Horizon, April 1946)</p></blockquote></blockquote>
<p>Code follows this same pattern. The manner in which we write code both reflects the way we think about a problem <strong>and</strong> in turn, it causes us to to frame our thoughts about the problem in the context of the solution we've crafted. Cause and effect exert a bidirectional influence on each other. It's clear that poor use of our language, the formal language of Objective-C, may cause us to think about the problems we're solving in faulty ways.</p>
<p>At Cocoa Factory, we love to code and we love to code well. So, we're taking the next few weeks to describe our approach to readable, maintainable, functional, and beautiful code.</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[NSArrayController thread safety]]></title>
<link href="http://cocoa-factory.github.com/blog/2012/09/12/nsarraycontroller-thread-safety/"/>
<updated>2012-09-12T03:18:00-05:00</updated>
<id>http://cocoa-factory.github.com/blog/2012/09/12/nsarraycontroller-thread-safety</id>
<content type="html"><![CDATA[<p><code>NSArrayController</code> is a work-horse of Cocoa bindings. We recently ran into a problem with thread-safety which is not mentioned <a href="https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSArrayController_Class/Reference/Reference.html">in the documentation</a>.</p>
<p>Consider this piece of code that downloads some resources and populates an <code>NSArrayController</code>:</p>
<p>``` objc</p>
<pre><code>- (void)downloadUsers {
CCFUserListDownloader *downloader = [[CCFUserListDownloader alloc] initWithSessionID:self.sessionID];
[downloader downloadWithCompletionBlock:^(CCFAPIStatus status,NSArray *remoteObjects) {
[remoteObjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
CCFRemoteUser *user = [[CCFRemoteUser alloc] initWithDictionary:obj];
if( user )
[[self userArrayController] addObject:user];
}];
[[self userArrayController] removeObjectAtArrangedObjectIndex:0];
}];
</code></pre>
<p>```</p>
<p>Occasionally, we would get crashes on startup traced to this method. The problem is that our <code>userArrayController</code> has multiple bindings to the UI. Since <code>downloadUsers</code> was being executed on a background queue, its completion block was executed on the same queue. When we add a <code>user</code> object to the array controller on a queue other than main, we would occasionally crash. The solution is simple, just wrap the <code>addObject</code> call in an asynchronous dispatch to the main queue:</p>
<p>``` objc</p>
<pre><code>dispatch_async(dispatch_get_main_queue(), ^{
[[self userArrayController] addObject:user];
});
</code></pre>
<p><code>``
So, don't add objects to instances of</code>NSArrayController` on a background queue.</p>
]]></content>
</entry>
</feed>