你的位置:首页 > 操作系统

[操作系统](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 *)contents10 {11   return nil;12 }13 14 - (int)match:(NSArray *)otherCards15 {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     else31     {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     else44     {45       score -= 1;46     }47     48     if ((self.shading == firstCard.shading) && (firstCard.shading == secondCard.shading))49     {50       score += 1;51     }52     else53     {54       score -= 1;55     }56 57   }58   59   if (score == 3)60   {61     score = 1;62   }63   else64   {65     score = 0;66   }67   68   return score;69 }70 71 + (NSArray *)validSuits72 {73   return @[@"▲",@"▲▲",@"▲▲▲",@"●",@"●●",@"●●●",@"■",@"■ ■",@"■ ■ ■"];74 }75 76 + (NSArray *)validColors77 {78   return @[@"red",@"green",@"purple"];79 }80 81 82 + (NSUInteger)shadingNumber83 {84   return 2;85 } 

SetCardDeck

  与PlayingCardDeck类似

1 #import "Deck.h"2 3 @interface SetCardDeck : Deck4 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 : NSObject10 11 // designated initializer12 - (instancetype) initWithCardCount:(NSUInteger)count13             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;// >=222 @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         else101         {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 *)otherCards114 {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 : CardMatchingGame4 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 PlayingCardMatchingGame10 11 - (void)chooseCardAtIndex:(NSUInteger)index12 {13   int forwardScore = self.score;14   [super chooseCardAtIndex:index];15   [self updateGameState:forwardScore withIndexOfCard:index];16 }17 18 - (void)updateGameState:(int)forwardScore withIndexOfCard:(int)index19 {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 : CardMatchingGame4 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 SetCardMatchingGame11 12 - (void)chooseCardAtIndex:(NSUInteger)index13 {14   int forwardScore = self.score;15   [super chooseCardAtIndex:index];16   [self updateGameState:forwardScore withIndexOfCard:index];17 }18 19 - (NSString *)validOfOtherCards:(NSArray *)otherCards20 {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)index30 {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 subclasses10 - (Deck *)createDeck; // abstract11 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)cardButtonsNumber11 {12   return [_cardButtons count];13 }14 15 - (Deck *)createDeck // abstract16 {17   return nil;18 }19 20 - (IBAction)touchCardButton:(UIButton *)sender21 {22   NSUInteger cardIndex = [self.cardButtons indexOfObject:sender];23   [self.game chooseCardAtIndex:cardIndex];24   [self updateUI];25 }26 27 - (void) updateUI28 {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 *)card41 {42   return card.isChosen ? card.contents : nil;43 }44 45 - (UIImage *)backgroundImageForCard:(Card *)card46 {47   return [UIImage imageNamed:card.isChosen ? @"cardFront" : @"cardBack"];48 }49 50 @end

PlayingCardGameController

  与上次作业基本相同,只不过变为从更抽象的超类中继承为子类(一些只适用于PlayingCardGame的UI与方法在本类实现),并且添加了History功能

1 #import "ViewController.h"2 3 @interface PlayingCardGameViewController : ViewController4 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 *)sender104 {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) updateUIWithNotCreateGame122 {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)updateUI134 {135   [super updateUI];136   self.gameStateLable.text = ((PlayingCardMatchingGame *)self.game).gameState;137 }138 139 - (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event140 {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 : ViewController4 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)updateUI103 {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 *)card117 {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).suit126                      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 *)card132 {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 : UIViewController4 @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课程