
·您现在的位置: 江北区云翼计算机软件开发服务部 >> 文章中心 >> 网站建设 >> app软件开发 >> IOS开发 >> (6/18)重学Standford_iOS7开发_控制器多态性、导航控制器、选项卡栏控制器_课程笔记
终于有时间跟新了,两周时间复(yu)习(xi)了5门考试累觉不爱。。。。。。
--------------------------------------------------------------------------我是正文分割线---------------------------------------------------------------------------------------------
第六课
1、控制器多态性
这里控制器多态性是指在控制器中使用继承,通过继承构造通用视图控制器(ViewController),在具体的MVC中继承通用视图控制器实现具体功能(通常是父类中的抽象方法)
注意:objective-C并没有abstract关键字,因此通常要通过注释的方式说明,抽象方法需要在.h文件中声明,一般实现文件中的抽象方法没有具体功能,只返回nil。
storyboard的多态控制器类需要在属性检查器中更改指定的类。
说明:通过继承,父类中的连接也会被完整的继承下来,如输出口(Outlet)等。
demo地址:https://github.com/NSLogMeng/Stanford_iOS7_Study/commit/238167d6080df94e0383aceccab6d16fea290af2
2、多MVC
理解:多MVC基本组合方式,子MVC作为父MVC的视图(View)呈现。

(1)UINavigationController(以日历应用为例)
1) Navigation Bar
a.title 当前MVC的标题,与内容有关
b.navigationItem.rightBarButtonItems(ViewController属性) 功能按钮(注意与UIButton区别,UIBarButton的数组,即可以嵌入多个BarButton)
c.Back Button (一般由UINavigationController自动设定,默认为上一MVC的title,长度较长时显示为back,亦可自行设置)
当点击当前MVC的back时,当前MVC会被释放,返回前一个MVC
2)toolbarItems (ViewController属性)
底部出现(an NSArray of UIBarButtonItems )
3)rootViewController
指向一个MVC的controller,作为根MVC
//UINavigationController常用方法 - (instancetype)initWithRootViewController:(UIViewController *)rootViewController; // 设置根视图控制器 - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated; //压入新视图 - (UIViewController *)popViewControllerAnimated:(BOOL)animated;//弹出当前视图 //navigationBar @PRoperty(nonatomic,getter=isNavigationBarHidden) BOOL navigationBarHidden; - (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated; //toolbar @property(nonatomic,getter=isToolbarHidden) BOOL toolbarHidden NS_AVAILABLE_IOS(3_0); - (void)setToolbarHidden:(BOOL)hidden animated:(BOOL)animated NS_AVAILABLE_IOS(3_0);
(2)segue(非常重要,MVC间切换的基础)
push:一种以push,pop方式在UINavigationController上进行MVC切换的方式
identifier:用来表示push,方便在代码中使用
1 //需要跳转时的Action
2 - (IBAction)rentEquipment
3 {
4 if (self.snowTraversingTalent == Skiing) {
5 [self performSegueWithIdentifier:@“AskAboutSkis” sender:self];
6 } else {
7 [self performSegueWithIdentifier:@“AskAboutSnowboard” sender:self];
8 }
9 }
10
11 //为各segue做跳转前的准备
12 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
13 if ([segue.identifier isEqualToString:@“DoSomething”]) {
14 if ([segue.destinationViewController isKindOfClass:[DoSomethingVC class]]) {
15 DoSomethingVC *doVC = (DoSomethingVC *)segue.destinationViewController;
16 doVC.neededInfo = ...; }
17 }
18 }
19
20 //segue跳转判断
21 - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
22  if ([segue.identifier isEqualToString:@“DoAParticularThing”]) {
23 return [self canDoAParticularThing] ? YES : NO;
24 }
25 }
注意:segue跳转的准备工作中并未设置好输出口,因此不能直接赋值给新MVC的输出口,MVC中需要设置API来接收数据。
(3)demo : Attributor Stats
Use a UINavigationController to show “statistics” on colors and outlining in Attributor.
demo地址:https://github.com/NSLogMeng/Stanford_iOS7_Study/commit/fba52faaefd72cb1d4e0ef0c9029092fd5749f63
(4)UITabBarController(以时钟应用为例)
//设置选项个数,一般不超过5个,超过5个时,第五个及其他将集合在最后一栏 @property (nonatomic, strong) NSArray *viewControllers;
3、作业
要求:a.在Matchismo 的基础上添加一个新的MVC作为Set纸牌匹配游戏。
b.新的Set纸牌游戏与原纸牌游戏类似(3张牌匹配模式)
c.原游戏的功能保留,可以适当删减或更改UI(原要求是取消UISegmentedControll)
d.使用UITabBarController将两个游戏分开
e.Set纸牌的数目可能会与之前不同,选择合适的UI尺寸
f.使用▲ ● ■ 以及NSAttributedString 来表示Set纸牌的三个属性(形状,颜色,阴影)
g.每个游戏必须展示当前的得分状况,并允许用户重置
h.使用NSAttributedString来记录并展示纸牌匹配状况
i.使用UINavigationController 添加新的MVC来详细展示纸牌匹配情况的历史
Set纸牌游戏规则:Wikipedia 或 百度百科
作业解析:
最终实现效果如下图:



