勤之时 - 表示层(一)

表示层:由UIKit Framework构成,也就是我们看到的视图,控制器,各种控件以及事件处理等内容。

首先来谈谈表示层的架构,继续推荐大神的iOS应用架构谈 view层的组织和调用方案

说下【勤之时】最后适用的要点:

以下内容摘抄自iOS应用架构谈 view层的组织和调用方案

  • 所有的属性都使用getter和setter
    不要在viewDidLoad里面初始化你的view然后再add,这样代码就很难看。在viewDidload里面只做addSubview的事情,然后在viewWillLayoutSubviews里面做布局的事情,最后在viewDidAppear里面做Notification的监听之类的事情。至于属性的初始化,则交给getter去做。 例如:
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
#pragma mark - Life Cycle

- (void)viewDidLoad {
[super viewDidLoad];

[self.view addSubview:self.taskScrollView];
[self.view addSubview:self.pageControl];
}

- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];

[self.taskScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
[self.taskScrollView setContentSize:CGSizeMake(CGRectGetWidth(_taskScrollView.frame) * self.taskIds.count, CGRectGetHeight(_taskScrollView.frame))];
}];

[self.pageControl mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_top).with.offset(30);
make.centerX.equalTo(self.view);
make.height.mas_equalTo(28);
make.width.mas_equalTo(ScreenWidth - 42 * 2);
}];
}

#pragma mark - Getter and Setter

- (UIScrollView *)taskScrollView {
if (!_taskScrollView) {
_taskScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, MainScreenWidth, MainScreenHeight)];
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (NSUInteger i = 0; i < self.taskIds.count; i++)
{
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;

_taskScrollView.pagingEnabled = YES;
_taskScrollView.contentSize = CGSizeMake(CGRectGetWidth(_taskScrollView.frame) * self.taskIds.count, CGRectGetHeight(_taskScrollView.frame));
_taskScrollView.showsHorizontalScrollIndicator = NO;
_taskScrollView.showsVerticalScrollIndicator = NO;
_taskScrollView.scrollsToTop = NO;
_taskScrollView.delegate = self;
}

return _taskScrollView;
}

- (UIPageControl *)pageControl {
if (!_pageControl) {
_pageControl = [[UIPageControl alloc] init];
self.pageControl.numberOfPages = self.taskIds.count;
self.pageControl.currentPage = 0;
}

return _pageControl;
}
  • getter和setter全部都放在最后
    因为一个ViewController很有可能会有非常多的view,就像上面给出的代码样例一样,如果getter和setter写在前面,就会把主要逻辑扯到后面去,其他人看的时候就要先划过一长串getter和setter,这样不太好。然后要求业务工程师写代码的时候按照顺序来分配代码块的位置,先是life cycle,然后是Delegate方法实现,然后是event response,然后才是getters and setters。这样后来者阅读代码时就能省力很多。

  • 每一个delegate都把对应的protocol名字带上,delegate方法不要到处乱写,写到一块区域里面去
    比如UITableViewDelegate的方法集就老老实实写上#pragma mark - UITableViewDelegate。这样有个好处就是,当其他人阅读一个他并不熟悉的Delegate实现方法时,他只要按住command然后去点这个protocol名字,Xcode就能够立刻跳转到对应这个Delegate的protocol定义的那部分代码去,就省得他到处找了。

  • event response专门开一个代码区域
    所有button、gestureRecognizer的响应事件都放在这个区域里面,不要到处乱放。

  • 关于private methods,正常情况下ViewController里面不应该写
    不是delegate方法的,不是event response方法的,不是life cycle方法的,就是private method了。对的,正常情况下ViewController里面一般是不会存在private methods的,这个private methods一般是用于日期换算、图片裁剪啥的这种小功能。这种小功能要么把它写成一个category,要么把他做成一个模块,哪怕这个模块只有一个函数也行。
    ViewController基本上是大部分业务的载体,本身代码已经相当复杂,所以跟业务关联不大的东西能不放在ViewController里面就不要放。另外一点,这个private method的功能这时候只是你用得到,但是将来说不定别的地方也会用到,一开始就独立出来,有利于将来的代码复用。

关于View的布局

  • 【勤之时】使用了Masonry

何时使用storyboard,何时使用nib,何时使用代码写View

  • 【勤之时】使用代码

