I have a revision quiz app in the App store that has been receiving around 10 downloads per day for the last 3 months. It's quite well used and for the most part has good reviews. However, recently I've had two 1 star reviews because people are complaining that the "Review" screen is blank after the quiz ends.
I've tried this on iPhone 5/5c/5s and it works perfectly well. These are the only devices I have access to, but I've also run the simulator for iPad and both iPhone 6 versions and I can't replicate the problem.
The flow is RootController->QuizController->ResultsController->ReviewController. The quiz controller has an object called GameManager as an instance variable. This tracks the user answers and questions as the quiz progresses, and is passed from the Quiz to the Results and then to the Review section during segues.
The functions that pass the object are:
//From Quiz to Results
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"quiz2results"])
{
// Get reference to the destination view controller
ResultsController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
[vc setManager:_manager];
}
}
//From Results to Review
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"results2review"])
{
// Get reference to the destination view controller
ReviewController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
[vc setManager:_manager];
}
}
Then in the Review controller the following code is executed:
-(void)setReviewText{
NSArray* questions = [[NSArray alloc] init];
questions = [_manager getAllQuestions];
NSMutableArray* userAnswer = [[NSMutableArray alloc] init];
userAnswer = [self answersAttributed];
if (userAnswer == nil)
{
_output.text = #"Error";
}
NSMutableAttributedString *mutableAttString = [[NSMutableAttributedString alloc] init];
int i = 0;
NSAttributedString *linebreak =[[NSAttributedString alloc] initWithString:#"\n"];
for (Question* q in questions)
{
if (i == [userAnswer count])
break;
NSAttributedString *qtext =[[NSAttributedString alloc] initWithString:q.questionText];
NSAttributedString *ctext =[[NSAttributedString alloc] initWithString:#"Correct Answer:\n"];
NSAttributedString *atext =[[NSAttributedString alloc] initWithString:q.answerText];
NSAttributedString *ytext =[[NSAttributedString alloc] initWithString:#"Your Answer:\n"];
NSAttributedString *utext =[[NSAttributedString alloc] initWithAttributedString:userAnswer[i]];
[mutableAttString appendAttributedString:qtext];
[mutableAttString appendAttributedString:linebreak];
[mutableAttString appendAttributedString:ctext];
[mutableAttString appendAttributedString:atext];
[mutableAttString appendAttributedString:linebreak];
[mutableAttString appendAttributedString:ytext];
[mutableAttString appendAttributedString:utext];
[mutableAttString appendAttributedString:linebreak];
[mutableAttString appendAttributedString:linebreak];
i++;
}
NSAttributedString* outText = [[NSAttributedString alloc] init];
outText = [mutableAttString mutableCopy];
_output.attributedText = outText;
[_output setFont:[UIFont preferredFontForTextStyle:UIFontTextStyleBody]];
}
-(NSMutableArray*)answersAttributed
{
NSMutableArray* ansAtt = [[NSMutableArray alloc] init];
NSArray* questions = [[NSArray alloc] init];
questions = [_manager getAllQuestions];
NSArray* userAnswers = [[NSArray alloc] init];
userAnswers = [_manager getAllUserAnswers];
if ([questions count] != [userAnswers count])
{
return ansAtt;
}
int i = 0;
for (NSString* userAnswer in userAnswers )
{
if([[questions[i] answerText] isEqualToString:userAnswer] )
{
NSDictionary *attributes = #{NSBackgroundColorAttributeName:[UIColor greenColor]};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:userAnswer attributes:attributes];
[ansAtt addObject:attrString];
}
else
{
NSDictionary *attributes = #{NSBackgroundColorAttributeName:[UIColor redColor]};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:userAnswer attributes:attributes];
[ansAtt addObject:attrString];
}
i++;
}
return ansAtt;
}
If anybody could shed some light on this I'd be delighted. The cause of this problem has alluded me so far.
Related
NSMutableArray *arrayDetails = [[NSMutableArray alloc] init];
CustomClass *country1 = [[CustomClass alloc] init];
country1.strCountryName = #"USA";
country1.code = #"12";
[arrayDetails addObject:country1];
CustomClass *country2 = [[CustomClass alloc] init];
country2.strCountryName = #"India";
country2.code = #"234";
[arrayDetails addObject:country2];
CustomClass *country4 = [[CustomClass alloc] init];
country4.strCountryName = #"UK";
country4.code = #"34";
[arrayDetails addObject:country4];
CustomClass *country5 = [[CustomClass alloc] init];
country5.strCountryName = #"USA";
country5.code = #"12";
[arrayDetails addObject:country5];
CustomClass *country6 = [[CustomClass alloc] init];
country6.strCountryName = #"India";
country6.code = #"12";
[arrayDetails addObject:country6];
CustomClass *country7 = [[CustomClass alloc] init];
country7.strCountryName = #"India";
country7.code = #"12";
[arrayDetails addObject:country7];
CustomClass *country8 = [[CustomClass alloc] init];
country8.strCountryName = #"USA";
country8.code = #"12";
[arrayDetails addObject:country8];
CustomClass *country3 = [[CustomClass alloc] init];
country3.strCountryName = #"UK";
country3.code = #"12";
[arrayDetails addObject:country3];
CustomClass *country77 = [[CustomClass alloc] init];
country77.strCountryName = #"PAK";
country77.code = #"12";
[arrayDetails addObject:country3];
I want to make separate sets based on country name, all custom class objects which contain the USA in one array, India in the array and UK one array, even single object PAK.
Data comes from the server, based on the strContry name I want to make groups.
After adding all objects to "arraydetails", try the below code for achieving the same.
NSArray *countryNames = [[NSOrderedSet orderedSetWithArray:[arrayDetails valueForKey:#"strCountryName"]] array];
for (int i =0; i < [countryNames count]; i++) {
NSMutableArray *countryArray = [[NSMutableArray alloc] init];
for (CustomClass *country in arrayDetails) {
if ([country.strCountryName isEqualToString:[countryNames objectAtIndex:i]]) {
[countryArray addObject:country];
}
}
NSLog(#"Country array[%d] is %#", i, countryArray);
}
I have a template quiz application that has been on the app store in various guises for a while now, in general it receives good reviews and had no bug reports.
Recently I've had two bug reports, people using iPads with iOS 8.1.2 or 8.1.3, saying that now and again the UITextView that I use to show the questions is blank.
I've not been able to replicate this bug, but I would be grateful if someone could shed some light on it.
The objects questions and userAnswer are not nil so it is definitely a UITextView issue.
The Controller is here:
#interface ReviewController ()
#property (strong, nonatomic) IBOutlet UITextView *output;
#end
#implementation ReviewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setReviewText];
// Do any additional setup after loading the view.
}
-(void)setReviewText{
NSArray* questions = [[NSArray alloc] init];
questions = [_manager getAllQuestions];
NSMutableArray* userAnswer = [[NSMutableArray alloc] init];
userAnswer = [self answersAttributed];
if(questions == nil)
_output.text = #"Questions Error";
if (userAnswer == nil)
{
_output.text = #"Error";
}
NSMutableAttributedString *mutableAttString = [[NSMutableAttributedString alloc] init];
int i = 0;
NSAttributedString *linebreak =[[NSAttributedString alloc] initWithString:#"\n"];
for (Question* q in questions)
{
if (i == [userAnswer count])
break;
NSAttributedString *qtext =[[NSAttributedString alloc] initWithString:q.questionText];
NSAttributedString *ctext =[[NSAttributedString alloc] initWithString:#"Correct Answer:\n"];
NSAttributedString *atext =[[NSAttributedString alloc] initWithString:q.answerText];
NSAttributedString *ytext =[[NSAttributedString alloc] initWithString:#"Your Answer:\n"];
NSAttributedString *utext =[[NSAttributedString alloc] initWithAttributedString:userAnswer[i]];
[mutableAttString appendAttributedString:qtext];
[mutableAttString appendAttributedString:linebreak];
[mutableAttString appendAttributedString:ctext];
[mutableAttString appendAttributedString:atext];
[mutableAttString appendAttributedString:linebreak];
[mutableAttString appendAttributedString:ytext];
[mutableAttString appendAttributedString:utext];
[mutableAttString appendAttributedString:linebreak];
[mutableAttString appendAttributedString:linebreak];
i++;
}
NSAttributedString* outText = [[NSAttributedString alloc] init];
outText = [mutableAttString mutableCopy];
_output.attributedText = outText;
[_output setFont:[UIFont preferredFontForTextStyle:UIFontTextStyleBody]];
}
-(NSMutableArray*)answersAttributed
{
NSMutableArray* ansAtt = [[NSMutableArray alloc] init];
NSArray* questions = [[NSArray alloc] init];
questions = [_manager getAllQuestions];
NSArray* userAnswers = [[NSArray alloc] init];
userAnswers = [_manager getAllUserAnswers];
if ([questions count] != [userAnswers count])
{
return ansAtt;
}
int i = 0;
for (NSString* userAnswer in userAnswers )
{
if([[questions[i] answerText] isEqualToString:userAnswer] )
{
NSDictionary *attributes = #{NSBackgroundColorAttributeName:[UIColor greenColor]};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:userAnswer attributes:attributes];
[ansAtt addObject:attrString];
}
else
{
NSDictionary *attributes = #{NSBackgroundColorAttributeName:[UIColor redColor]};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:userAnswer attributes:attributes];
[ansAtt addObject:attrString];
}
i++;
}
return ansAtt;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"review2results"])
{
// Get reference to the destination view controller
ReviewController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
[vc setManager:_manager];
}
}
With autolayout, if you don't have enough constraints to fully specify size and position, then sometimes you'll see and and sometimes you won't. The unspecified constraints can have random values, like 0 height or off screen position.
Check to make sure the constraints are fully specified.
In my case textview marked as Non-Editable and Non-Selectable in Storyboard have the same strange behavior on iOS 8.1 (no problems in iOS 9+).
- (void)viewDidLoad {
[super viewDidLoad];
textview.attributedText = [[NSAttributedString alloc] initWithString:#"non-nil"]];
value = textview.attributedText;
//^ value is nil !!!
}
Fixed with this:
- (void)viewDidLoad {
[super viewDidLoad];
textview.editable = YES;
textview.attributedText = [[NSAttributedString alloc] initWithString:#"non-nil"]];
textview.editable = NO;
value = textview.attributedText;
//^ value is #"non-nil" now !!!
}
Now I create is as follow (my file.h):
UIImageView *pic1, *pic2, *pic3, *pic4, *pic5, *pic6, *pic7, *pic8, *pic9, *pic10;
Then in my (file.m):
UIImageView *pic1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#”picName.png”]];
UIImageView *pic2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#”picName.png”]];
……
UIImageView *pic10 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#”picName.png”]];
I need many instances of UIImageView (number triggered by other factors) of only one in this case picture.
Is there any way to create multiple instances of UIImageView automatically in my file.m, somehow as follow?:
for (int x; (x=10); x++)
{
UIImageView * pic[x] = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"myPic.png"]];
}
This example doesn’t work but I’d like to show what I want to program.
Of course you can - that is what the arrays are for:
NSMutableArray *pics = [NSMutableArray array];
for (int i = 0 ; i != 10 ; i++) {
[pics addObject:[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"myPic.png"]]];
}
In case the name of the picture depends on the index, use NSString's stringWithFormat to produce the name of the picture - for example, you can do it like this:
NSMutableArray *pics = [NSMutableArray array];
for (int i = 0 ; i != 10 ; i++) {
NSString *imgName = [NSString stringWithFormat:#"myPic%d.png"];
[pics addObject:[[UIImageView alloc] initWithImage:[UIImage imageNamed:imgName]]];
}
If the names of images follow a standard pattern you can just loop over the required amount and build the image name dynamically using the index.
If the names of the images do not follow a standard patten you can chuck them in an array and loop over them
NSArray *imageNames = #[
#"alarm.jpg",
#"bell.jpg",
#"car.jpg"
];
NSMutableArray *imageViews = [[NSMutableArray alloc] init];
for (NSString *imageName in imageNames) {
[imagesViews addObject:({
[[UIImageView alloc] initWithImage:[UIImage imageNamed:imageName]];
})];
}
Completely optional - further reading
You could write yourself a simple category that abstracts this looping and collecting the results into a new array. This would allow you to simply write
NSArray *imageNames = #[
#"alarm.jpg",
#"bell.jpg",
#"car.jpg"
];
NSArray *imageViews = [imageNames pas_arrayByMappingWithBlock:^(id obj){
return [[UIImageView alloc] initWithImage:[UIImage imageNamed:imageName]];
}];
The category to do that would look something like this:
#interface NSArray (PASAdditions)
- (NSArray *)pas_arrayByMappingWithBlock:(id (^)(id obj))block
#end
#implementation NSArray (PASAdditions)
- (NSArray *)pas_arrayByMappingWithBlock:(id (^)(id obj))block
{
NSMutableArray *result = [[NSMutableArray alloc] init];
for (id obj in self) {
[result addObject:block(obj)];
}
return [result copy];
}
#end
I got the following problem: The labels always change to the ones definied in tag 9001. Could someone help me spot my error?
ViewController.m:
- (IBAction)switch0:(id)sender
{(button.tag = 9001);
UIButton *buttonPressed = (UIButton *)sender;
SecondViewController *second =[[SecondViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:second animated:YES];
second.buttonTag = buttonPressed.tag;
}
- (IBAction)switch2:(id)sender2
{ (button2.tag = 9002);
UIButton *buttonPressed = (UIButton *)sender2;
SecondViewController *third =[[SecondViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:third animated:YES];
second.buttonTag = buttonPressed.tag;
}
SecondViewcontroller.m
- (void)viewDidLoad
{
[super viewDidLoad];
if (buttonTag == 9001) {
self.label1.text = [[NSString alloc] initWithFormat:#"Radnomtext"];
self.label2.text = [[NSString alloc] initWithFormat:#"Randomtext"];
self.label3.text = [[NSString alloc] initWithFormat:#"Randomtext?"];
}
if (buttonTag == 9002) {
self.label1.text = [[NSString alloc] initWithFormat:#"Radnomtext2"];
self.label2.text = [[NSString alloc] initWithFormat:#"Randomtext2"];
self.label3.text = [[NSString alloc] initWithFormat:#"Randomtext2?"];
Heres an example and your presenting the view twice I took out the modal in this example:
- (IBAction)switch0:(id)sender
{
UIButton *buttonPressed = (UIButton *)sender;
SecondViewController *second =[[SecondViewController alloc] initWithNibName:nil bundle:nil];
NSInteger tagToSet = 9001;
second.buttonTag = tagToSet;;
[self.navigationController pushViewController:second animated:YES];
}
and using the keyword switch is no good. It is a reserved word in Objective C.
Maybe because you forgot to close the ending bracket for first if?
if (buttonTag == 9001) {
self.label1.text = [[NSString alloc] initWithFormat:#"Radnomtext"];
self.label2.text = [[NSString alloc] initWithFormat:#"Randomtext"];
self.label3.text = [[NSString alloc] initWithFormat:#"Randomtext?"];
} // bracket supposed to be here
if (buttonTag == 9002) {
or maybe you set buttonTag to incorrect instance?
- (IBAction)switch2:(id)sender2
{
UIButton *buttonPressed = (UIButton *)sender2;
SecondViewController *third =[[SecondViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:third animated:YES];
second.buttonTag = buttonPressed.tag; // supposed to be third?
[self.navigationController pushViewController:third animated:YES];
(button2.tag = 9002);
}
And to be safe, you should set buttonTag before presentModalViewController.
UPDATE: Figured out some stuff and changed code.
When I add my NSDictionary to my array it suddenly replaces the previous dictionary I added last time. I don't know why this is happening. I am using a plist as data storage.
I get a error message like this:
Thread 1:Program received signal: "EXC_BAD_ACCESS".
Init
-(id)init{
self=[super init];
if(self){
dbArray = [[NSMutableArray alloc] init];
}
return self;
}
Adding a new item.
-(void)addNewItem:(NSString *)aString
{
// Creates a mutable dictionary with a anonymous string under the NAME key.
NSDictionary *newString = [[NSDictionary alloc] initWithObjectsAndKeys:aString,#"name", nil];
// Adds the new string to empty dbArray.
[dbArray addObject:(newString)];
NSLog(#"[add]:Added anonymous string to dbArray, under name key.");
// Writes the current dbArray (with the dict) to plist and releases retain counts.
[self writeItem];
[newString release];
}
My method to view my data.
-(void)viewData
{
// View data from the created plist file in the Documents directory.
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *finalPath = [documentsDirectory stringByAppendingPathComponent:#"data.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:finalPath]) {
self.dbArray = [NSMutableArray arrayWithContentsOfFile:finalPath];
}
else {
self.dbArray = [NSMutableArray array];
}
}
instead this
self.dbArray = [[NSMutableArray alloc] init];
use this
if( nil == self.dbArray ) {
self.dbArray = [[NSMutableArray alloc] init];
}
UPDATE: (based on provided code)
you're using different instances of DataObject class for displaying & saving data. Your content is over-written, because you don't load data from file during initialization of each instance; to fix that fast, you need to implement init method of your DataObject class as below:
- (id)init{
self = [super init];
if(self){
[self viewData];
}
return self;
}
the following code from viewDidLoad of ViewController class will crash your application very often:
db = [[DataObject alloc] init];
[db viewData];
[db release];
array = [[NSMutableArray alloc] initWithArray:[db dbArray]];
replace it with
db = [[DataObject alloc] init];
[db viewData];
array = [[NSMutableArray alloc] initWithArray:[db dbArray]];
call [db release] only in dealloc implementation
another problem, that you'll probably arise - is updated data is not displayed when you're back to the main screen; to fix that add the following method implementation to your ViewController.m file:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[db viewData];
self.array = [NSMutableArray arrayWithArray: db.dbArray];
[self.tableView reloadData];
}
also in AddView.m replace the following code
// Dismiss view and reload tableview.
ViewController *vc = [[ViewController alloc] init];
[self dismissModalViewControllerAnimated:YES];
[vc release];
with
// Dismiss view and reload tableview.
[self dismissModalViewControllerAnimated:YES];
Just as advise: see more information about using delegates and passing object instances & copies between objects.
I think you are creating a new Array:
self.dbArray = [[NSMutableArray alloc] init];
You should create the dbArray on the viewDidLoad or on the init of your UIViewController (I am assuming you are using this on an UIViewController)
inside your DataObject do the following:
-(id)init{
self=[super init];
if(self){
self.dbArray = [[NSMutableArray alloc] init];
}
return self;
}