Tag Archives: Mac

How to Make a Very Fast TableView on Mac with Variant Cell Height

I recently started my project to build my own Sina Weibo (a Chinese Twitter clone) app for the Mac. I started with NSTableView just like how I usually deal with UITableView on iOS programming, but I finally found that NSTableView is very complex and it’s completely different with UITableView, especially for custom cells and variant cell height. I have to use NSCell subclass instead of NSView subclass in NSTableView. Luckily, I then found an article Making Cocoa List Views Really Fast which talks about the performance issue about NSTableView. The author of the article wrote his own implementation of NSTableView, just like a UITableViewPXListView.

The benefit of using PXListView is that I can finally use NSViews as my cell instead of NSCells, so that you can construct your cells very easily just like how you make a custom UITableViewCell on iOS. However, I still have many issues when using this framework. Here is the thing:

My app is a Twitter-like app, so I need to show the Tweet and the Retweet in one cell, and the height of my cell is some paddings + Tweet NSTextView height + Retweet NSTextView height, which is based on the width of my PXListView, that is, when I changed the width of my PXListView, the height of each cell will be changed.

I then found the NS(Attributed)String+Geometrics category by Sheep Systems, which solved some of my problems. But it is way too slow because I have to calculate the height of NSTextView twice – in PXListView delegate listView:heightOfRow and my custom cell’s drawRect method.

After I looked closely through the category by Sheep Systems, I found that it used NSTextContainer, NSTextStorage, NSLayoutManager to calculate the height of the NSTextView with a fixed width. Every time the method was called, I have to alloc, init, and finally release them. It was just a waste of memory and time. I then suddenly found that all the three necessary classes to calculate the text height are just right in an NSTextView instance. So I rewrote the category to this:

@implementation NSTextView (Geometrics)

- (CGFloat)heightForWidth {

    if ([[self textStorage] length] > 0) {

		// Checking for empty string is necessary since Layout Manager will give the nominal
		// height of one line if length is 0.  Our API specifies 0.0 for an empty string.

		// NSLayoutManager is lazy, so we need the following kludge to force layout:
		[self.layoutManager glyphRangeForTextContainer:self.textContainer];

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

	return 0;
}

@end

It’s very clean now and have a great performance. Another thing is that I still have to call it 2 times when the cell is scrolling. To solve this, I wrote a TextViewHeightCache, which will cache the height of the given text with a fixed width. Next time PXListView asked for the height, the TextViewHeightCache first checked wether or not the height of the provided width is available in the cache. If we found it, just return the corresponding height, otherwise, recalculate it.

#import "NSTextView+Geometrics.h"

@implementation SNTextViewHeightCache

- (id)init {

	if ((self = [super init])) {
		textView = [[NSTextView alloc] init];
		textViewSizes = [[NSMutableDictionary alloc] init];
	}

	return self;
}

- (CGFloat)heightForText:(NSAttributedString *)text withWidth:(CGFloat)width {

	NSValue *sizeValue = [textViewSizes objectForKey:text];
	if (sizeValue) {
		NSSize size = [sizeValue sizeValue];
		if (size.width == width) {
			return size.height;
		}
	}

	[[textView textStorage] setAttributedString:text];
	[textView setFrameSize:CGSizeMake(width, 0)];

	CGFloat height = [textView heightForWidth];

	[textViewSizes setObject:[NSValue valueWithSize:CGSizeMake(width, height)] forKey:text];

	return height;
}

- (void)dealloc {

	[textView release];
	[textViewSizes release];

	[super dealloc];
}

@end

Now we have a very fast table view with variant cell height, completely without any knowledge of NSTableView.

终于不纠结

这两天啊, 这纠结的心情就没有停止过. 昨天中午去学校联通的营业厅去办网, 办的时候查了一下电话号码, 结果就直接给办了, 幸亏我那个是66的套餐, 要是其他的3G套餐貌似还得改资费, 那可就费了劲了. 于是办完了, 设备也领了, 我和李xx说我那宿舍电话到底能不能确定对, 因为刚搬宿舍, 我宿舍的电话确实不知道. 然后他就说他也不能确定, 于是我回宿舍查查看. 找同学借了一个201电话, 结果根本没声音, 结果我又去找他报修, 终于下午来人给修好了, 而且电话号码也是对的, 联通也发来短信说业务已经开通. 结果扣了我15块钱, 我就欠费了.

下午上完组件那课我就赶紧回宿舍试, 结果无论如何也不能上网! 但是能连接! 我ping DNS都没有反应, 绝望. 拿同学的Windows, 一点问题也没有, 拨号就上. 汗! 难道是OS X不能行? 于是找同学借了个Mac过来, 完全可以上! 证明只是我的系统有问题, 不是OS X的问题. 我就想啊想啊到底是什么问题. 我就突然想到了原来弄的VPN, 当浏览国内网站的时候, 默认不走VPN, 当浏览国外网站的时候, 就走VPN. 结果我把那个chnroutes的文件删了, 就可以上了! 结果我就又纠结了, 因为这个时候连上VPN很慢, 不知道怎么回事, 难道是跟我用PPPoE拨号有关? 这应该不能够. 试了两个VPN都不能行, 而且就算连上了, 也不能走分线, 绝望. 我突然就想到了去用SSH, 因为原来看过相关介绍, 就是一直没用过, 于是在Twitter上问了一下前几天我用TwitBird在图书馆搜来的一个我们系的大二同学. 结果刚才去买了那个SSH, 果然好用.

SSH我大概是这么用的, 先下Shimo, 设置好后, 用Chrome的Switchy插件. 虽然在Mac上我很不习惯Chrome这个浏览器. 不过木有办法… 然后去下了一个别人已经设置好的Pac文件, 这样就完全没有任何问题了. 所以现在只好是Safari做一般浏览用, 要是上需要FQ的网站, 就只好用Chrome. 还好新版的Dropbox支持Proxy, 所以Dropbox也没有任何问题现在.

还有我怀疑这回联通在学校可能把移动给打败了, 就冲着他那上网包什么的, 新生估计也会去选择联通. 而且原来宿舍区内只有移动可以进来, 联通每次都是外面的一个小亭子, 很是可怜. 这回竟然平起平坐, 而且学校里有一个联通一个移动的正式营业厅还. 真是不可思议.

这两天学校迎新, 于是一年两度开放的统计胡喷泉再度伴随着那些超级要命的音乐喷起来. 我还发现C, D还是E, F座后面竟然还有一个喷泉, 记得原来和同学去上辅修的时候总经过, 从来也不知道那是喷泉. 这回竟然开了! 真是不容易. 还有不得不赞叹宿舍区这边收拾的速度, 6号的时候还是一片废墟了, 8号的时候就什么都好了, 什么都好了…

昨天的基于组件的程序设计和今天的J2EE着实让我很头疼, 一向不喜欢Java, 实在木有办法. 哦对还有那个嵌入式, 我实在看不懂那堆电路图…