关于MVC、MVVM等一大堆思想

  • 【勤之时】使用MVC

我会分好几个篇来说明表示层的开发。虽然这个应用的内容不多,但还是有几个页面的。首先来看看主界面:【勤之时】用来计时学习的界面:

功能描述:

  • 背景为每日一图
  • 主界面四个角落有四个按钮,点击可以进入各自的功能页面,分别为:
    • 任务管理
    • 统计
    • 每日分享
    • 设置
  • 顶部有一个Page Controller,有多少个小圆点就代表有多少个任务。通过左右滑动,可以切换到不同的任务。
  • 中上部为当前任务名称及当前的日历。同样,当左右滑动时,切换为不同的任务。
  • 背景图片在左右滑动是不会移动,每个任务有不同的配色,根据配色会对背景图增加一层对应的遮罩。
  • 中下部为【勤·开始】按钮,点击开始任务计时。
  • 计时时,会显示暂停按钮(若为沉浸模式,则无暂停按钮。)背景音乐开始播放(若背景音乐关闭,则不播放)。中上部圆盘内显示倒计时,圆环同时开始进度显示。
  • 点击暂停按钮,则出现继续/放弃按钮。音乐停,倒计时停。
  • 计时完成时,提示计时完成(蜂鸣+手机震动)。主页面切换为休息建议,并出现【勤·休息】按钮以及跳过按钮。
  • 若按【勤·休息】按钮,进入休息倒计时。
  • 若按跳过按钮,则直接回到默认的【勤·开始】页面。
  • 在倒计时过程中,所有其他额外的按钮都会隐藏。
  • 应用最小化后,音乐和倒计时会继续。
  • 在沉浸模式,倒计时时没有暂停按钮。最小化应用会直接退出倒计时,此时倒计时和应用都会暂停,回到默认的【勤·开始】页面

MVC设计考虑:

Controller:

  • ILDDiligenceViewController:
    page controller结合scrollview,以page的模式来显示ILDDiligenceClockViewController的View的内容。 当然,其他所有的按钮都在这个Controller中定义,包括四角的四个功能按钮,以及开始,暂停,继续,放弃,休息等按钮。

  • ILDDiligenceClockViewController:每一个Page的ViewController,主要包括背景颜色的遮罩,圆环以及圆环内的任务名称,日期,倒计时显示,休息建议等。每一个Page代表一个任务。

Model:

  • ILDDiligenceViewController对应的Model

    • NSArray:所有任务的Ids
    • ILDTaskModel:当前任务的具体内容
    • ILDStoryModel: 背景图片的内容
  • ILDDiligenceClockViewController对应的Model

    • ILDTaskModel:当前任务的具体内容

View:

  • ILDDiligenceClockViewController对应的View
    • ILDDiligenceClockView:用于具体描绘每个ClockView的类

ILDDiligenceViewController编码

  • 四个角的四个功能按钮,定义
1
2
3
4
5
6
7
8
@interface ILDDiligenceViewController () 

@property(nonatomic, strong) UIButton *taskButton;
@property(nonatomic, strong) UIButton *statisticsButton;
@property(nonatomic, strong) UIButton *storyButton;
@property(nonatomic, strong) UIButton *settingButton;

@end

在viewDidLoad中将这些按钮添加到Controller的View中

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
[super viewDidLoad];

[self.view addSubview:self.taskButton];
[self.view addSubview:self.statisticsButton];
[self.view addSubview:self.storyButton];
[self.view addSubview:self.settingButton];
}

在viewWillLayoutSubviews中设定Layout

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
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];

[self.taskButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_top).with.offset(30);
make.left.equalTo(self.view.mas_left).with.offset(12);
make.height.mas_equalTo(28);
make.width.mas_equalTo(28);
}];

[self.statisticsButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_top).with.offset(30);
make.right.equalTo(self.view.mas_right).with.offset(-12);
make.height.mas_equalTo(28);
make.width.mas_equalTo(28);
}];

[self.storyButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_bottom).with.offset(-50);
make.left.equalTo(self.view.mas_left).with.offset(12);
make.height.mas_equalTo(28);
make.width.mas_equalTo(28);
}];

[self.settingButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_bottom).with.offset(-50);
make.right.equalTo(self.view.mas_right).with.offset(-12);
make.height.mas_equalTo(28);
make.width.mas_equalTo(28);
}];
}