实现过程:
针对上次作业的结果对纸牌游戏再次进行抽象化并添加Set纸牌游戏
抽象化结果:https://github.com/NSLogMeng/Stanford_iOS7_Study/commit/58afa7577cb339328819fccb5422aef27fd843ff
a.添加Set及Model的抽象化
SetCard
SetCard的匹配详见作业要求中的规则,此处判定匹配成功则score为1,否则为0(仅作为判定标记,也可以根据自己喜好自定义分数规则)
1 #import "Card.h" 2 3 @interface SetCard : Card 4 5 @property (strong,nonatomic) NSString *suit;// ▲ ● ■ 6 @property (strong,nonatomic) NSString *color;// red green purple 7 @property (nonatomic) BOOL shading; 8 9 + (NSArray *)validSuits; 10 + (NSArray *)validColors; 11 + (NSUInteger)shadingNumber; 12 13 @end
1 #import "SetCard.h"
2
3 @interface SetCard()
4
5 @end
6
7 @implementation SetCard
8
9 - (NSString *)contents
10 {
11 return nil;
12 }
13
14 - (int)match:(NSArray *)otherCards
15 {
16 int score = 0;
17
18 if ([otherCards count] == 2)
19 {
20 SetCard *firstCard = [otherCards firstObject];
21 SetCard *secondCard = [otherCards lastObject];
22 if ([self.suit isEqualToString:firstCard.suit] && [firstCard.suit isEqualToString:secondCard.suit])
23 {
24 score += 1;
25 }
26 else if (![self.suit isEqualToString:firstCard.suit] && ![self.suit isEqualToString:secondCard.suit] && ![firstCard.suit isEqualToString:secondCard.suit])
27 {
28 score += 1;
29 }
30 else
31 {
32 score -= 1;
33 }
34
35 if ([self.color isEqualToString:firstCard.color] && [firstCard.color isEqualToString:secondCard.color])//全相等
36 {
37 score += 1;
38 }
39 else if (![self.color isEqualToString:firstCard.color] && ![self.color isEqualToString:secondCard.color] && ![firstCard.color isEqualToString:secondCard.color])//全不相等
40 {
41 score += 1;
42 }
43 else
44 {
45 score -= 1;
46 }
47
48 if ((self.shading == firstCard.shading) && (firstCard.shading == secondCard.shading))
49 {
50 score += 1;
51 }
52 else
53 {
54 score -= 1;
55 }
56
57 }
58
59 if (score == 3)
60 {
61 score = 1;
62 }
63 else
64 {
65 score = 0;
66 }
67
68 return score;
69 }
70
71 + (NSArray *)validSuits
72 {
73 return @[@"▲",@"▲▲",@"▲▲▲",@"●",@"●●",@"●●●",@"■",@"■ ■",@"■ ■ ■"];
74 }
75
76 + (NSArray *)validColors
77 {
78 return @[@"red",@"green",@"purple"];
79 }
80
81
82 + (NSUInteger)shadingNumber
83 {
84 return 2;
85 }
SetCardDeck
与PlayingCardDeck类似
1 #import "Deck.h" 2 3 @interface SetCardDeck : Deck 4 5 @end
1 #import "SetCardDeck.h"
2 #import "SetCard.h"
3
4 @implementation SetCardDeck
5
6 - (instancetype) init
7 {
8 self = [super init];
9 if (self)
10 {
11 for (NSString *suit in [SetCard validSuits])
12 {
13 for (NSString *color in [SetCard validColors])
14 {
15 for (NSUInteger i = 0; i < [SetCard shadingNumber]; i++)
16 {
17 SetCard *card = [[SetCard alloc] init];
18 card.suit = suit;
19 card.color = color;
20 card.shading = (i<1) ? true : false;
21
22 [self addCard:card];
23 }
24 }
25 }
26 }
27 return self;
28 }
29
30 @end
CardMatchingGame的抽象化
上次作业中我们的CardMatchingGame只针对PlayingCard的匹配规则及方法在SetCard中任然类似或适用,因此我们抽象化CardMatchingGame,使其成为PlayingCardMatchingGame与SetCardMatchingGame的超类。
首先我们根据作业要求我们添加gameStateHistory属性来保存游戏进行的历史数据,由于可能会出现多纸牌匹配,因此添加validOfOtherCards属性来获取其余纸牌的内容。因为gameState属性最好使用只读属性,因此我们在超类中去除此属性而在子类中实现
1 #import <Foundation/Foundation.h> 2 #import "Deck.h" 3 #import "Card.h" 4 5 static const int MISMATCH_PENALTY = 2; 6 static const int MATCH_BOUNDS = 4; 7 static const int COST_TO_CHOOSE = 1; 8 9 @interface CardMatchingGame : NSObject 10 11 // designated initializer 12 - (instancetype) initWithCardCount:(NSUInteger)count 13 usingDeck:(Deck *)deck; 14 - (void) chooseCardAtIndex:(NSUInteger)index; 15 - (Card *) cardAtIndex:(NSUInteger)index; 16 17 - (NSString *)validOfOtherCards:(NSArray *)otherCards; 18 19 @property (nonatomic,readonly) NSString *validOfOtherCards; 20 @property (nonatomic,readonly) NSInteger score; 21 @property (nonatomic) NSUInteger gameModel;// >=2 22 @property (nonatomic,strong) NSMutableArray *gameStateHistory; 23 24 @end
1 #import "CardMatchingGame.h"
2
3 @interface CardMatchingGame()
4
5 @property (nonatomic,readwrite) NSString *validOfOtherCards;
6 @property (nonatomic,readwrite) NSInteger score;
7 @property (nonatomic,strong) NSMutableArray *cards;//of playingcard
8
9 @end
10
11 @implementation CardMatchingGame
12
13 - (NSMutableArray *)cards
14 {
15 if (!_cards) _cards = [[NSMutableArray alloc] init];
16 return _cards;
17 }
18
19 - (NSMutableArray *)gameStateHistory
20 {
21 if (!_gameStateHistory)
22 {
23 _gameStateHistory = [[NSMutableArray alloc] init];
24 }
25 return _gameStateHistory;
26 }
27
28 - (instancetype) initWithCardCount:(NSUInteger)count usingDeck:(Deck *)deck
29 {
30 self = [super init];
31 if (self)
32 {
33 for (int i = 0; i < count; i++)
34 {
35 Card *card = [deck drawRandomCard];
36 if (card)
37 {
38 [self.cards addObject:card];
39 }
40 else
41 {
42 self = nil;
43 break;
44 }
45 }
46 }
47
48 return self;
49 }
50
51 - (Card *) cardAtIndex:(NSUInteger)index
52 {
53 return (index < [self.cards count]) ? self.cards[index] : nil;
54 }
55
56 - (void) chooseCardAtIndex:(NSUInteger)index
57 {
58 Card *card = [self cardAtIndex:index];
59 if (!card.isMacthed)
60 {
61 if (card.isChosen)
62 {
63 card.chosen = NO;
64 }
65 else
66 {
67 // match against other chosen cards
68 NSMutableArray *otherCards = [NSMutableArray arrayWithCapacity:self.gameModel];
69
70 for (Card *otherCard in self.cards)
71 {
72 if (otherCard.isChosen && !otherCard.isMacthed)
73 {
74 [otherCards addObject:otherCard];
75 }
76 }
77
78 //不能放于for循环之前,否则会将本次被选择的牌加入cards,不能放于下面的if之后,否则当if成立时返回,没有将本次翻牌的cost记录,且不能翻牌
79 self.score -= COST_TO_CHOOSE * self.gameModel;
80 card.chosen = YES;
81
82 if ([otherCards count] < self.gameModel - 1)
83 {
84 return;
85 }
86 else
87 {
88 self.validOfOtherCards = [self validOfOtherCards:otherCards];
89 int matchScore = [card match:otherCards];
90
91 if (matchScore)
92 {
93 self.score += matchScore * MATCH_BOUNDS * self.gameModel;
94 for (Card *otherCard in otherCards)
95 {
96 otherCard.matched = YES;
97 }
98 card.matched = YES;
99 }
100 else
101 {
102 self.score -= MISMATCH_PENALTY * self.gameModel;
103 for (Card *otherCard in otherCards)
104 {
105 otherCard.chosen = NO;
106 }
107 }
108 }
109 }
110 }
111 }
112
113 - (NSString *)validOfOtherCards:(NSArray *)otherCards
114 {
115 NSMutableString *string = [[NSMutableString alloc] init];
116 for (Card *card in otherCards)
117 {
118 [string appendFormat:@"%@ ",card.contents];
119 }
120 return string;
121 }
122
123 @end
子类化的PlayingCardMatchingGame
实现类gameState的记录,并添加方法使其在纸牌匹配时记录状态及状态历史
1 #import "CardMatchingGame.h" 2 3 @interface PlayingCardMatchingGame : CardMatchingGame 4 5 @property (nonatomic,readonly) NSString *gameState; 6 7 @end
1 #import "PlayingCardMatchingGame.h"
2
3 @interface PlayingCardMatchingGame()
4
5 @property (nonatomic,readwrite) NSString *gameState;
6
7 @end
8
9 @implementation PlayingCardMatchingGame
10
11 - (void)chooseCardAtIndex:(NSUInteger)index
12 {
13 int forwardScore = self.score;
14 [super chooseCardAtIndex:index];
15 [self updateGameState:forwardScore withIndexOfCard:index];
16 }
17
18 - (void)updateGameState:(int)forwardScore withIndexOfCard:(int)index
19 {
20 if (self.score == forwardScore - self.gameModel * COST_TO_CHOOSE)
21 {
22 _gameState = [self cardAtIndex:index].contents;
23 }
24 else if (self.score < (forwardScore - self.gameModel * COST_TO_CHOOSE))
25 {
26 _gameState = [NSString stringWithFormat:@"%@ with %@ not matched. %d point penalty!",[self cardAtIndex:index].contents,self.validOfOtherCards,forwardScore - self.score];
27 [self.gameStateHistory addObject:_gameState];
28 }
29 else if (self.score > (forwardScore - self.gameModel * COST_TO_CHOOSE))
30 {
31 _gameState = [NSString stringWithFormat:@"%@ matched %@ . get %d score!",[self cardAtIndex:index].contents,self.validOfOtherCards,self.score - forwardScore];
32 [self.gameStateHistory addObject:_gameState];
33 }
34 }
35
36 @end
子类化的SetCardMatchingGame
与PlayingCardMatchingGame类似,注意在SetCard中并没有content的数据,因此要重写validOfOtherCard:方法,以保证正确获取SetCard的内容
1 #import "CardMatchingGame.h" 2 3 @interface SetCardMatchingGame : CardMatchingGame 4 5 @property (nonatomic,readonly) NSString *gameState; 6 7 @end
1 #import "SetCardMatchingGame.h"
2 #import "SetCard.h"
3
4 @interface SetCardMatchingGame()
5
6 @property (nonatomic,readwrite) NSString *gameState;
7
8 @end
9
10 @implementation SetCardMatchingGame
11
12 - (void)chooseCardAtIndex:(NSUInteger)index
13 {
14 int forwardScore = self.score;
15 [super chooseCardAtIndex:index];
16 [self updateGameState:forwardScore withIndexOfCard:index];
17 }
18
19 - (NSString *)validOfOtherCards:(NSArray *)otherCards
20 {
21 NSMutableString *string = [[NSMutableString alloc] init];
22 for (SetCard *card in otherCards)
23 {
24 [string appendString:card.suit];
25 }
26 return string;
27 }
28
29 - (void)updateGameState:(int)forwardScore withIndexOfCard:(int)index
30 {
31 if (self.score == forwardScore - (self.gameModel * COST_TO_CHOOSE))
32 {
33 _gameState = ((SetCard *)[self cardAtIndex:index]).suit;
34 }
35 else if (self.score < (forwardScore - (self.gameModel * COST_TO_CHOOSE)))
36 {
37 _gameState = [NSString stringWithFormat:@"%@ with %@ not matched. %d point penalty!",((SetCard *)[self cardAtIndex:index]).suit,self.validOfOtherCards,forwardScore - self.score];
38 [self.gameStateHistory addObject:_gameState];
39 }
40 else if (self.score > (forwardScore - (self.gameModel * COST_TO_CHOOSE)))
41 {
42 _gameState = [NSString stringWithFormat:@"%@ matched %@ . get %d score!",((SetCard *)[self cardAtIndex:index]).suit,self.validOfOtherCards,self.score - forwardScore];
43 [self.gameStateHistory addObject:_gameState];
44 }
45 }
46
47 @end
b.UI的更新
如图:
由于SetCard的游戏规则,SetCard并不会被翻回到背面,因此要更改纸牌的牌面图片
c.Controller的抽象化及子类化实现
上次作业中针对ViewController有很多只与PlayingCardMatchingGame有关的内容,此次将这些内容转移到PlayingCardGameViewController中,对ViewController进行抽象化,并且也在子类SetCardGameViewController中实现SetCard匹配游戏
ViewController
由于许多有关UI的操作需要在子类中实现,因此将他们放到.h文件中,同时titleForCard:与backgroundImageForCard:方法也许要在SetCard子类中重写(想想为什么?),因此也放入.h文件中
1 #import <UIKit/UIKit.h> 2 #import "Deck.h" 3 4 @class CardMatchingGame; 5 6 @interface ViewController : UIViewController 7 8 //protected 9 //for subclasses 10 - (Deck *)createDeck; // abstract 11 12 @property (strong,nonatomic) CardMatchingGame *game; 13 @property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *cardButtons; 14 @property (weak, nonatomic) IBOutlet UILabel *scoreLable; 15 16 - (NSUInteger)cardButtonsNumber; 17 - (void) updateUI; 18 - (NSString *)titleForCard:(Card *)card; 19 - (UIImage *)backgroundImageForCard:(Card *)card; 20 21 @end
1 #import "ViewController.h"
2 #import "CardMatchingGame.h"
3
4 @interface ViewController ()
5
6 @end
7
8 @implementation ViewController
9
10 - (NSUInteger)cardButtonsNumber
11 {
12 return [_cardButtons count];
13 }
14
15 - (Deck *)createDeck // abstract
16 {
17 return nil;
18 }
19
20 - (IBAction)touchCardButton:(UIButton *)sender
21 {
22 NSUInteger cardIndex = [self.cardButtons indexOfObject:sender];
23 [self.game chooseCardAtIndex:cardIndex];
24 [self updateUI];
25 }
26
27 - (void) updateUI
28 {
29 for (UIButton *cardButton in self.cardButtons)
30 {
31 NSUInteger cardIndex = [self.cardButtons indexOfObject:cardButton];
32 Card *card = [self.game cardAtIndex:cardIndex];
33 [cardButton setTitle:[self titleForCard:card] forState:UIControlStateNormal];
34 [cardButton setBackgroundImage:[self backgroundImageForCard:card] forState:UIControlStateNormal];
35 cardButton.enabled = !card.isMacthed;
36 }
37 self.scoreLable.text = [NSString stringWithFormat:@"Score:%ld",(long)self.game.score];
38 }
39
40 - (NSString *)titleForCard:(Card *)card
41 {
42 return card.isChosen ? card.contents : nil;
43 }
44
45 - (UIImage *)backgroundImageForCard:(Card *)card
46 {
47 return [UIImage imageNamed:card.isChosen ? @"cardFront" : @"cardBack"];
48 }
49
50 @end
PlayingCardGameController
与上次作业基本相同,只不过变为从更抽象的超类中继承为子类(一些只适用于PlayingCardGame的UI与方法在本类实现),并且添加了History功能
1 #import "ViewController.h" 2 3 @interface PlayingCardGameViewController : ViewController 4 5 @end
1 #import "PlayingCardGameViewController.h"
2 #import "PlayingCardDeck.h"
3 #import "PlayingCardMatchingGame.h"
4 #import "HistoryViewController.h"
5
6 @interface PlayingCardGameViewController ()
7
8 @property (weak, nonatomic) IBOutlet UISegmentedControl *gameModelSelectSegmented;
9 @property (weak, nonatomic) IBOutlet UILabel *gameModelLable;
10 @property (weak, nonatomic) IBOutlet UITextField *matchModelTextFiled;
11 @property (weak, nonatomic) IBOutlet UILabel *gameStateLable;
12 @property (weak, nonatomic) IBOutlet UIButton *restartButton;
13 @property (nonatomic) NSUInteger selfDefiningModel;
14
15 @end
16
17 @implementation PlayingCardGameViewController
18
19 #define CORNER_FONT_STANDARD_HIGHT 180.0
20 #define CORNER_RADIUS 12.0
21
22 //让界面变得更好看的魔法
23 - (CGFloat)cornerScaleFactor:(CGFloat)height {return height / CORNER_FONT_STANDARD_HIGHT;}
24 - (CGFloat)cornerRadius:(CGFloat)height {return CORNER_RADIUS * [self cornerScaleFactor:height];}
25
26 - (void)viewDidLoad
27 {
28 //又是个让界面变得更好看的魔法
29 self.restartButton.layer.cornerRadius = [self cornerRadius:self.restartButton.bounds.size.height];
30 self.gameModelSelectSegmented.layer.cornerRadius = [self cornerRadius:self.gameModelSelectSegmented.bounds.size.height * 3];
31
32 [self.gameModelSelectSegmented addTarget:self action:@selector(segmentAction:) forControlEvents:UIControlEventValueChanged];//target-action
33
34 _selfDefiningModel = 2;//default model
35 }
36
37 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
38 {
39 if ([segue.identifier isEqualToString:@"show PlayingCardGameHistory"])
40 {
41 if ([segue.destinationViewController isKindOfClass:[HistoryViewController class]])
42 {
43 HistoryViewController *HVC = (HistoryViewController *)segue.destinationViewController;
44 HVC.history = self.game.gameStateHistory;
45 }
46 }
47 }
48
49
50 - (void) segmentAction:(UISegmentedControl *)Seg
51 {
52 if (self.gameModelSelectSegmented.selectedSegmentIndex == 2)
53 {
54 [self assertSelfDefiningModel:self.matchModelTextFiled.text];
55 }
56 else
57 {
58 self.selfDefiningModel = self.gameModelSelectSegmented.selectedSegmentIndex + 2;
59 }
60 self.gameModelLable.text = [NSString stringWithFormat:@"game model:%lu",(unsigned long)self.selfDefiningModel];
61 }
62
63 - (void) assertSelfDefiningModel:(NSString *)text
64 {
65 if ([self.matchModelTextFiled.text integerValue] < 2)
66 {
67 [[[UIAlertView alloc] initWithTitle:@"Wrong" message:@"game model at least 2" delegate:nil cancelButtonTitle:@"certain" otherButtonTitles:nil, nil] show];
68 self.matchModelTextFiled.text = @"";
69 self.gameModelSelectSegmented.selectedSegmentIndex = 0;
70 }
71 else if ([self.matchModelTextFiled.text integerValue] > [self cardButtonsNumber])
72 {
73 [[[UIAlertView alloc] initWithTitle:@"Wrong" message:@"beyond card max number" delegate:nil cancelButtonTitle:@"certain" otherButtonTitles:nil, nil] show];
74 self.matchModelTextFiled.text = @"";
75 self.gameModelSelectSegmented.selectedSegmentIndex = 0;
76 }
77 else
78 {
79 self.selfDefiningModel = [self.matchModelTextFiled.text integerValue];
80 }
81 }
82
83 - (Deck *)createDeck
84 {
85 return [[PlayingCardDeck alloc] init];
86 }
87
88 - (IBAction)touchRestartButton
89 {
90 //恢复默认值
91 self.gameModelSelectSegmented.enabled = YES;
92 self.matchModelTextFiled.enabled = YES;
93 self.matchModelTextFiled.enabled = YES;
94 self.gameModelSelectSegmented.selectedSegmentIndex = 0;
95 self.selfDefiningModel = 2;
96 self.gameModelLable.text = [NSString stringWithFormat:@"game model:%d",_selfDefiningModel];
97 self.matchModelTextFiled.text = nil;
98
99 self.game = nil;
100 [self updateUIWithNotCreateGame];
101 }
102
103 - (IBAction)touchCardButton:(UIButton *)sender
104 {
105 //游戏开始后禁用模式选择功能
106 self.gameModelSelectSegmented.enabled = NO;
107 self.matchModelTextFiled.enabled = NO;
108 self.matchModelTextFiled.enabled = NO;
109
110 NSUInteger cardIndex = [self.cardButtons indexOfObject:sender];
111 if(!self.game)
112 {
113 self.game = [[PlayingCardMatchingGame alloc] initWithCardCount:[self.cardButtons count]
114 usingDeck:[self createDeck]];
115 self.game.gameModel = self.selfDefiningModel;
116 }
117 [self.game chooseCardAtIndex:cardIndex];
118 [self updateUI];
119 }
120
121 - (void) updateUIWithNotCreateGame
122 {
123 for (UIButton *cardButton in self.cardButtons)
124 {
125 [cardButton setTitle:@"" forState:UIControlStateNormal];
126 [cardButton setBackgroundImage:[UIImage imageNamed:@"cardBack"] forState:UIControlStateNormal];
127 cardButton.enabled = YES;
128 }
129 self.gameStateLable.text = @"State";
130 self.scoreLable.text = @"Score:0";
131 }
132
133 - (void)updateUI
134 {
135 [super updateUI];
136 self.gameStateLable.text = ((PlayingCardMatchingGame *)self.game).gameState;
137 }
138
139 - (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
140 {
141 [self.view endEditing:YES];
142 }
143
144 @end
SetCardGameViewController
本类实现了SetCard匹配游戏,因为是UI层,因此使用了NSAttributeString来表示SetCard的内容,同样也实现了History功能
注意:由于SetCard牌面始终朝上,因此要在ViewDidLoad中初始化game
1 #import "ViewController.h" 2 3 @interface SetCardGameViewController : ViewController 4 5 @end
1 #import "SetCardGameViewController.h"
2 #import "SetCardMatchingGame.h"
3 #import "SetCardDeck.h"
4 #import "SetCard.h"
5 #import "HistoryViewController.h"
6
7 @interface SetCardGameViewController()
8
9 @property (weak, nonatomic) IBOutlet UILabel *gameStateLable;
10 @property (weak, nonatomic) IBOutlet UIButton *restartButton;
11 @property (nonatomic) NSUInteger selfDefiningModel;
12 @property (nonatomic,strong) NSDictionary *colorOfCard;
13
14 @end
15
16 @implementation SetCardGameViewController
17
18 #define CORNER_FONT_STANDARD_HIGHT 180.0
19 #define CORNER_RADIUS 12.0
20
21 //让界面变得更好看的魔法
22 - (CGFloat)cornerScaleFactor:(CGFloat)height {return height / CORNER_FONT_STANDARD_HIGHT;}
23 - (CGFloat)cornerRadius:(CGFloat)height {return CORNER_RADIUS * [self cornerScaleFactor:height];}
24
25 - (void)viewDidLoad
26 {
27 //又是个让界面变得更好看的魔法
28 self.restartButton.layer.cornerRadius = [self cornerRadius:self.restartButton.bounds.size.height];
29
30 self.colorOfCard = @{@"red" : [UIColor redColor], @"green" : [UIColor greenColor], @"purple" : [UIColor purpleColor]};
31
32 _selfDefiningModel = 3;//default model
33
34 if(!self.game)
35 {
36 self.game = [[SetCardMatchingGame alloc] initWithCardCount:[self.cardButtons count]
37 usingDeck:[self createDeck]];
38 self.game.gameModel = self.selfDefiningModel;
39 }
40 [self updateUIWithNotCreateGame];
41 }
42
43 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
44 {
45 if ([segue.identifier isEqualToString:@"show SetCardGameHistory"])
46 {
47 if ([segue.destinationViewController isKindOfClass:[HistoryViewController class]])
48 {
49 HistoryViewController *HVC = (HistoryViewController *)segue.destinationViewController;
50 HVC.history = self.game.gameStateHistory;
51 }
52 }
53 }
54
55
56 - (Deck *)createDeck
57 {
58 return [[SetCardDeck alloc] init];
59 }
60 - (IBAction)touchCardButtons:(UIButton *)sender
61 {
62 //游戏开始后禁用模式选择功能
63 NSUInteger cardIndex = [self.cardButtons indexOfObject:sender];
64 if(!self.game)
65 {
66 self.game = [[SetCardMatchingGame alloc] initWithCardCount:[self.cardButtons count]
67 usingDeck:[self createDeck]];
68 self.game.gameModel = self.selfDefiningModel;
69 }
70 [self.game chooseCardAtIndex:cardIndex];
71 [self updateUI];
72 }
73
74 - (IBAction)touchRestartButton:(UIButton *)sender
75 {
76 //恢复默认值
77 self.selfDefiningModel = 3;
78 self.game = nil;
79 if(!self.game)
80 {
81 self.game = [[SetCardMatchingGame alloc] initWithCardCount:[self.cardButtons count]
82 usingDeck:[self createDeck]];
83 self.game.gameModel = self.selfDefiningModel;
84 }
85 [self updateUIWithNotCreateGame];
86 }
87
88 - (void) updateUIWithNotCreateGame
89 {
90 for (UIButton *cardButton in self.cardButtons)
91 {
92 NSUInteger cardIndex = [self.cardButtons indexOfObject:cardButton];
93 Card *card = [self.game cardAtIndex:cardIndex];
94 [cardButton setAttributedTitle:[self attributeTitleForCard:card] forState:UIControlStateNormal];
95 [cardButton setBackgroundImage:[UIImage imageNamed:@"cardFront"] forState:UIControlStateNormal];
96 cardButton.enabled = YES;
97 }
98 self.gameStateLable.text = @"State";
99 self.scoreLable.text = @"Score:0";
100 }
101
102 - (void)updateUI
103 {
104 for (UIButton *cardButton in self.cardButtons)
105 {
106 NSUInteger cardIndex = [self.cardButtons indexOfObject:cardButton];
107 Card *card = [self.game cardAtIndex:cardIndex];
108 [cardButton setAttributedTitle:[self attributeTitleForCard:card] forState:UIControlStateNormal];
109 [cardButton setBackgroundImage:[self backgroundImageForCard:card] forState:UIControlStateNormal];
110 cardButton.enabled = !card.isMacthed;
111 }
112 self.scoreLable.text = [NSString stringWithFormat:@"Score:%ld",(long)self.game.score];
113 self.gameStateLable.text = ((SetCardMatchingGame *)self.game).gameState;
114 }
115
116 - (NSAttributedString *)attributeTitleForCard:(Card *)card
117 {
118 NSShadow *shadow1 = [[NSShadow alloc] init];
119 shadow1.shadowColor = [UIColor grayColor];
120 shadow1.shadowOffset = CGSizeMake(2.0f, 2.0f);
121
122 NSShadow *shadow2 = [[NSShadow alloc] init];
123 shadow2.shadowColor = [UIColor whiteColor];
124 shadow2.shadowOffset = CGSizeMake(0.0F,0.0f);
125 return [[NSAttributedString alloc] initWithString:((SetCard *)card).suit
126 attributes:@{NSForegroundColorAttributeName : [self.colorOfCard valueForKey:((SetCard *)card).color],
127 NSShadowAttributeName : ((SetCard *)card).shading ? shadow1 : shadow2,
128 NSFontAttributeName : [UIFont boldSystemFontOfSize:10.0f]}];
129 }
130
131 - (UIImage *)backgroundImageForCard:(Card *)card
132 {
133 return card.chosen ? [UIImage imageNamed:@"setCardback"] : [UIImage imageNamed:@"cardFront"];
134 }
135
136 @end
d.history功能的实现
HistoryViewController
属性history用于接收父MVC传来的数据
1 #import <UIKit/UIKit.h> 2 3 @interface HistoryViewController : UIViewController 4 @property (strong, nonatomic) NSArray *history; 5 @end
1 #import "HistoryViewController.h"
2
3 @interface HistoryViewController ()
4 @property (weak, nonatomic) IBOutlet UITextView *HistoryTextView;
5 @end
6
7 @implementation HistoryViewController
8
9 - (void)viewDidLoad {
10 [super viewDidLoad];
11 // Do any additional setup after loading the view.
12 for (NSString *state in self.history)
13 {
14 self.HistoryTextView.text = [self.HistoryTextView.text stringByAppendingFormat:@"%@\n",state];
15 }
16 }
17 @end
总结:本次作业提出的问题比解决的问题要多很多,作者只是极其简单的实现了作业的大致要求,仍然有许多问题没有解决,如Model的抽象化问题中原有的readonly属性在超类中可能变为了readwrite,是否会产生安全性问题,有没有更好的方法;Controller的抽象化中将许多原本隐藏在.m文件中的输出口放至超类的.h文件中,是否会造成子类对输出口的滥用,有没有更好的解决方案;仍然没有解决State显示内容超出屏幕空间的问题(太懒了=_=);Setcard的state显示可以使用NSAttributeString(感兴趣可以试试);同样SetCard中history的显示也可以使用NSAttributeString,此时可能要改变一些传值的内容(感兴趣去玩耍一下吧);关于SetCard的游戏规则其实还不太完善,比如初始化后可能无适合匹配情况的判断(百度百科的游戏规则中是要再次添加三张牌,我在屏幕中尽可能多添加了几张纸牌来避免这种情况),以及游戏结束情况的判断,本次作业相当于只实现了setcard选择后的匹配计分规则(任重而道远。。。)。到此就是我大概能想到的值得改进的地方(感觉到提出的问题比解决的问题多了吧=_=),一次作业的内容不仅能复习到前面所学的内容,还是对个人学到知识融汇贯通的一次检验,各位加油,有任何问题欢迎讨论:)
作业源码地址:https://github.com/NSLogMeng/Stanford_iOS7_Study/commit/0bb1103ccb4293caa9dc3b12200ba9fb12a51170
课程视频地址:网易公开课:http://open.163.com/movie/2014/1/L/H/M9H7S9F1H_M9H801GLH.html
或者iTunes U搜索standford课程