Cocoa学习

我的Cocoa/Cocoa Touch学习笔记

关于多行NSString的高度

有时候,我们需要动态地得到一个多行字符串的高度。比如,在TableView中,如果我们允许UILabel/NSTextField多行显示,而TableView的Delegate就需要知道Cell的高度,这时,我们需要根据TableView呈现的内容,动态地计算多行字符串的高度,以确定Cell的高度。

这样的需求,在UIKit中实现起来相对比较直观。我们就先看看UIKit中的情况。

NSStringUIStringDrawing这个Catagory中,有一个非常方便调用的方法:- sizeWithFont:forWidth:lineBreakMode:。看下面的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    ...
    cell.textLabel.lineBreakMode = UILineBreakModeWordWrap; //设置换行模式
    cell.textLabel.numberOfLines = 0; //允许多行
    ...
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    ...
    NSString cellText = ...;
    CGSize maximumSize = CGSizeMake(280., 9999.); //指定一个宽度为实际宽度,高度为极大指的尺寸
    CGSize labelSize = [cellText sizeWithFont:[UIFont systemFontOfSize:14.] constrainedToSize:maximumSize lineBreakMode:UILineBreakModeWordWrap]; //调用方法,获取字符串的尺寸

    return labelSize.height + SOME_SPECE_FOR_OTHER_CONTROL; //返回字符串的高度,加上一些自由空间的高度或其他空间所占的高度。
}

遗憾的是,AppKit里没有对应于上述调用的简便方法。这时候,苹果的文档就发挥作用了。在Text Layout Programming Guide中有一段示例代码,展示了如何使用NSString以及一些其他的类,来实现类似的功能。下面是我自己用在NSTableView中的的一段代码,照抄了文档中的示例代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row {
    NSString *statusText = [[_statusMessages objectAtIndex:row] text];
    return [self heightForStringDrawing:statusText font:[NSFont systemFontOfSize:12] width:tableView.frame.size.width] + 8;
}

//苹果官方文档中的示例方法
- (CGFloat)heightForStringDrawing:(NSString *)myString font:(NSFont *)myFont width:(CGFloat)myWidth {
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithString:myString];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize: NSMakeSize(myWidth, FLT_MAX)];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];

    [layoutManager addTextContainer:textContainer];
    [textStorage addLayoutManager:layoutManager];
    [textStorage addAttribute:NSFontAttributeName value:myFont range:NSMakeRange(0, [textStorage length])];
    [textContainer setLineFragmentPadding:0.0];

    [layoutManager glyphRangeForTextContainer:textContainer];
    return [layoutManager usedRectForTextContainer:textContainer].size.height;
}

上述代码中涉及了NSTextStorageNSTextContainerNSLayoutManager等多个比较复杂的方法,我对这些类也不是很熟悉,因此我就不详细注释了,有兴趣的同学可以查阅这些类的文档。你也可以将上述方法稍作修改,自己写一个NSString的Catagory,方便以后使用。你甚至可以基于上述方法,写一个/一组对应于NSString(UIStringDrawing)里计算字符串尺寸的快捷方法。

还是那个老梗,任何时候,RTFM总能给人带来惊喜和收获。

(全文完)

Block的Retain Cycle的解决方法

一个使用Block语法的实例变量,在引用另一个实例变量的时候,经常会引起retain cycle。这个问题在使用ASIHTTPRequest的block语法的时候会时不时的碰到。这个问题困扰了我这个小白很久。终于有一天,在Advanced Mac OS X Programming上,看到了这个问题的解决方案。

先用代码描述一下症状:

检测iOS的网络可用性并打开网络设置

今天接到个需求,要求程序能够检测网络可用性,并在没有网络可用的时候能够弹出对话框,并允许用户点击按钮打开网络设置。

这个问题,我首先想到的就是用一个方法检测网络可用性,然后用UIApplicationopenURL方法打开某个特殊URL,就可以进入设置了。于是,我迅速地建了个测试项目,写了个简单的实现,如下:

模拟低速网络,测试iOS程序的表现

因为没有考虑程序在低速网络下的表现,昨天在测试公司的一个程序的时候出了点丑,于是痛定思痛,要解决模拟低速网络连接的问题。遗憾的是iOS Simulator并没有提供模拟低速网络的功能。

经过一番STFW之后,发现原来苹果从Xcode 4.1开始提供了一款叫做“Network Link Connector”的工具。但是如今我已经升级到Xcode 4.3.2,用SpotLight搜索了一番之后也没有发现系统里已经安装了这样的一个工具。

又经过一番搜索,我发现原来“Network Link Connector”是一个prefPane程序,从Xcode 4.3开始已经不再默认包含在程序里了,而是需要从developer.apple.com/download上单独下载,它位于“Hardware IO Tools”的一个包中。

跳动Dock图标的实现

OS X程序,在执行后台任务的时候,有时需要通过跳动Dock图标来提示用户进行操作或某项操作已经执行完成。方法很简单,NSApplication有一个-requestUserAttention:的方法可以跳动Dock图标。

下面是实例代码:

1
2
3
4
5
6
7
8
//跳动一次
- (void)bounceOnce:(id)sender {
    [NSApp requestUserAttention:NSInformationalRequest];
}
//总是跳动,直到用户干预为止
- (void)bounceAlways:(id)sender {
    [NSApp requestUserAttention:NSCriticalRequest];
}

FYI: NSApp是一个指向当前运行的程序实例的全局变量,等同于调用:[NSApplication sharedApplication]

示例代码已经上传到github,有兴趣的可以看看。不过记得在测试示例程序的时候,点击按钮后就让程序进入后台运行。5秒钟后就能看到效果了。