在get函数中初始化

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
- (UIButton *)taskButton {
if (!_taskButton) {
_taskButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_taskButton setImage:[UIImage imageNamed:@"menu_task_28x28_"] forState:UIControlStateNormal];
[_taskButton addTarget:self action:@selector(clickTaskButton:) forControlEvents:UIControlEventTouchUpInside];
}

return _taskButton;
}

- (UIButton *)statisticsButton {
if (!_statisticsButton) {
_statisticsButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_statisticsButton setBackgroundImage:[UIImage imageNamed:@"menu_statistics_28x28_"] forState:UIControlStateNormal];
[_statisticsButton addTarget:self action:@selector(clickStatisticsButton:) forControlEvents:UIControlEventTouchUpInside];
}

return _statisticsButton;
}

- (UIButton *)storyButton {
if (!_storyButton) {
_storyButton = [[UIButton alloc] init];
[_storyButton setBackgroundImage:[UIImage imageNamed:@"menu_story_28x28_"] forState:UIControlStateNormal];
[_storyButton addTarget:self action:@selector(clickStoryButton:) forControlEvents:UIControlEventTouchUpInside];
}

return _storyButton;
}

- (UIButton *)settingButton {
if (!_settingButton) {
_settingButton = [[UIButton alloc] init];
[_settingButton setBackgroundImage:[UIImage imageNamed:@"menu_settings_26x26_"] forState:UIControlStateNormal];
[_settingButton addTarget:self action:@selector(clickSettingButton:) forControlEvents:UIControlEventTouchUpInside];
}

return _settingButton;
}

按钮对应的Event函数

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
- (void)clickTaskButton:(id)sender {
[self copyScreen];

ILDTaskListViewController *taskListVC = [[ILDTaskListViewController alloc] init];
UINavigationController *taskListNC = [[UINavigationController alloc] initWithRootViewController:taskListVC];
[self presentViewController:taskListNC animated:YES completion:nil];
}

- (void)clickStatisticsButton:(id)sender {
[self copyScreen];

ILDStatisticsTodayViewController *statisticsTodayVC = [[ILDStatisticsTodayViewController alloc] init];
UINavigationController *settingNC = [[UINavigationController alloc] initWithRootViewController:statisticsTodayVC];
[self presentViewController:settingNC animated:YES completion:nil];
}

- (void)clickStoryButton:(id)sender {
ILDStoryViewController *storyVC = [[ILDStoryViewController alloc] init];
[self presentViewController:storyVC animated:YES completion:nil];
}

- (void)clickSettingButton:(id)sender {
[self copyScreen];

ILDSettingViewController *settingVC = [[ILDSettingViewController alloc] init];
UINavigationController *settingNC = [[UINavigationController alloc] initWithRootViewController:settingVC];
[self presentViewController:settingNC animated:YES completion:nil];
}

其他控件的操作基本上同上,以同样的方式处理ScrollView和PageView,然后再添加对应的ScrollViewDeligate

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
- (void)loadScrollViewWithPage:(NSUInteger)page {
if (page >= self.taskIds.count) {
return;
}

// replace the placeholder if necessary
ILDDiligenceClockViewController *controller = [self.viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) {
controller = [[ILDDiligenceClockViewController alloc] init];
controller.taskId = self.taskIds[page];
controller.diligenceClockView.delegate = self;
controller.isRestMode = NO;
[self.viewControllers replaceObjectAtIndex:page withObject:controller];
}

// add the controller's view to the scroll view
if (controller.view.superview == nil) {
CGRect frame = self.clockScrollView.frame;
frame.origin.x = CGRectGetWidth(frame) * page;
frame.origin.y = 0;
controller.view.frame = frame;

[self addChildViewController:controller];
[self.clockScrollView addSubview:controller.view];
[controller didMoveToParentViewController:self];
}
}

