Cocoa学习

我的Cocoa/Cocoa Touch学习笔记

强制Label(NSTextField)重绘

今天在码代码的时候遇到了标签在修改了内容后没有重绘的问题,试了n种办法,查了老半天,结果终于找到了比较好的解决方法。

首先,要说明,下面这样的方法是不能让Label(NSTextField)立刻重绘的:

1
2
[someLabel setStringValue:@"Some new text"];
[someLabel setNeedsDisplay:YES];

在Cocoa程序里使用Gravatar

Gravatar是一个非常流行的头像服务,可以根据用户的Email获取头像。下面一段简单的代码展示了如何在Cocoa里使用Gravatar。简单的界面测试:

计算字符串的MD5哈希

字符串或数据的MD5哈希值是非常常用的。在Cocoa里很容易实现。因为这类方法非常好用,因此可以做一个NSStringNSData的Catagory。如下:

NSString:

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
// NSString+MD5.h

#import <foundation/foundation.h>
@interface NSString (NSString_MD5)
- (NSString *)md5;
@end

// NSString+MD5.m

#import "NSString+MD5.h"
#import <commoncrypto/commondigest.h>
@implementation NSString (NSString_MD5)
- (NSString *)md5 {
    const char *cStr = [self UTF8String];
    unsigned char result[16];
    CC_MD5(cStr, (unsigned int)strlen(cStr), result);
    return [NSString stringWithFormat:
            @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
            result[0], result[1], result[2], result[3],
            result[4], result[5], result[6], result[7],
            result[8], result[9], result[10], result[11],
            result[12], result[13], result[14], result[15]
            ];
}
@end

【更新】把程序窗口放在屏幕中央

在写程序的时候,我们往往需要把窗口放在屏幕中央。方法其实很简单,代码如下:

1
2
3
4
5
6
7
8
- (void)awakeFromNib {
    CGRect windowRect = [self.window frame];
    CGRect screenRect = [[NSScreen mainScreen] frame];
    CGPoint newOrigin;
    newOrigin.x = (screenRect.size.width / 2) - (windowRect.size.width / 2);
    newOrigin.y = (screenRect.size.height / 2) + (windowRect.size.height / 2);
    [self.window setFrameOrigin:newOrigin];
}

如果程序只有一个窗口,那么这段代码也可以放在AppDelegate-applicationDidFinishLaunching:方法里。如果放在-applicationDidFinishLaunching:里,很可能会引起窗口“闪”到屏幕中央(除非程序特别小,小到几乎无需初始化),因此,放在-awakeFromNib里才是正确的做法。

2011-08-15更新:

事实上NSWindow已经有了一个专门的方法来处理窗口居中的:- (void)center

1
2
3
- (void)awakeFromNib {
    [self.window center];
}

不仔细看文档害死人啊!

(全文完)

动态改变窗口大小

根据SubView的大小动态的改变窗口大小的操作在Cocoa编程里很常用。记录方法如下。下面的代码假定通过一个Box容器,分别容纳不同大小的SubView,在切换SubView的时候,窗口的大小适应容器大小的变化。SubView的容器可以是任何NSView。这个代码通常放在Controller里。

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
// 通过某个View获得需要改变大小的窗口
NSWindow *window = [self.box window];
// 获取新View
NSView *view = [viewController view];
// 当前View的大小
NSSize currentSize = [[self.box contentView] frame].size;
// 新View的大小
NSSize newSize = [view frame].size;
// 新旧View的宽度和高度差
CGFloat deltaWidth = newSize.width - currentSize.width;
CGFloat deltaHeight = newSize.height - currentSize.height;
// 获取窗口大小
NSRect windowFrame = [window frame];
// 加上SubView高度和宽度的变化值
windowFrame.size.height += deltaHeight;
windowFrame.size.width += deltaWidth;
// 为了保持窗口左上角位置不变,需要重设窗口框的绘制起点的y值
// 之所以用 -= 是因为Cocoa的绘图起点是左下角
// 起点的x值无需改变。
windowFrame.origin.y -= deltaHeight;
// 移除旧View
[self.box setContentView:nil];
// 改变窗口大小,使用动画
[window setFrame:windowFrame display:YES animate:YES];
// 加入新View
[self.box setContentView:view];

(全文完)

用NSOpenPanel打开文件

NSOpenPanelNSSavePanel从Mac OS X 10.6开始把很多方法都标记为了Deprecated,文档里也很详尽的给出了替代方法。

比如,要打开一个选文件的ModalView,以前是用NSOpenPanel– beginSheetForDirectory:file:types:modalForWindow:modalDelegate:didEndSelector:contextInfo:方法。现在,必须用新的方法替代。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-(IBAction)showOpenPanel:(id)sender {
    NSOpenPanel *panel = [NSOpenPanel openPanel];
    [panel setDirectory:NSHomeDirectory()]; // Set panel's default directory.
    [panel setAllowedFileTypes:[NSImage imageFileTypes]]; // Set what kind of file to select.
    // More panel configure code.
    [panel beginSheetModalForWindow:[myView window] completionHandler: (^(NSInteger result){
        if(result == NSOKButton) {
            NSArray *fileURLs = [panel URLs];
            NSImage *image = [[NSImage alloc] initWithContentsOfURL:
                              [fileURLs objectAtIndex:0]];
            NSLog(@"%@", [fileURLs objectAtIndex:0]);
            // Deal with the image.
            [image release];
        }
    })];
}

用这个新方法利用了Objective-C 2.0的新特征:Block。在引入了Block之后,这个方法也就变成了多线程的了——这是Grand Central Dispatch的使用实例之一。

这段代码的调试输出如下(多线程):

1
2
3
4
5
6
7
2011-03-29 13:49:54.204 CustomView[50396:903] In app delegate: 1.000000
[Switching to process 50396 thread 0xad07]
[Switching to process 50396 thread 0x903]
[Switching to process 50396 thread 0x9633]
[Switching to process 50396 thread 0x903]
2011-03-29 13:50:04.528 CustomView[50396:903] x-marsedit-filelocal://localhost/Users/venj/Pictures/Old%20Photo/IMG_2443.JPG
Program ended with exit code: 0

详情参考ADC文档:在这里

Updated:另外,值得注意的是,NSOpenPanelNSSavePanel的子类。

(全文完)