我项目中Layout模块的解析

context

从上次确定使用抽象工厂模式, 到最终项目实现,采用了建造者模式. 因为如果使用抽象工厂模式,将每个模块的layout抽象, 会导致产品体系非常复杂, 难以使用和维护.

结构图

说明

GUILayout 是一个由各个模块布局模型组合而成的复杂对象 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@interface GUILayout : NSObject


@property(nonatomic,strong)GUIGeneralLayoutModel *generalLayout;

@property(nonatomic,strong)GUILoginLayoutModel *loginLayout;

@property(nonatomic,strong)GUIDiaryLayoutModel *diaryLayout;

@property(nonatomic,strong)GUIPregnantManagerLayoutModel * pregnantLayout;

@property(nonatomic,strong)GUIInfoProviewLayoutModel *infoPreviewLayout;

@property(nonatomic,strong)GUIPediaLayoutModel *peidaLayout;

@property(nonatomic,strong)GUIAntenatalCareLayoutModel *anteCareLayout;

@property(nonatomic,strong)GUIExpertListLayoutModel * expertLayout;

@end

GUIDiaryLayoutModel 日记模块的参数模型,内部包含了图标大小,间距等尺寸信息.其余的XXXLayoutModel均类似GUIDiaryLayoutModel ,只是内部参数根据实际UI控件有差异.其参数参考UI给出的UI文档.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@interface GUIDiaryLayoutModel : NSObject

@property ( assign , nonatomic )CGFloat topViewHeight;
@property ( assign , nonatomic )CGFloat weekLabelTopMargin;
@property ( assign , nonatomic )CGFloat weekLabelLeftMargin;
@property ( assign , nonatomic )CGFloat weekLabelRightMargin;
@property ( assign , nonatomic )CGFloat weekLabelBottomMargin;
@property ( assign , nonatomic )CGFloat weekLabelNumberFontSize;
@property ( assign , nonatomic )CGFloat weekLabelTextFontSize;
@property ( assign , nonatomic )CGFloat bottomBarHeight;
@property ( assign , nonatomic )CGFloat addButtonLeftMargin;
@property ( assign , nonatomic )CGFloat addButtonWH;
@property ( assign , nonatomic )CGFloat topButtonWH;
@property ( assign , nonatomic )CGFloat topButtonLeftRightMargin;
@property ( assign , nonatomic )CGFloat timeLabelFontSize;
@property ( assign , nonatomic )CGFloat timeLabelRightMargin;
@property ( assign , nonatomic )CGFloat cellIconLeftMargin;
@property ( assign , nonatomic )CGFloat cellIconRightMargin;
@property ( assign , nonatomic )CGFloat cellCellDetailFontSize;
@property ( assign , nonatomic )CGFloat cellCellHeight;
@property ( assign , nonatomic )CGFloat cellWeekTextFontSize;
@property ( assign , nonatomic )CGFloat cellWeekTextRightMargin;

@end

LayoutBuilder 建造者父类,拥有一个 GUILayout 对象,并规定了建造不同模块参数的方法.

.h文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import <Foundation/Foundation.h>
#import "GUILayout.h"


@interface LayoutBuilder : NSObject

@property(nonatomic,strong)GUILayout * layout;

-(LayoutBuilder*)buildLayout;
-(LayoutBuilder*)configLoginLayout;
-(LayoutBuilder*)configDiaryLayout;
-(LayoutBuilder*)configPregnantMangerLayout;
-(LayoutBuilder*)configPediaLayout;
-(LayoutBuilder*)configAnteCareLayout;
-(LayoutBuilder*)configExpertListLayout;
-(LayoutBuilder*)configInfoPreviewLayout;

@end

.m 文件

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
#import "LayoutBuilder.h"


@interface LayoutBuilder()

@end

@implementation LayoutBuilder


-(LayoutBuilder*)buildLayout
{
GUILayout * layout = [GUILayout new];
_layout = layout;
return self;
}

-(LayoutBuilder*)configLoginLayout{

return self;
}

-(LayoutBuilder*)configDiaryLayout{

return self;
}

-(LayoutBuilder*)configPregnantMangerLayout{

return self;
}

-(LayoutBuilder*)configPediaLayout{
return self;
}
-(LayoutBuilder*)configAnteCareLayout{
return self;
}
-(LayoutBuilder*)configExpertListLayout{
return self;
}
-(LayoutBuilder*)configInfoPreviewLayout{
return self;
}

IP4LayoutBuilder , IP5LayoutBuilder ,IP6LayoutBuilder 这些类分别重写父类的建造方法,给不同的XXXLayoutModel赋不同值.

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

#import "IP6LayoutBuilder.h"


@implementation IP6LayoutBuilder