// at the end of scroll animation, reset the boolean used when scrolls originate from the UIPageControl
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// switch the indicator when more than 50% of the previous/next page is visible
CGFloat pageWidth = CGRectGetWidth(self.clockScrollView.frame);
NSUInteger page = floor((self.clockScrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
self.pageControl.currentPage = page;

// load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];

// a possible optimization would be to unload the views+controllers which are no longer visible
}

- (void)gotoPage:(BOOL)animated {
NSInteger page = self.pageControl.currentPage;

// load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];

// update the scroll view to the appropriate page
CGRect bounds = self.clockScrollView.bounds;
bounds.origin.x = CGRectGetWidth(bounds) * page;
bounds.origin.y = 0;
[self.clockScrollView scrollRectToVisible:bounds animated:animated];
}

点击开始按钮是,我们需要播放背景音乐,所以需要引入AVAudioPlayer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@property(nonatomic, strong) AVAudioPlayer *musicPlayer;

- (void)playMusic {
self.musicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[MusicHelper musicUrlByName:self.currentTaskModel.musicName] error:nil];
self.musicPlayer.delegate = self;
self.musicPlayer.numberOfLoops = -1;
self.musicPlayer.volume = 1;
[self.musicPlayer prepareToPlay];
self.musicPlayer.meteringEnabled = YES;
[self.musicPlayer play];
}

- (void)pauseMusic {
[self.musicPlayer pause];
}

- (void)stopMusic {
[self.musicPlayer stop];
}

- (void)resumeMusic {
[self.musicPlayer play];
}

每次计时完成时,需要有提示声及振动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)playSystemSound {
SystemSoundID sound = kSystemSoundID_Vibrate;

//这里使用在上面那个网址找到的铃声,注意格式
NSString *path = [NSString stringWithFormat:@"/System/Library/Audio/UISounds/%@.%@",@"new-mail",@"caf"];
if (path) {
OSStatus error = AudioServicesCreateSystemSoundID((__bridge CFURLRef)[NSURL fileURLWithPath:path],&sound);
if (error != kAudioServicesNoError) {
sound = 0;
}
}

AudioServicesPlaySystemSound(sound);//播放声音
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);//静音模式下震动
}

每次任务完成后,需要把任务的统计信息保存起来

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
- (void)taskCompleted {
NSInteger page = self.pageControl.currentPage;
ILDDiligenceClockViewController *controller = [self.viewControllers objectAtIndex:page];

[self stopMusic];
[self playSystemSound];

if (controller.isRestMode) {
[self setToDiligenceStartStatus];
} else {
ILDDiligenceModel *diligencModel = [[ILDDiligenceModel alloc] init];
diligencModel.taskId = self.taskIds[page];
diligencModel.startDate = self.startDate;
diligencModel.endDate = [NSDate date];
diligencModel.breakTimes = [NSNumber numberWithInteger:self.breakTimes];
diligencModel.diligenceTime = self.currentTaskModel.diligenceTime;

[[ILDDiligenceDataCenter sharedInstance] addDiligence:diligencModel];
if (self.currentTaskModel.isRestModeEnabled) {
[self setToRestStartStatus];
} else {
[self setToDiligenceStartStatus];
}
}
}

应用最小化时,要根据是否是沉浸模式,继续计时和音乐或关闭计时和音乐,此时需要监听UIApplicationDidEnterBackgroundNotification,使用NSNotificationCenter

1
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterBackground:)name:UIApplicationDidEnterBackgroundNotification object:nil];

需要监听ILDStoryDataCenter的storyDataDictionary成员,当变化时,背景图片也要相应的变化,使用KVO

1
[[ILDStoryDataCenter sharedInstance] addObserver:self forKeyPath:@"storyDataDictionary" options:NSKeyValueObservingOptionNew context:NULL];

ILDDiligenceViewController编码
这个类相对简单
首先根据当前的任务设定背景颜色

1
2
self.taskModel = [[ILDTaskDataCenter sharedInstance] taskConfigurationById:self.taskId];
self.backgroundView.backgroundColor = [ColorHelper colorByName:self.taskModel.color];

其他的事情由ILDDiligenceClockView来处理,他仅需要把ILDDiligenceClockView添加到自己的view中。

