iOS

一处重复代码的改进

在APP中列表是一种比较常见的数据展示方式,当有数据时,就显示数据;如果没有数据,一般不会显示一个空白页面,而是在空白页面上加一些提示信息,比如像下面这样

no_message.png

不同的APP会有不同的设计,但不管是什么样的设计,它在整个APP内部应该是一致的,要变也只是文字或图片稍有不同。

##现状
因为我们目前的项目还算比较庞大,所以这种列表无数据的情况出现了20多次,所以类似下面的代码出现了就有20多次。为什么说类似,因为是由不同的人写的,逻辑也是差不多,但真的各不相同,有的封装成一个方法,比如:setNoMessageView,有的直接写在viewDidLoad里面……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)setNoMessageView
{
self.noInfoView = [[UIView alloc] initWithFrame:CGRectMake(0, 45, SCREEN_WIDTH, SCREEN_HEIGHT)];
self.noInfoView.backgroundColor = [UIColor clearColor];
[self.view addSubview:self.noInfoView];

UIImageView *carImageView = [[UIImageView alloc] initWithFrame:CGRectMake((SCREEN_WIDTH-120)/2, 60, 120, 86)];
[carImageView setImage:[UIImage imageNamed:@"no_message.png"]];
[self.noInfoView addSubview:carImageView];

UILabel *noInfoLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 160, SCREEN_WIDTH, 20)];
noInfoLabel.textAlignment = NSTextAlignmentCenter;
noInfoLabel.textColor = LCRGBColor(211, 211, 211);
noInfoLabel.text = NSLocalizedString(@"Dear, no information", nil);
noInfoLabel.backgroundColor = [UIColor clearColor];
noInfoLabel.font = [LCFont systemFontOfSize:20];
[self.noInfoView addSubview:noInfoLabel];
}

先不考虑重复的问题,只是孤立的看上述代码,它也有一些问题:

  • self.noInfoView的frame应该视根据上下文获得的,而不是和屏幕大小绑定,而且yOffset是45也是不对的。
  • carImageView的frame是固定大小的,而图片有可能变。

第一个解决办法

因为创建noInfoView的代码基本差不多,我们可以封装出一个Util方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+ (UIView*)createNoMessageViewWithFrame:(CGRect)frame image:(UIImage*)image text:(NSString*)text
{
UIView* noMessageView = [[UIView alloc] initWithFrame:frame];
noMessageView.backgroundColor = [UIColor clearColor];

UIImageView *carImageView = [[UIImageView alloc] initWithFrame:CGRectMake((frame.size.width-image.size.width)/2, 60, image.size.width, image.size.height)];
[carImageView setImage:image];
[noMessageView addSubview:carImageView];

UILabel *noInfoLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 160, frame.size.width, 20)];
noInfoLabel.textAlignment = NSTextAlignmentCenter;
noInfoLabel.textColor = LCRGBColor(211, 211, 211);
noInfoLabel.text = text;
noInfoLabel.backgroundColor = [UIColor clearColor];
noInfoLabel.font = [LCFont systemFontOfSize:20];
[noMessageView addSubview:noInfoLabel];

return noMessageView;
}

调用:

1
2
3
4
5
6
7
8
- (void)setNoMessageView
{
CGRect rect = self.shopListView.frame;
UIImage* image = [UIImage imageNamed:@"no_message.png"];
NSString* text = NSLocalizedString(@"Dear, no information", nil);
self.noInfoView = [HJUIUtil createNoMessageViewWithFrame:rect image:image text:text];
[self.view addSubview:self.noInfoView];
}

这样改看起来好多了,把共性封装,把差异作为接口留出。然后其他地方原本要写20行代码,现在只要写5行,而且多个地方调用的代码不会有太大的出入,便于阅读和理解。

第二个解决办法

上面的办法已经不错了,不过除了写5行代码之外,还给ViewController增加了一个属性:noInfoView。现在仔细想一下noInfoView出现的原因,是因为TableView没有内容显示的时候,noInfoView才显示出来,否则就隐藏。可见,这个noInfoView和TableView是紧密联系的,我们可以从UITableView的状态得出noInfoView的状态。那为什么不把它绑定到UITableView上呢?好,那就给UITableView增加一个属性,叫做emptyView。

给一个系统的类增加属性不是那么的简单,但是只要看过SVPullToRefresh的代码,就知道怎么做了。
首先,给UITableView增加一个Category。

1
2
3
4
5
6
7
@interface UITableView(EmptyView)

@property (nonatomic, strong, readonly) UIView *emptyView;

-(void)addEmptyViewWithImageName:(NSString*)imageName title:(NSString*)title;

@end

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
static char UITableViewEmptyView;

@implementation UITableView(EmptyView)

@dynamic emptyView;

- (UIView *)emptyView
{
return objc_getAssociatedObject(self, &UITableViewEmptyView);
}

- (void)setEmptyView:(UIView *)emptyView
{
[self willChangeValueForKey:@"HJEmptyView"];
objc_setAssociatedObject(self, &UITableViewEmptyView,
emptyView,
OBJC_ASSOCIATION_ASSIGN);
[self didChangeValueForKey:@"HJEmptyView"];
}


-(void)addEmptyViewWithImageName:(NSString*)imageName title:(NSString*)title
{
if (!self.emptyView)
{
CGRect frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
UIImage* image = [UIImage imageNamed:imageName];
NSString* text = title;

UIView* noMessageView = [[UIView alloc] initWithFrame:frame];
noMessageView.backgroundColor = [UIColor clearColor];

UIImageView *carImageView = [[UIImageView alloc] initWithFrame:CGRectMake((frame.size.width-image.size.width)/2, 60, image.size.width, image.size.height)];
[carImageView setImage:image];
[noMessageView addSubview:carImageView];

UILabel *noInfoLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 160, frame.size.width, 20)];
noInfoLabel.textAlignment = NSTextAlignmentCenter;
noInfoLabel.textColor = LCRGBColor(211, 211, 211);
noInfoLabel.text = text;
noInfoLabel.backgroundColor = [UIColor clearColor];
noInfoLabel.font = [LCFont systemFontOfSize:20];
[noMessageView addSubview:noInfoLabel];

[self addSubview:noMessageView];

self.emptyView = noMessageView;
}

}

@end

然后外部调用就很简单了,没有额外的属性,而且一句话就搞定。

1
[self.shopListView.shopListTableView addEmptyViewWithImageName:@"no_message.png" title:@"Dear, no information"];