-(LayoutBuilder*)configLoginLayout{

GUILoginLayoutModel * model = [GUILoginLayoutModel new];
model.iconSize = CGSizeMake(16, 16);
model.leftViewInsets = 12;
model.welcomePageButtonWidth = 172;
model.welcomepageButtonTitleFontSize = 17;
model.welcomePageLeftMargin = 10;
model.welcomePageBottomMargin = 6;
model.welcomePageMiddleMargin = 10;
model.toSignUpFontSize = 12;
model.noAccoutStringColor = GUIColorFromRGB(0xffbebe);
model.loginButtonWidth = 295;
model.tableTopMargin = 20;
model.submitButtonWidth = 172;
model.submitTitleHeight = 17;
model.signupLeftIconLeftRightMargin = 12;
model.signupLeftIconTopBottomMargin = 17;
model.textFieldTextLeftMargin =10;
self.layout.loginLayout = model;
return self;
}

-(LayoutBuilder*)configPregnantMangerLayout{

GUIPregnantManagerLayoutModel * model = [GUIPregnantManagerLayoutModel new];

model.topViewHeight = 242;
model.userIconTopMargin = 25;
model.userIconWidth = 71;
model.userIconHeight = 71;
model.usernameTopMargin = 8;
model.usernameFontSize = 17;
model.pregnantDateTopMargin = 35;
model.pregnantDateRightMargin = 35;
model.toBirthLabelFontSize = 12;
model.toBirthLabelLeftMargin = 15;
model.toBirthLabelDescFontSize = 12;
model.toBirthLabelNumberFontSize = 17;
model.editButtonTopMargin = 15;
model.editButtonRightMargin = 15;
model.editButtonBottomMargin = 15;
model.editButtonHeight = 30;
model.editButtonWidth = 30;
model.userIconBorderWidth = 2;
model.pregnantDateLeftMargin = 40;
model.pregnantDateFontSize = 34;

self.layout.pregnantLayout = model;
return self;
}

-(LayoutBuilder*)configDiaryLayout{

GUIDiaryLayoutModel * model = [GUIDiaryLayoutModel new];
model.topViewHeight = 181;
model.weekLabelTopMargin = 38;
model.weekLabelLeftMargin = 55;
model.weekLabelRightMargin =55;
model.weekLabelBottomMargin =42;
model.weekLabelNumberFontSize =52;
model.weekLabelTextFontSize =26;
model.bottomBarHeight =44;
model.addButtonLeftMargin = 15;
model.timeLabelFontSize = 12;
model.timeLabelRightMargin = 15;
model.cellIconLeftMargin = 11;
model.cellIconRightMargin = 17;
model.cellCellDetailFontSize = 12;
model.cellCellHeight = 55;
model.cellWeekTextFontSize = 12;
model.cellWeekTextRightMargin = 15;
model.addButtonWH = 30;
model.topButtonWH = 30;
model.topButtonLeftRightMargin = 60;

self.layout.diaryLayout = model;
return self;
}

-(LayoutBuilder*)configPediaLayout{

GUIPediaLayoutModel *model = [GUIPediaLayoutModel new];
model.iconTopMargin = 25;
model.iconBorderWidth = 3;
model.iconViewWH = 100;
model.weekLabelNormalFontSize = 12;
model.weekLabelNumberFontSize = 28;
model.fruitIconWH = 26;
model.iconDescFontSize = 12;
model.babyLengthNormalFontSize = 12;
model.babyLengthValueFontSize = 17;
model.babyLengthBottomMargin = 15;
model.controllButtonWidth = 40;
model.controllBUttonHeight = 46;
model.timeLineWidth = 30;
model.cellContentViewLeftRightMargin = 10;


self.layout.peidaLayout = model;
return self;
}
-(LayoutBuilder*)configAnteCareLayout{
GUIAntenatalCareLayoutModel * model = [GUIAntenatalCareLayoutModel new];

model.contollTopMargin= 10;
model.controllleftMargin=80;
model.controlButtonSize= 20;
model.weekIconTopMargin= 25;
model.weekIconBottomMargin= 25;
model.weekIconTitleFontSize=12;
model.weekIconWeekFontSize=12;
model.cellLeftMargin=10;
model.redDotRightMargin=15;
model.cellHeight=44;
model.tagViewMiddleMargin= 10;
model.tagViewHeight=15;
model.tagViewFontSize = 12;
model.tagViewTopMargin= 44;
model.cellLineLeftMargin= 10;
model.cellLineRightMargin= 10;
model.cellNoteBottomMargin= 15;
model.cellNoteFontSize= 15;
model.cellNoteLeftMargin = 42;

self.layout.anteCareLayout = model;
return self;
}