ILDDiligenceClockView编码
主要代码就是画时钟及其内容

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
- (void)drawRect:(CGRect)rect {
// calculate angle for progress
if (self.diligenceSeconds == 0) {
self.endAngle = self.startAngle;
} else {
self.endAngle = (1 - self.timeLeft / self.diligenceSeconds) * 2 * M_PI + self.startAngle;
}

CGFloat radius = (rect.size.width - 20)/2;

// draw circle
UIBezierPath *circle = [UIBezierPath bezierPath];
[circle addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:radius
startAngle:0
endAngle:2 * M_PI
clockwise:YES];
circle.lineWidth = CIRCLE_WIDTH;
[FlatWhiteDark setStroke];
[circle stroke];

// draw progress
UIBezierPath *progress = [UIBezierPath bezierPath];
[progress addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:radius
startAngle:self.startAngle
endAngle:self.endAngle
clockwise:YES];
progress.lineWidth = PROGRESS_WIDTH;
[FlatWhite setStroke];
[progress stroke];

if (self.isRunning) {
// if Timer is running, always show time left in the center of the circle
NSString *textContent = [ILDDateHelper minutesFormatBySeconds:self.timeLeft];

UIFont *textFont = [UIFont fontWithName: @"-" size: TEXT_NAME_SIZE];
CGSize textSize = [textContent sizeWithAttributes:@{NSFontAttributeName:textFont}];
CGRect textRect = CGRectMake(rect.size.width / 2 - textSize.width / 2,
rect.size.height / 2 - textSize.height / 2,
textSize.width , textSize.height);

NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
textStyle.lineBreakMode = NSLineBreakByWordWrapping;
textStyle.alignment = NSTextAlignmentCenter;

[textContent drawInRect:textRect withAttributes:@{NSFontAttributeName:textFont, NSForegroundColorAttributeName:FlatWhite, NSParagraphStyleAttributeName:textStyle}];
} else {
// show task Name or rest suggestion Name
NSString *taskOrRestName = self.taskName;
if (self.isRestMode) {
taskOrRestName = [ILDRestSuggestion randomRestSuggestion];
}

NSInteger fontSize = TEXT_NAME_SIZE;

UIFont *taskNameFont = [UIFont fontWithName: @"-" size: fontSize];
CGSize taskNameSize = [taskOrRestName sizeWithAttributes:@{NSFontAttributeName:taskNameFont}];

while (taskNameSize.width > (self.frame.size.width - 20)) {
fontSize -= 2;
taskNameFont = [UIFont fontWithName: @"-" size: fontSize];
taskNameSize = [taskOrRestName sizeWithAttributes:@{NSFontAttributeName:taskNameFont}];
}

CGFloat taskNameX = 10;
CGFloat taskNameY = (rect.size.height - taskNameSize.height)/2 - 10;
CGFloat taskNameWidth = self.frame.size.width - 20;
CGFloat taskNameHeight = taskNameSize.height;

CGRect taskNameRect = CGRectMake(taskNameX, taskNameY, taskNameWidth, taskNameHeight);

NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
textStyle.lineBreakMode = NSLineBreakByWordWrapping;
textStyle.alignment = NSTextAlignmentCenter;

[taskOrRestName drawInRect:taskNameRect withAttributes:@{NSFontAttributeName:taskNameFont, NSForegroundColorAttributeName:FlatWhite, NSParagraphStyleAttributeName:textStyle}];

NSString *dateToday = [ILDDateHelper stringOfDayWithWeekDay:[NSDate date]];
UIFont *dateFont = [UIFont fontWithName: @"-" size: TEXT_DATE_SIZE];
CGSize dateSize = [dateToday sizeWithAttributes:@{NSFontAttributeName:dateFont}];

CGFloat dateX = (rect.size.width - dateSize.width)/2;
CGFloat dateY = taskNameY + taskNameHeight + 5;
CGFloat dateWidth = dateSize.width;
CGFloat dateHeight = dateSize.height;

CGRect dateRect = CGRectMake(dateX, dateY, dateWidth, dateHeight);

[dateToday drawInRect:dateRect withAttributes:@{NSFontAttributeName:dateFont, NSForegroundColorAttributeName:FlatWhite, NSParagraphStyleAttributeName:textStyle}];

CGContextRef context = UIGraphicsGetCurrentContext();

[FlatWhite setStroke];
CGContextMoveToPoint(context, dateX, dateY - 2);
CGContextAddLineToPoint(context, dateX + dateWidth, dateY - 2);
CGContextMoveToPoint(context, dateX, dateY + dateHeight + 2);
CGContextAddLineToPoint(context, dateX + dateWidth, dateY + dateHeight + 2);
CGContextStrokePath(context);
}
}

额外的讨论: