Shared/Quiz.m
/* |
Copyright (C) 2018 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Model class for a Quiz. Manages loading the quiz |
data (questions, answers) from a plist file, vending questions, and recording |
responses. |
*/ |
#import "Quiz.h" |
#import "QuestionViewController.h" |
NSString * const QuestionsListKey = @"QuestionList"; |
NSString * const QuestionTextKey = @"QuestionText"; |
NSString * const AnswerTextKey = @"AnswerText"; |
NSString * const AnswerKey = @"Answer"; |
@interface Quiz () |
- (instancetype)initWithQuestionsPlistAtURL:(NSURL*)questionsURL NS_DESIGNATED_INITIALIZER; |
/// Array of Question objects. |
@property (strong) NSArray *questions; |
@property (readwrite) NSUInteger correctlyAnsweredQuestions; |
@property (readwrite) NSUInteger answeredQuestions; |
@end |
@implementation Quiz |
//| ---------------------------------------------------------------------------- |
//! Initializes and retuns a newly created Quiz with the questions from a plist |
//! file at questionsURL. |
// |
//! @param questionsURL |
//! A url for a plist file containing a list of questions. |
/// |
- (instancetype)initWithQuestionsPlistAtURL:(NSURL*)questionsURL |
{ |
self = [super init]; |
if (self) |
{ |
NSData *questionsPlistData; |
NSDictionary *questionsPlistDict; |
NSArray *questionsList; |
// Load and deserialize the plist |
questionsPlistData = [NSData dataWithContentsOfURL:questionsURL]; |
questionsPlistDict = [NSPropertyListSerialization propertyListWithData:questionsPlistData |
options:NSPropertyListImmutable |
format:nil |
error:NULL]; |
questionsList = questionsPlistDict[QuestionsListKey]; |
// Create a temporary NSMutableArray to hold the Question objects |
// that will be created from the data in questionsList. |
NSMutableArray *questions = [NSMutableArray arrayWithCapacity:questionsList.count]; |
for (NSDictionary *questionDict in questionsList) { |
Question *question = [[Question alloc] initWithQuestionDict:questionDict]; |
// Observe changes to the selectedResponse property of the newly |
// created Question object. When a change is observed, the Quiz may |
// need to update its answeredQuestions and |
// correctlyAnsweredQuestions properties. |
[question addObserver:self |
forKeyPath:@"selectedResponse" |
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) |
context:NULL]; |
[questions addObject:question]; |
} |
// Create an immutable array from the mutable questions array and |
// assign it to the ivar backing the questions property. |
_questions = [[NSArray alloc] initWithArray:questions]; |
} |
return self; |
} |
//| ---------------------------------------------------------------------------- |
- (void)dealloc |
{ |
// Unregister Key-Value observing notifications for all Questions. |
for (Question *question in self.questions) |
[question removeObserver:self forKeyPath:@"selectedResponse" context:NULL]; |
} |
+ (void)setQuizOnQuestionViewController:(QuestionViewController *)controller { |
Quiz *newQuiz = [[Quiz alloc] initWithQuestionsPlistAtURL:[[NSBundle mainBundle] URLForResource:@"Questions" withExtension:@"plist"]]; |
controller.currentQuiz = newQuiz; |
} |
//| ---------------------------------------------------------------------------- |
//! Manual implementation of the getter for the percentageScore property. |
// |
// Because percentageScore is a function of the correctlyAnsweredQuestions |
// and answeredQuestions, it is computed as needed instead of stored. |
// |
- (float)percentageScore |
{ |
return (float)self.correctlyAnsweredQuestions/(float)self.answeredQuestions; |
} |
//| ---------------------------------------------------------------------------- |
//! Manual implementation of the getter for the totalQuestions property. |
// |
- (NSUInteger)totalQuestions |
{ |
return self.questions.count; |
} |
//| ---------------------------------------------------------------------------- |
//! Returns the Question at the specified index. |
// |
// Implementing this method allows other objects to use the subscript |
// operator to access Questions by their index. |
// Ex: Question *q = myQuiz[0]; |
// |
- (Question*)objectAtIndexedSubscript:(NSInteger)idx |
{ |
// Call through to -questionAtIndex: |
return [self questionAtIndex:idx]; |
} |
//| ---------------------------------------------------------------------------- |
//! Returns the Question at the specified index. |
// |
- (Question*)questionAtIndex:(NSUInteger)idx |
{ |
return (self.questions)[idx]; |
} |
//| ---------------------------------------------------------------------------- |
//! Resets the Quiz, clearing the current score. |
// |
- (void)resetQuiz |
{ |
// NSArray forwards calls to -setValue:forKey: to all the objects |
// within the array. |
[self.questions setValue:@(NSNotFound) forKey:@"selectedResponse"]; |
} |
#pragma mark - |
#pragma mark Key Value Observing |
//| ---------------------------------------------------------------------------- |
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key |
{ |
if ([key isEqualToString:@"percentageScore"]) |
// percentageScore is derived from answeredQuestions and |
// correctlyAnsweredQuestions. |
return [NSSet setWithObjects:@"answeredQuestions", @"correctlyAnsweredQuestions", nil]; |
else |
return [super keyPathsForValuesAffectingValueForKey:key]; |
} |
//| ---------------------------------------------------------------------------- |
// Callback for key-value observing (KVO) change notifications. |
// |
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context |
{ |
if ([keyPath isEqualToString:@"selectedResponse"]) |
// The selectedResponse of a Question changed. |
{ |
NSInteger previouslySelectedResponse = [change[NSKeyValueChangeOldKey] integerValue]; |
NSInteger selectedResponse = [change[NSKeyValueChangeNewKey] integerValue]; |
if (previouslySelectedResponse == NSNotFound && selectedResponse != NSNotFound) |
// If the question had not been answered but now is, increment |
// answeredQuestions. |
self.answeredQuestions++; |
else if (previouslySelectedResponse != NSNotFound && selectedResponse == NSNotFound) |
// If the question had been answered but no longer is, decrement |
// answeredQuestions. This will occur when the quiz is being reset. |
self.answeredQuestions--; |
if (selectedResponse == ((Question*)object).correctResponse && |
previouslySelectedResponse != ((Question*)object).correctResponse) |
// If the current response is correct and the previous response was |
// incorrect, increment correctlyAnsweredQuestions. |
self.correctlyAnsweredQuestions++; |
else if (previouslySelectedResponse == ((Question*)object).correctResponse && |
selectedResponse != ((Question*)object).correctResponse) |
// If the current response is incorrect and the previous response was |
// correct, decrement correctlyAnsweredQuestions. |
self.correctlyAnsweredQuestions--; |
} |
else |
// Always invoke the super's implementation for unhandled keyPath's. |
return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; |
} |
@end |
Copyright © 2018 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2018-03-15