-(LayoutBuilder*)configExpertListLayout{

GUIExpertListLayoutModel * model = [GUIExpertListLayoutModel new];
model.iconWH = 50;
model.iconLeftMargin = 14;
model.iconRightMargin = 14;
model.titleFontSize = 17;
model.detailTextTopMargin = 10;
model.cellHeight = 60;
model.detailFontSize = 12;
self.layout.expertLayout = model;
return self;
}

-(LayoutBuilder*)configInfoPreviewLayout{

GUIInfoProviewLayoutModel * model = [GUIInfoProviewLayoutModel new];
model.tableTopMargin = 20;
model.titleLeftMargin = 15;
model.iconCellHeight = 54;
model.iconWH = 41;
model.appendViewRrightMargin = 15;
model.tableHeaderHeight = 20;

self.layout.infoPreviewLayout = model;

return self;
}

@end

LayoutDirector 规定了LayoutBuilder要执行哪些方法,以及方法的执行顺序,确保最终的GUILayout对象能被顺利初始化.

.h文件

1
2
3
4
5
6
7
8
9
10
11
#import <Foundation/Foundation.h>
#import "LayoutBuilder.h"
#import "GUILayout.h"

@interface LayoutDirector : NSObject

@property(nonatomic,strong)LayoutBuilder * builder;

-(GUILayout*)layout;

@end

.m 文件

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

#import "LayoutDirector.h"
#import "IP4LayoutBuilder.h"
#import "IP6LayoutBuilder.h"
#import "IP5LayoutBuilder.h"
#import "IP6PLayoutBuilder.h"

#define kisiPhone4 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 960), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone5 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 1136), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone6 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(750, 1334), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone6Plus ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242,2208), [[UIScreen mainScreen] currentMode].size) : NO)



@implementation LayoutDirector

-(LayoutBuilder *)builder{

if (!_builder) {

if (kisiPhone4) {
_builder = [IP4LayoutBuilder new];
}else if(kisiPhone5){
_builder = [IP5LayoutBuilder new];
}else if(kisiPhone6){
_builder = [IP6LayoutBuilder new];
}else if(kisiPhone6Plus){
_builder = [IP6PLayoutBuilder new];
}

}
return _builder;
}

-(GUILayout*)layout{

[self.builder buildLayout];
[self.builder configLoginLayout];
[self.builder configDiaryLayout];
[self.builder configPregnantMangerLayout];
[self.builder configInfoPreviewLayout];
[self.builder configExpertListLayout];
[self.builder configPediaLayout];
[self.builder configAnteCareLayout];
return [self.builder layout];
}
@end

因为这个模块的判断方式是屏幕的尺寸,故结合简单工厂模式,将原本属于调用者的判断逻辑下放到了 LayoutDiretor中.

最后,为了调用者使用方便,在整个布局模块的上层包装了一个 GUILayoutManager ,并在其内部为不同模块定义了宏.方便调用

.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import <UIKit/UIKit.h>
#import "GUILayout.h"
#import "LayoutDirector.h"

#define kGenralLayout [GUILayoutManager layout].generalLayout
#define kLoginLayout [GUILayoutManager layout].loginLayout
#define kPregnantLayout [GUILayoutManager layout].pregnantLayout
#define kInfoOverviewLayout [GUILayoutManager layout].infoPreviewLayout
#define kExpertLayout [GUILayoutManager layout].expertLayout
#define kDiaryLayout [GUILayoutManager layout].diaryLayout
#define kPediaLayout [GUILayoutManager layout].peidaLayout
#define kAnteLayout [GUILayoutManager layout].anteCareLayout

@interface GUILayoutManager : NSObject

@property(nonatomic,strong)GUILayout * layout;

+(GUILayout*)layout;

+(instancetype) sharedLayoutManager;

@end

.m

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

#import "GUILayoutManager.h"

@implementation GUILayoutManager


-(GUILayout *)layout{
if (!_layout) {
LayoutDirector * director = [LayoutDirector new];
GUILayout *layout = [director layout];
_layout = layout;
}
return _layout;
}

+(GUILayout*)layout{

GUILayoutManager * manager = [GUILayoutManager new];
return manager.layout;

}



+(instancetype)allocWithZone:(struct _NSZone *)zone{

static dispatch_once_t onceToken;
static id instance = nil;
dispatch_once(&onceToken, ^{
instance = [super allocWithZone:zone];
});
return instance;

}

+(instancetype)sharedLayoutManager{

static dispatch_once_t onecToken;
static id instance;
dispatch_once(&onecToken, ^{
instance = [[self alloc] init];
});
return instance;

}




@end

便利

如果UI出了新的尺寸,只需要在特定屏幕的的 XXXLayoutBuilder的对应方法中修改参数值即可, 项目中其它文件均无需修改.
例如:
要加大iPhone6 下 ,百科模块的用户头像大小.
IP6LayoutBuilder.m-> configPediaLayout ->configPediaLayout方法中,将model.iconViewWH修改即可.