Please note, this blog entry is from a previous course. You might want to check out the current one.
Add another tab for some “settings” in the game.
We will need to access the settings from both card games as well as from the settings view. Therefore we create a new class derived from NSObject with public properties of the “settings” which can be used by all of those controllers:
@interface GameSettings : NSObject @property (nonatomic) int matchBonus; @property (nonatomic) int mismatchPenalty; @property (nonatomic) int flipCost; @end
Store the settings in a dictionary in NSUserDefaults which is called when the properties are accessed:
#define GAME_SETTINGS_KEY @"Game_Settings_Key" #define MATCHBONUS_KEY @"MatchBonus_Key" #define MISMATCHPENALTY_KEY @"MismatchPenalty_Key" #define FLIPCOST_KEY @"Flipcost_Key" - (int)getIntForKey:(NSString *)key withDefaultInt:(int)defaultValue { NSDictionary *settings = [[NSUserDefaults standardUserDefaults] dictionaryForKey:GAME_SETTINGS_KEY]; if (!settings) return defaultValue; if (![[settings allKeys] containsObject:key]) return defaultValue; return [settings[key] intValue]; } - (int)matchBonus { return [self getIntForKey:MATCHBONUS_KEY withDefaultInt:4]; } - (int)mismatchPenalty { return [self getIntForKey:MISMATCHPENALTY_KEY withDefaultInt:2]; } - (int)flipCost { return [self getIntForKey:FLIPCOST_KEY withDefaultInt:1]; }
… and will be written if their values have changed:
- (void)setInt:(int)value forKey:(NSString *)key { NSMutableDictionary *settings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:GAME_SETTINGS_KEY] mutableCopy]; if (!settings) { settings = [[NSMutableDictionary alloc] init]; } settings[key] = @(value); [[NSUserDefaults standardUserDefaults] setObject:settings forKey:GAME_SETTINGS_KEY]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (void)setMatchBonus:(int)matchBonus { if (matchBonus != self.matchBonus) [self setInt:matchBonus forKey:MATCHBONUS_KEY]; } - (void)setMismatchPenalty:(int)mismatchPenalty { if (mismatchPenalty != self.mismatchPenalty) [self setInt:mismatchPenalty forKey:MISMATCHPENALTY_KEY]; } - (void)setFlipCost:(int)flipCost { if (flipCost != self.flipCost) [self setInt:flipCost forKey:FLIPCOST_KEY]; }
In storyboard create a new view controller with sliders and labels for the settings and link it to the tab-view controller:
The sliders are set to allow values from 0 to 10 in storyboard – which are completely arbitrary – and could also be set in code.
Create a new class for the new view controller (don’t forget to link it in storyboard).
Add a property for the game settings and initialize it lazily:
@property (strong, nonatomic) GameSettings *gameSettings; ... - (GameSettings *)gameSettings { if (!_gameSettings) _gameSettings = [[GameSettings alloc] init]; return _gameSettings; }
Create outlets for the labels and sliders as well as actions for when the sliders have changed:
@property (weak, nonatomic) IBOutlet UILabel *matchBonusLabel; @property (weak, nonatomic) IBOutlet UILabel *mismatchPenaltyLabel; @property (weak, nonatomic) IBOutlet UILabel *flipCostLabel; @property (weak, nonatomic) IBOutlet UISlider *matchBonusSlider; @property (weak, nonatomic) IBOutlet UISlider *mismatchPenaltySlider; @property (weak, nonatomic) IBOutlet UISlider *flipCostSlider; ... - (IBAction)matchBonusSliderChanged:(UISlider *)sender { [self setLabel:self.matchBonusLabel forSlider:sender]; self.gameSettings.matchBonus = floor(sender.value); } - (IBAction)mismatchPenaltySliderChanged:(UISlider *)sender { [self setLabel:self.mismatchPenaltyLabel forSlider:sender]; self.gameSettings.mismatchPenalty = floor(sender.value); } - (IBAction)flipCostSliderChanged:(UISlider *)sender { [self setLabel:self.flipCostLabel forSlider:sender]; self.gameSettings.flipCost = floor(sender.value); }
The action methods use a helper method (following below) to set the labels according to the slider position, and set the game settings accordingly. The helper method limits the slider to integer values:
- (void)setLabel:(UILabel *)label forSlider:(UISlider *)slider { int sliderValue; sliderValue = lroundf(slider.value); [slider setValue:sliderValue animated:NO]; label.text = [NSString stringWithFormat:@"%d", sliderValue]; }
Whenever the view controller will appear on screen, it will load the current settings and update the sliders and labels accordingly:
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.matchBonusSlider.value = self.gameSettings.matchBonus; self.mismatchPenaltySlider.value = self.gameSettings.mismatchPenalty; self.flipCostSlider.value = self.gameSettings.flipCost; [self setLabel:self.matchBonusLabel forSlider:self.matchBonusSlider]; [self setLabel:self.mismatchPenaltyLabel forSlider:self.mismatchPenaltySlider]; [self setLabel:self.flipCostLabel forSlider:self.flipCostSlider]; }
In the common game-view controller create a new property for the game settings – again use lazy initialization in its getter:
@property (strong, nonatomic) GameSettings *gameSettings; ... - (GameSettings *)gameSettings { if (!_gameSettings) _gameSettings = [[GameSettings alloc] init]; return _gameSettings; }
When a game view controller appears on screen, load the settings:
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.game.matchBonus = self.gameSettings.matchBonus; self.game.mismatchPenalty = self.gameSettings.mismatchPenalty; self.game.flipCost = self.gameSettings.flipCost; }
Because the deal button deletes creates a new game, the settings have to be reloaded in the game getters:
- (CardMatchingGame *)game { ... _game.matchBonus = self.gameSettings.matchBonus; _game.mismatchPenalty = self.gameSettings.mismatchPenalty; _game.flipCost = self.gameSettings.flipCost; ... }
One minor problem is hidden in the card-game model. At the moment the values are checked against zero for setting default values. Because the sliders can actually be zero this needs to be changed:
- (id)initWithCardCount:(NSUInteger)count usingDeck:(Deck *)deck { ... _matchBonus = -1; _mismatchPenalty = -1; _flipCost = -1; ... } - (int)matchBonus { if (_matchBonus < 0) _matchBonus = 4; return _matchBonus; } - (int)mismatchPenalty { if (_mismatchPenalty < 0) _mismatchPenalty = 2; return _mismatchPenalty; } - (int)flipCost { if (_flipCost < 0) _flipCost = 1; return _flipCost; }
The complete code is available on github.