Skip to content

Instantly share code, notes, and snippets.

@rosem
Last active October 9, 2015 16:54
Show Gist options
  • Save rosem/f59914ff6425fa1108da to your computer and use it in GitHub Desktop.
Save rosem/f59914ff6425fa1108da to your computer and use it in GitHub Desktop.
Instrument view controller. Handles logic to control views (UI) and record the user's response (via bluetooth keyboard). Next item is then requested from the engine.
//
// ListSortingViewController.m
// mss-cognition
//
// Created by Mike Rose on 11/22/14.
// Copyright (c) 2014 Northwestern University. All rights reserved.
//
#import "ListSortingViewController.h"
#import "ListSortingObject.h"
#import "ListSortingFlowLayout.h"
#import "ListSortingInstructionCell.h"
#import "ListSortingConstants.h"
#import "ListSortingItemHelper.h"
#import "ListSortingScoreCalculations.h"
#import "ListSortingAttributedStringHelper.h"
#define ITEM_CODE_LABEL_MARGIN 25.0
NSString * const ListSortingInstructionCellKey = @"InstructionCell";
NSString * const ListSortingInstructionWithDelayedResponseCellKey = @"InstructionWithDelayedResponseCell";
NSString * const ListSortingObjectPlaybackCellKey = @"ObjectPlaybackCell";
@interface ListSortingViewController ()
@property (nonatomic) NSInteger keyboardResponse;
@property (nonatomic) NSInteger trialCount;
@property (nonatomic) BOOL disableKeybaord;
@property (nonatomic) BOOL isPresentingConfirmation;
@property (nonatomic) BOOL instructionTimerDidFinish;
@property (strong, nonatomic) UICollectionView *collectionView;
@property (strong, nonatomic) NSArray *objects;
@property (strong, nonatomic) UILabel *itemCodeLabel;
@property (strong, nonatomic) NSLayoutConstraint *itemCodeLabelCnWidth;
@property (strong, nonatomic) NSLayoutConstraint *itemCodeLabelCnHeight;
@end
@implementation ListSortingViewController
#pragma mark - instrument init
- (id)initWithInstrument:(MSSInstrument *)instrument user:(MSSUser *)user engine:(id<Engine>)engine bundle:(NSBundle *)bundle
{
self = [super initWithInstrument:instrument user:user engine:engine bundle:bundle];
if (self) {
_keyboardResponse = ListSortingKeyboardResponseNone;
_trialCount = 0;
_disableKeybaord = NO;
_isPresentingConfirmation = NO;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSLayoutConstraint *cnX;
NSLayoutConstraint *cnY;
NSLayoutConstraint *cnWidth;
NSLayoutConstraint *cnHeight;
// self
self.view.backgroundColor = [UIColor whiteColor];
// collection view
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:[[ListSortingFlowLayout alloc] init]];
[_collectionView registerClass:[ListSortingInstructionCell class] forCellWithReuseIdentifier:ListSortingInstructionCellKey];
[_collectionView registerClass:[ListSortingInstructionWithDelayedResponseCell class] forCellWithReuseIdentifier:ListSortingInstructionWithDelayedResponseCellKey];
[_collectionView registerClass:[ListSortingObjectPlaybackCell class] forCellWithReuseIdentifier:ListSortingObjectPlaybackCellKey];
_collectionView.backgroundColor = [UIColor whiteColor];
_collectionView.dataSource = self;
_collectionView.delegate = self;
_collectionView.scrollEnabled = NO;
_collectionView.userInteractionEnabled = NO;
[self.view addSubview:_collectionView];
_collectionView.translatesAutoresizingMaskIntoConstraints = NO;
cnX = [NSLayoutConstraint constraintWithItem:_collectionView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0];
cnY = [NSLayoutConstraint constraintWithItem:_collectionView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
cnWidth = [NSLayoutConstraint constraintWithItem:_collectionView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:0];
cnHeight = [NSLayoutConstraint constraintWithItem:_collectionView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraints:@[ cnX, cnY, cnWidth, cnHeight ]];
// item code label
_itemCodeLabel = [[UILabel alloc] init];
[self.view addSubview:_itemCodeLabel];
_itemCodeLabel.translatesAutoresizingMaskIntoConstraints = NO;
cnX = [NSLayoutConstraint constraintWithItem:_itemCodeLabel attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:-ITEM_CODE_LABEL_MARGIN];
cnY = [NSLayoutConstraint constraintWithItem:_itemCodeLabel attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-ITEM_CODE_LABEL_MARGIN];
_itemCodeLabelCnWidth = [NSLayoutConstraint constraintWithItem:_itemCodeLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:0 constant:0];
_itemCodeLabelCnHeight = [NSLayoutConstraint constraintWithItem:_itemCodeLabel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:0 constant:0];
[self.view addConstraints:@[ cnX, cnY, _itemCodeLabelCnWidth, _itemCodeLabelCnHeight ]];
// observe text changes on the label for resizing
[_itemCodeLabel addObserver:self forKeyPath:@"attributedText" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
}
- (void)dealloc
{
[_itemCodeLabel removeObserver:self forKeyPath:@"attributedText"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"attributedText"]) {
// adjust label width/height
NSAttributedString *attributedString = [change objectForKey:NSKeyValueChangeNewKey];
if (![attributedString isEqual:[NSNull null]]) {
_itemCodeLabelCnWidth.constant = [MSSStringUtility attributedTextWidth:attributedString width:MAXFLOAT];
_itemCodeLabelCnHeight.constant = [MSSStringUtility attributedTextHeight:attributedString width:MAXFLOAT];
}
}
}
# pragma mark - instrument life-cycle
- (void)startInstrument
{
[self nextItem];
}
- (void)stopInstrument
{
_disableKeybaord = YES;
_trialCount = 0;
[self stopAllSounds];
// if the current cell is a playback cell, stop its playback
ListSortingItemType type = [ListSortingItemHelper itemTypeForItem:self.currentItem];
if (type == ListSortingItemTypePractice || type == ListSortingItemTypeLive) {
for (UICollectionViewCell *cell in _collectionView.visibleCells) {
if ([cell isKindOfClass:[ListSortingObjectPlaybackCell class]]) {
ListSortingObjectPlaybackCell *playbackCell = (ListSortingObjectPlaybackCell *)cell;
playbackCell.delegate = nil;
[playbackCell stopPlayback];
}
}
}
}
- (void)previousItem
{
self.currentItem = [self.engine previousItem];
if (self.currentItem) {
[self displayItem:self.currentItem];
}
}
- (void)nextItem
{
_trialCount = 0;
self.currentItem = [self.engine nextItem];
if (self.currentItem) {
[self displayItem:self.currentItem];
} else {
// instrument is finished, calculate score
ListSortingScoreCalculations *scores = [[ListSortingScoreCalculations alloc] initWithInstrument:self.instrument];
self.instrument.rawScore = scores.rawScore;
[self.delegate instrumentDidFinish:self.instrument];
}
}
- (void)displayItem:(MSSItem *)item
{
[super displayItem:item];
// because the item type is determined by the item id (and it's not consistent)
// each instrument is responsible for setting the item's type
ListSortingItemType type = [ListSortingItemHelper itemTypeForItem:item];
_keyboardResponse = ListSortingKeyboardResponseNone;
_disableKeybaord = YES;
_isPresentingConfirmation = NO;
MSSElement *element = [item.elements objectAtIndex:0];
MSSResource *resource = [element.resources objectAtIndex:0];
NSString *description = resource.Description;
switch (type) {
case ListSortingItemTypeTitle: {
_disableKeybaord = NO;
// title
ListSortingObject *object = [[ListSortingObject alloc] init];
object.displayText = [ListSortingAttributedStringHelper instructionAttributedStringForString:self.instrument.title];
object.instructionText = [ListSortingAttributedStringHelper adminInstructionAttributedStringForString:@"Press the Space Bar to continue"];
_objects = @[ object ];
break;
}
case ListSortingItemTypeInstruction: {
_instructionTimerDidFinish = NO;
// instructions
ListSortingObject *object = [[ListSortingObject alloc] init];
object.displayText = [ListSortingAttributedStringHelper instructionAttributedStringForString:description];
object.instructionText = [ListSortingAttributedStringHelper adminInstructionAttributedStringForString:@"Press the Space Bar to continue"];
_objects = @[ object ];
break;
}
case ListSortingItemTypePractice:
case ListSortingItemTypeLive: {
// prase resource description into objects to display
_objects = [self objectsForResourceDescription:description];
break;
}
default: {
// ruh roh
break;
}
}
// update the item code
_itemCodeLabel.attributedText = [ListSortingAttributedStringHelper itemCodeAttributedStringForString:[ListSortingItemHelper itemCodeForItem:self.currentItem]];
// reload the collection view
[_collectionView performBatchUpdates:^{
[_collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
} completion:nil];
// if automated testing is enabled, get a reference to the cell that is currently visible
if ([[AutomatedTestManager sharedInstance] isTestingEnabled] && _collectionView.visibleCells.count > 0) {
UICollectionViewCell *cell = _collectionView.visibleCells[0];
// if it's an instruction cell and the space bar is required to continue, then simulate a tap of the space bar
if ([cell isKindOfClass:[ListSortingInstructionCell class]]) {
ListSortingInstructionCell *listSortingInstructionCell = (ListSortingInstructionCell *)cell;
if ([listSortingInstructionCell.instructionTextLabel.text caseInsensitiveCompare:@"press the space bar to continue"] == NSOrderedSame) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, .75 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
// highlight the border of this instruction text label in purple to indicate that a space bar press will be simulated
listSortingInstructionCell.instructionTextLabel.layer.borderColor = [UIColor colorWithRed:210.0/255.0 green:0 blue:253.0/255.0 alpha:1].CGColor;
listSortingInstructionCell.instructionTextLabel.layer.borderWidth = 10;
listSortingInstructionCell.instructionTextLabel.layer.cornerRadius = 5;
// simulate tap of the space bar
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, .75 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self didTapSpace];
});
});
}
}
}
}
- (NSArray *)objectsForResourceDescription:(NSString *)description
{
// parses the resource.description into object
// ("Elephant,Rabbit,Sheep,series|Rabbit Sheep Elephant" > ["Elephant", "Rabbit", "Sheep"])
NSArray *objectStrings = [self objectStringsForResourceDescription:description];
// parse object strings into a list sorting object to display via the collection view
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:objectStrings.count];
for (NSString *objectString in objectStrings) {
ListSortingObject *object = [[ListSortingObject alloc] init];
// base filename
NSString *filename = [objectString lowercaseString];
filename = [filename stringByReplacingOccurrencesOfString:@" " withString:@"_"];
// image
NSString *imageFilename = [filename stringByAppendingString:@".jpg"];
object.image = [self imageForImageName:imageFilename];
// display text
object.displayText = [ListSortingAttributedStringHelper imageCaptionAttributedStringForString:[objectString uppercaseString]];
// sound filename
object.soundFilename = filename;
[objects addObject:object];
}
return objects;
}
- (NSArray *)objectStringsForResourceDescription:(NSString *)description
{
if ([description rangeOfString:@"|"].location != NSNotFound) {
// practice items have a pipe "|" in the resource description with the correct items on the right (lastObject)
description = [[description componentsSeparatedByString:@"|"] firstObject];
}
NSMutableArray *array = [[description componentsSeparatedByString:@","] mutableCopy];
[array removeLastObject];
return array;
}
#pragma mark - collection view data source
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell;
ListSortingItemType type = [ListSortingItemHelper itemTypeForItem:self.currentItem];
if (type == ListSortingItemTypeTitle) {
// title
ListSortingInstructionCell *instructionCell = [collectionView dequeueReusableCellWithReuseIdentifier:ListSortingInstructionCellKey forIndexPath:indexPath];
[self configureInstructionCell:instructionCell atIndexPath:indexPath];
cell = instructionCell;
} else {
if (type == ListSortingItemTypeInstruction || _isPresentingConfirmation) {
// instruction or confirmation
ListSortingInstructionWithDelayedResponseCell *confirmationCell = [collectionView dequeueReusableCellWithReuseIdentifier:ListSortingInstructionWithDelayedResponseCellKey forIndexPath:indexPath];
[self configureInstructionWithDelayedResponseCell:confirmationCell atIndexPath:indexPath];
cell = confirmationCell;
} else {
// practice or live
ListSortingObjectPlaybackCell *objectPlaybackCell = [collectionView dequeueReusableCellWithReuseIdentifier:ListSortingObjectPlaybackCellKey forIndexPath:indexPath];
[self configureObjectPlaybackCell:objectPlaybackCell atIndexPath:indexPath];
cell = objectPlaybackCell;
}
}
return cell;
}
- (void)configureInstructionCell:(ListSortingInstructionCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
ListSortingObject *object = [_objects objectAtIndex:indexPath.row];
cell.displayTextLabel.attributedText = object.displayText;
cell.instructionTextLabel.attributedText = object.instructionText;
cell.instructionTextLabel.layer.borderWidth = 0;
}
- (void)configureInstructionWithDelayedResponseCell:(ListSortingInstructionWithDelayedResponseCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
[self configureInstructionCell:cell atIndexPath:indexPath];
cell.delegate = self;
[cell startTimerWithTimeInterval:[ListSortingItemHelper instructionDelayedResponseTimeIntervalForItem:self.currentItem]];
}
- (void)configureObjectPlaybackCell:(ListSortingObjectPlaybackCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
cell.delegate = self;
[cell startPlaybackWithObjects:_objects];
}
#pragma mark - collection view flow layout delegate
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return collectionView.bounds.size;
}
#pragma mark - list sorting instruction with delayed response cell delegate
- (void)listSortingInstructionWithDelayedResponseCellTimerDidFinish
{
_disableKeybaord = NO;
_instructionTimerDidFinish = YES;
}
#pragma mark - list sorting object playback cell delegate
- (void)listSortingObjectPlayerbackCellDidDisplayObject:(ListSortingObject *)object
{
[self playLocalizedSound:object.soundFilename];
}
- (void)listSortingObjectPlayerbackCellDidFinishPlayback
{
// if automated testing is in progress then enable the keyboard immediately
if ([[AutomatedTestManager sharedInstance] isTestingInProgress]) {
_disableKeybaord = NO;
UICollectionViewCell *cell = _collectionView.visibleCells[0];
ListSortingObjectPlaybackCell *listSortingObjectPlaybackCell = (ListSortingObjectPlaybackCell *)cell;
if ([ListSortingItemHelper itemTypeForItem:self.currentItem] == ListSortingItemTypePractice) {
// show a simulated space bar with a purple border to indicate that space will be pressed
UIView *simulatedKeyView = [[UIView alloc] initWithFrame:CGRectMake(20, 768-20-80, 80, 80)];
simulatedKeyView.layer.cornerRadius = 10;
simulatedKeyView.layer.borderColor = [UIColor colorWithRed:210.0/255.0 green:0 blue:253.0/255.0 alpha:1].CGColor;
simulatedKeyView.layer.borderWidth = 8;
simulatedKeyView.layer.cornerRadius = 5;
UILabel *simulatedKeyLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 30, 80, 50)];
simulatedKeyLabel.text = @"1";
simulatedKeyLabel.font = [UIFont systemFontOfSize:25];
simulatedKeyLabel.textAlignment = NSTextAlignmentCenter;
simulatedKeyLabel.textColor = [UIColor colorWithRed:210.0/255.0 green:0 blue:253.0/255.0 alpha:1];
// show a simulated space bar with a purple border to indicate that space will be pressed
UIView *simulatedSpacebarView = [[UIView alloc] initWithFrame:CGRectMake(120, 768-20-80, 210, 80)];
simulatedSpacebarView.layer.cornerRadius = 10;
simulatedSpacebarView.layer.borderColor = [UIColor colorWithRed:210.0/255.0 green:0 blue:253.0/255.0 alpha:1].CGColor;
simulatedSpacebarView.layer.borderWidth = 8;
simulatedSpacebarView.layer.cornerRadius = 5;
UILabel *simulatedSpaceBarLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 30, 210, 50)];
simulatedSpaceBarLabel.text = @"Space";
simulatedSpaceBarLabel.font = [UIFont systemFontOfSize:25];
simulatedSpaceBarLabel.textAlignment = NSTextAlignmentCenter;
simulatedSpaceBarLabel.textColor = [UIColor colorWithRed:210.0/255.0 green:0 blue:253.0/255.0 alpha:1];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, .5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[listSortingObjectPlaybackCell addSubview:simulatedKeyView];
[simulatedKeyView addSubview:simulatedKeyLabel];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, .25 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[listSortingObjectPlaybackCell addSubview:simulatedSpacebarView];
[simulatedSpacebarView addSubview:simulatedSpaceBarLabel];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, .75 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
// remove the simulated button
[simulatedKeyView removeFromSuperview];
[simulatedSpacebarView removeFromSuperview];
// simulate tap of the 1 key and then space bar
[self didTapOne];
[self didTapSpace];
});
});
}
else { // otherwise look to the automation testing manager to know whether to simulate the right or wrong answer
static NSInteger staticQuestionIndex_ZeroBased = 0;
static NSDictionary *staticParticipantDictionary = nil;
// if the participant has changed then reset the question index
if (staticParticipantDictionary != [[AutomatedTestManager sharedInstance] currentParticipantDictionary]) {
staticParticipantDictionary = [[AutomatedTestManager sharedInstance] currentParticipantDictionary];
staticQuestionIndex_ZeroBased = 0;
}
//determine whether the correct or incorrect answer should be selected
NSDictionary *currentParticipantDictionary = [AutomatedTestManager sharedInstance].currentParticipantDictionary;
NSString *assessmentResponsesString = currentParticipantDictionary[ASSESSMENT_RESPONSES];
if (isEmpty(assessmentResponsesString)) {
[[AutomatedTestManager sharedInstance] abortAutomatedTestWithMessage:@"Aborting automated test. Missing assessment responses."];
return;
}
NSArray *assessmentResponsesArray = [assessmentResponsesString componentsSeparatedByString:@"|"];
if (assessmentResponsesArray.count < staticQuestionIndex_ZeroBased) {
[[AutomatedTestManager sharedInstance] abortAutomatedTestWithMessage:@"Aborting automated test. Too few assessment responses."];
return;
}
NSString *responseToCurrentQuestion = assessmentResponsesArray[staticQuestionIndex_ZeroBased];
BOOL shouldAnswerCorrectly = [responseToCurrentQuestion boolValue];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, .25 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
// show a simulated key with a purple border to indicate that a 0 or 1 will be pressed
UIView *simulatedKeyView = [[UIView alloc] initWithFrame:CGRectMake(20, 768-20-80, 80, 80)];
simulatedKeyView.layer.cornerRadius = 10;
simulatedKeyView.layer.borderColor = [UIColor colorWithRed:210.0/255.0 green:0 blue:253.0/255.0 alpha:1].CGColor;
simulatedKeyView.layer.borderWidth = 8;
simulatedKeyView.layer.cornerRadius = 5;
[listSortingObjectPlaybackCell addSubview:simulatedKeyView];
UILabel *simulatedKeyLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 30, 80, 50)];
if (shouldAnswerCorrectly) {
simulatedKeyLabel.text = @"1";
}
else {
simulatedKeyLabel.text = @"0";
}
simulatedKeyLabel.font = [UIFont systemFontOfSize:25];
simulatedKeyLabel.textAlignment = NSTextAlignmentCenter;
simulatedKeyLabel.textColor = [UIColor colorWithRed:210.0/255.0 green:0 blue:253.0/255.0 alpha:1];
[simulatedKeyView addSubview:simulatedKeyLabel];
// simulate tap of the space bar
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, .75 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
// remove the simulated button
[simulatedKeyView removeFromSuperview];
if (shouldAnswerCorrectly) {
[self didTapOne];
}
else {
[self didTapZero];
}
});
});
// increment question counter
staticQuestionIndex_ZeroBased ++;
}
return;
}
if ([ListSortingItemHelper itemTypeForItem:self.currentItem] == ListSortingItemTypePractice) {
NSArray *feedback = [ListSortingItemHelper audioFeedbackForFinishedPlaybackOfItem:self.currentItem onTrialCount:_trialCount];
NSTimeInterval delay = 0;
for (NSString *filename in feedback) {
[self playLocalizedSound:filename afterDelay:delay];
delay = [self localizedSoundDuration:filename];
}
[self performSelector:@selector(enableKeyboard) withObject:nil afterDelay:delay];
} else {
_disableKeybaord = NO;
}
}
#pragma mark - process response
- (void)displayConfirmation
{
_isPresentingConfirmation = YES;
_instructionTimerDidFinish = NO;
// confirmation
ListSortingObject *object = [[ListSortingObject alloc] init];
object.displayText = [ListSortingAttributedStringHelper instructionAttributedStringForString:@"Are you ready?"];
object.instructionText = [ListSortingAttributedStringHelper adminInstructionAttributedStringForString:@"Press the Space Bar to continue"];
_objects = @[ object ];
// reload the collection view
[_collectionView performBatchUpdates:^{
[_collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
} completion:nil];
// if automated testing is enabled, get a reference to the cell that is currently visible
if ([[AutomatedTestManager sharedInstance] isTestingEnabled] && _collectionView.visibleCells.count > 0) {
UICollectionViewCell *cell = _collectionView.visibleCells[0];
// if it's an instructionWithDelayedResponse cell and the space bar is required to continue, then simulate a tap of the space bar
if ([cell isKindOfClass:[ListSortingInstructionWithDelayedResponseCell class]]) {
ListSortingInstructionWithDelayedResponseCell *listSortingInstructionWithDelayedResponseCell = (ListSortingInstructionWithDelayedResponseCell *)cell;
if ([listSortingInstructionWithDelayedResponseCell.instructionTextLabel.text caseInsensitiveCompare:@"press the space bar to continue"] == NSOrderedSame) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, .75 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
// highlight the border of this instruction text label in purple to indicate that a space bar press will be simulated
listSortingInstructionWithDelayedResponseCell.instructionTextLabel.layer.borderColor = [UIColor colorWithRed:210.0/255.0 green:0 blue:253.0/255.0 alpha:1].CGColor;
listSortingInstructionWithDelayedResponseCell.instructionTextLabel.layer.borderWidth = 10;
listSortingInstructionWithDelayedResponseCell.instructionTextLabel.layer.cornerRadius = 5;
// simulate tap of the space bar
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, .75 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self didTapSpace];
});
});
}
}
}
}
- (void)processResponse:(ListSortingKeyboardResponse)response
{
_trialCount++;
_disableKeybaord = YES; // disbale keyboard while feedback is playing
// audio feedback for practice items
NSTimeInterval delay = 0;
if ([ListSortingItemHelper itemTypeForItem:self.currentItem] == ListSortingItemTypePractice) {
NSArray *feedback = [ListSortingItemHelper audioFeedbackForResponse:response toItem:self.currentItem onTrialCount:_trialCount];
if (feedback) { // can return nil — max trial count (last chance) doesn't have feedback for incorrect answer
for (NSString *filename in feedback) {
[self playLocalizedSound:filename afterDelay:delay];
delay = [self localizedSoundDuration:filename];
}
}
}
if (response == ListSortingKeyboardResponseCorrect) {
// correct, submit the response
MSSMap *map = [ListSortingItemHelper mapForTrial:_trialCount onItem:self.currentItem];
[self performSelector:@selector(submitResponseWithMap:) withObject:map afterDelay:delay];
} else {
// incorrect
NSInteger maxTrials = [ListSortingItemHelper maxTrialsForItem:self.currentItem];
if (_trialCount >= maxTrials) {
// practice - if this was the final trail automatically submit the last map (failed a third time)
// live - this will be true for all live items (only two maps, right or wrong)
// no feedbcak is requied, so no delay, just end the test
MSSMap *map = [ListSortingItemHelper mapForTrial:(maxTrials + 1) onItem:self.currentItem];
[self submitResponseWithMap:map];
} else {
// if this wasn't the final trail allow the user to try again
BOOL lastChance = _trialCount == maxTrials - 1;
if (lastChance) {
// only replay the item again when it's the user's last chance
[self performSelector:@selector(displayItem:) withObject:self.currentItem afterDelay:delay];
} else {
// otherwise allow the administrator to enter another response
_keyboardResponse = ListSortingKeyboardResponseNone;
[self performSelector:@selector(enableKeyboard) withObject:nil afterDelay:delay];
}
}
}
}
- (void)submitResponseWithMap:(MSSMap *)map
{
// if it's a user's response, process it
if (map.ItemResponseOID.length) {
NSArray *itemsWithProcessedResponses = [self.engine processUserGeneratedResponses:@[ map.ItemResponseOID ] score:@(map.Description.integerValue) responseTime:0];
[self.delegate instrument:self.instrument didReceiveResponsesForItemsInArray:itemsWithProcessedResponses];
}
[self nextItem];
}
#pragma mark - key commands
- (NSArray *)keyCommands
{
UIKeyCommand *zeroKeyCommand = [UIKeyCommand keyCommandWithInput:@"0" modifierFlags:0 action:@selector(didTapZero)];
UIKeyCommand *oneKeyCommand = [UIKeyCommand keyCommandWithInput:@"1" modifierFlags:0 action:@selector(didTapOne)];
UIKeyCommand *spaceKeyCommand = [UIKeyCommand keyCommandWithInput:@" " modifierFlags:0 action:@selector(didTapSpace)];
UIKeyCommand *ctrlShiftQKeyCommand = [UIKeyCommand keyCommandWithInput:@"q" modifierFlags:UIKeyModifierControl|UIKeyModifierCommand action:@selector(didTapAdmin)];
return @[ zeroKeyCommand, oneKeyCommand, spaceKeyCommand, ctrlShiftQKeyCommand];
}
- (void)didTapZero
{
// incorrect
ListSortingItemType type = [ListSortingItemHelper itemTypeForItem:self.currentItem];
if ((type == ListSortingItemTypePractice || type == ListSortingItemTypeLive) && !_disableKeybaord) {
_keyboardResponse = ListSortingKeyboardResponseIncorrect;
if (type == ListSortingItemTypeLive) {
// show confirmation view
if (!_isPresentingConfirmation) {
[self displayConfirmation];
}
}
}
}
- (void)didTapOne
{
// correct
ListSortingItemType type = [ListSortingItemHelper itemTypeForItem:self.currentItem];
if ((type == ListSortingItemTypePractice || type == ListSortingItemTypeLive) && !_disableKeybaord) {
_keyboardResponse = ListSortingKeyboardResponseCorrect;
if (type == ListSortingItemTypeLive) {
// show confirmation view
if (!_isPresentingConfirmation) {
[self displayConfirmation];
}
}
}
}
- (void)didTapSpace
{
ListSortingItemType type = [ListSortingItemHelper itemTypeForItem:self.currentItem];
if (!_disableKeybaord) {
if (type == ListSortingItemTypeTitle) {
// the instrument did start after the title screen did advance
[self.delegate instrumentDidStart:self.instrument];
[self nextItem];
} else if (type == ListSortingItemTypeInstruction && _instructionTimerDidFinish) {
[self nextItem];
} else if (_keyboardResponse != ListSortingKeyboardResponseNone) {
if (type == ListSortingItemTypePractice) {
[self processResponse:_keyboardResponse];
} else if (type == ListSortingItemTypeLive && _isPresentingConfirmation && _instructionTimerDidFinish) {
[self processResponse:_keyboardResponse];
}
}
}
}
- (void)didTapAdmin
{
[self.delegate didReceiveAdminKeyCommandFromInstrument:self.instrument];
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)enableKeyboard
{
_disableKeybaord = NO;
}
#pragma mark - media utility
- (NSString *)localizedTextForKey:(NSString *)key
{
return [self localizedTextForKey:key fromBundle:self.bundle];
}
- (UIImage *)imageForImageName:(NSString *)imageName
{
return [self imageForImageName:imageName fromBundle:self.bundle];
}
- (void)playLocalizedSound:(NSString *)filename
{
[self playLocalizedSound:filename afterDelay:0];
}
- (void)playLocalizedSound:(NSString *)filename afterDelay:(NSTimeInterval)delay
{
[self playLocalizedSound:filename fromBundle:self.bundle afterDelay:delay];
}
- (NSTimeInterval)localizedSoundDuration:(NSString *)filename
{
return [self localizedSoundDuration:filename fromBundle:self.bundle];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment