Monday, August 29, 2011

Figuring out the next step

Ok, so, we're attempting to salvage something out of today after the nightmarish white table view issue. Really need to figure that one out. But, we've redone our changes and are back to where we started earlier today. That is, we now have an initial display which calls the second "quiz" display. That second quiz display is kind of a mockup at this point - it's displaying random data that doesn't have a whole lot to do with the problem.

All right. Well, the first order of business really has got to be figuring how the data is getting loaded into the table view.

This method might give an indication:


- (void) viewWillAppear:(BOOL)animated{

// NSLog(@"view will appear was called");

// won't be animated on the first call
if (animated) {
appState.currentQuestionNumber++;
[appState saveState];
}

currentQuestion = appState.currentQuestion;

answers = [currentQuestion answerArray];

// triggers reload, see veiwDidLoad method
[tableView reloadData];
}


So, the question and answers will somehow be pulled in into the question display at the top and the answers. Let's keep going.

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {

QuizAppDelegate *delegate =
(QuizAppDelegate *)[[UIApplication sharedApplication] delegate];

// get answers

appState = delegate.appState;

currentQuestion = appState.currentQuestion;

answers = [currentQuestion answerArray];

// change this to a lable above the table
self.title = @"JlptQuizApp";

tableView.rowHeight = 200;

[super viewDidLoad];
}


I'm not sure why I loaded it twice. Either it's a mistake, or one of them is a one-off.

This one looks interesting:

- (UITableViewCell *)tableView:(UITableView *)tv
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

// get the table veiw cell. Alwasy use the dequeue method
UITableViewCell *cell =
[ tv dequeueReusableCellWithIdentifier:@"cell"];


if( nil == cell ) {

// this sets up a resusable table cell
cell = [ [[UITableViewCell alloc]
initWithFrame:CGRectZero reuseIdentifier:@"cell"] autorelease];

// support line break mode for multiline
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;

// 0 means any number of lines - necessary for multiline
cell.textLabel.numberOfLines = 0;

// set it up with a consistent font with the height calculation (see below)
cell.textLabel.font = [UIFont fontWithName:@"Helvetica" size:30.0];
}


// set the answer

if (indexPath.row < answers.count ) {

NSString *answer = [answers objectAtIndex:indexPath.row];
cell.textLabel.text = answer;
}

return cell;

}


So, if I were to make a rough estimate, it looks like it's using the index of the row to access the corresponding row in the array, and then sets it on the table display.

How about the question?


Ah - here it is:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

// Create label with section title
headerLabel = [[[UILabel alloc] init] autorelease];
headerLabel.textAlignment = UITextAlignmentCenter;
headerLabel.frame = CGRectMake(20, 6, 300, 30);
headerLabel.backgroundColor = [UIColor clearColor];
headerLabel.textColor = [UIColor whiteColor];
headerLabel.font = [UIFont fontWithName:@"Helvetica-BoldOblique" size:45.0];
// support line break mode for multiline
headerLabel.lineBreakMode = UILineBreakModeWordWrap;

// 0 means any number of lines - necessary for multiline
headerLabel.numberOfLines = 0;
headerLabel.text = currentQuestion.questionTxt; <==== set here

// very important!
[headerLabel sizeToFit];

// Create header view and add label as a subview
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, SectionHeaderHeight)];
view.backgroundColor = [UIColor grayColor];
[view autorelease];
[view addSubview:headerLabel];


return view;
}



So, there's nothing to mess with in the interface here. It's just setting up the questions and answers.

So, how do they get set up? Coming in from the other side, we know we have an XMLRead class. It parses the XML and creates some kind of Array of Question object, which probably contains a question and an array of answers.

Here's some of the code:


Here it sets up the tags and the AppState object:

- (id)init
{
// Call the superclass's designated initializer
self = [super init];

// Did the superclass's initialization fail?
if (!self)
return nil;

appState = [[[AppState alloc] init] autorelease];

/* XML Tag Names */
XML_TAG_QUESTION_BLOCK = @"questions";

XML_TAG_QUESTION = @"question";

XML_TAG_QUESTION_ATTRIBUTE_NUMBER = @"number";
XML_TAG_QUESTION_ATTRIBUTE_TEXT = @"text";
XML_TAG_QUESTION_ATTRIBUTE_CORRECT_ANSWER = @"correct_answer";

XML_TAG_ANSWERS = @"answers";
XML_TAG_ANSWER = @"answer";
XML_TAG_ANSWER_ATTRIBUTE_TEXT = @"text";

// Return the address of the newly initialized object
return self;
}



The key in the read is this method.

- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{

if ([elementName isEqualToString:XML_TAG_QUESTION]){

// answer counter - initialize for new question
cntr = 1;

// allocate the question
question = [[Question alloc] init];

// get the question text
question.questionTxt = [attributeDict valueForKey:XML_TAG_QUESTION_ATTRIBUTE_TEXT]; // XML_TAG_QUESTION_ATTRIBUTE_TEXT

// get get the question number
NSString *text = [attributeDict valueForKey:XML_TAG_QUESTION_ATTRIBUTE_NUMBER];

// convert from the text value
question.number = [text intValue];

// Now get the question answer
text = [attributeDict valueForKey:XML_TAG_QUESTION_ATTRIBUTE_CORRECT_ANSWER];

// add the correct answer after converting it to an int
question.correctAnswer = [text intValue];

// add it to the question
[appState addQuestion: question];

[question release];

}

if ([elementName isEqualToString:XML_TAG_ANSWER]){

// convert counter to an object for storage in dictionary
NSNumber *counter = [NSNumber numberWithInt:cntr];

// Example of format string
// NSString *counter = [NSString stringWithFormat: @"%d", cntr];

// Get get the answer from the question
NSString *answer = [attributeDict valueForKey:XML_TAG_ANSWER_ATTRIBUTE_TEXT];

// example of NSLog
//NSLog(@"question, number is %i, text is %@, obj is %@", question.number, question.questionTxt, question);

// add the question to the answer dictionary
[question.answerHash setObject: answer
forKey: counter ];

// increment counter
cntr++;

}

}



So, it's creating the question, a number that indicates which is the correct answer, and each question has an answer hash. AppState in turn keeps some kind of a collection of questions.

So, what the app looks like it's doing is accessing the AppState collection of questions each time the question controller displays its view, and displaying it on it's page. It's probably incrementing a counter so that the question it displays each time is different.

If we go to the main app delegate, we'll see how this reading process is initiated:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

// Override point for customization after application launch.

NSString *xmlFilePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"questions.xml"];

NSString *xmlFileContents = [NSString stringWithContentsOfFile: xmlFilePath encoding:NSUTF8StringEncoding error:nil];

NSData *data = [NSData dataWithBytes:[xmlFileContents UTF8String] length:[xmlFileContents lengthOfBytesUsingEncoding: NSUTF8StringEncoding]];


XMLReader *xmlReader = [[XMLReader alloc] init];

[xmlReader parseXMLData: data];

self.appState = xmlReader.appState;

appState.currentQuestionNumber = 1;

[xmlReader release];

currentQuestion = [appState currentQuestion];

//navController.viewControllers = [NSArray arrayWithObject:questonViewController];
navController.viewControllers = [NSArray arrayWithObject:startController];

[window addSubview:navController.view];
[self.window makeKeyAndVisible];


return YES;
}




So, this section in particular does the parsing and setting up:

XMLReader *xmlReader = [[XMLReader alloc] init];

[xmlReader parseXMLData: data];

self.appState = xmlReader.appState;

appState.currentQuestionNumber = 1;

[xmlReader release];


Now, when it gets to the QuestionController, it initiates its own variables with the current question:

- (void) viewWillAppear:(BOOL)animated{


// won't be animated on the first call
if (animated) {
appState.currentQuestionNumber++;
[appState saveState];
}

currentQuestion = appState.currentQuestion;

answers = [currentQuestion answerArray];

// triggers reload, see veiwDidLoad method
[tableView reloadData];
}



Which are later used to initialize the display.

Now, the only remaining question is, what increments the question number? That's probably done in the question controller:


// won't be animated on the first call
if (animated) {
appState.currentQuestionNumber++;
[appState saveState];
}


Ok. So, the next challenge is to switch from the XML input to an SQLLite database input. We'll tackle that in the next post.

No comments:

Post a Comment