2

I've been trying to figure out the correct model for this new app of mine. Here's the thing. I need to create an exam type app. The app will have a "Grammar" section which requires a paragraph with fill in the blanks approach. So it will look like this.

    "Lorem ipsum dolor sit amet, _________________ , sed do eiusmod tempor 
     _______ ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud 
     exercitation ullamco _________________ ex ea commodo consequat. Duis aute 
     irure dolor in reprehenderit in voluptate velit esse _______ dolore eu fugiat nulla 
     pariatur. ____________ sint occaecat cupidatat non proident, sunt in culpa qui   
     officia deserunt mollit anim id est laborum."

Now, the paragraph is coming from the server. We mark the blanks as {#} so when I display it on the app I will replace it with "____" w/c is equivalent to a uitextfield. The problem is I've no idea how to go about this. Can I put everything in a uitextview? how should I deal with the blanks? And I dont think I can do this with uiwebview coz I need the update the contents of the blanks once I tap on them. Please help.

Prince John Wesley
  • 62,492
  • 12
  • 87
  • 94
Diffy
  • 735
  • 1
  • 15
  • 34

3 Answers3

2

My requirement was also same, but I solved it in native code, with UILabel & UIView, all of it done through pragmatically within a loop. Consider the below text.

Once upon a time there _____  a man called Damocles. A friend of his eventually _____  the ruler of a small city. Damocles thought, ‘How lucky my friend ____. He ____  now a ruler. He must ____  a great time. 

In which each work is treated as UILabel empty spaces treated as UIView. The correct options are in ordered list as show below.

(lldb) po self.card.questionDataModel.fillAnswers
<__NSArrayI 0x6000008798c0>(
lived,
became,
is,
is,
must be having
)

Check out code.

This methods renders entire UI for empty spaces and its options

    - (void)prepareUIForFillInBlankQuestionType {

    self.totalOptionCount = self.card.questionDataModel.fillAnswers.count;

    CGFloat leading = 8;
    CGFloat trailing = 8;

    if ([NSRUtilities isNilOREmptyString:self.card.questionDataModel.question] == NO) {

        NSAttributedString* titleAttributedString = [[ConstantTools sharedData] convertHTMLToString:self.card.questionDataModel.question];
        NSString* titleString = titleAttributedString.string;

        // Holds all blank UIView (empty spaces)
        NSMutableArray* blankViewContainerList = [NSMutableArray new];

        // Full string will be separate by spaces so that each work can for single instance of UILabel
        NSArray* blankSpaceSeperatedList = [titleString componentsSeparatedByString:@" "];

        CGFloat fillViewWidth = CGRectGetWidth(self.fillBlankContainerView.frame);

        CGFloat labelX = 8;
        CGFloat labelY = 8;
        CGFloat labelHeight = 25;

        NSInteger underscoreIndex = 0;
        for (NSString* text in blankSpaceSeperatedList) {

            CGFloat fillContainerWidth = (fillViewWidth - (trailing + leading));

            // inputs NSString instance, will return back its UILabel for all words in a sentence
            UILabel* textLabel = [self labelFromText:text];

            if ((labelX + textLabel.frame.size.width) < fillContainerWidth) {

                if ([text containsString:@"__"]) {

                    CGFloat answerWidth = CGRectGetWidth(self.fillBlankContainerView.frame) *  FILL_FRAME_RATIO; // 0.4

                    UIView* blankView = [[UIView alloc] initWithFrame:CGRectMake(labelX,
                                                                                 labelY,
                                                                                 answerWidth,
                                                                                 labelHeight)];

                    CALayer* bottomLayer = [CALayer layer];
                    [bottomLayer setFrame:CGRectMake(0, labelHeight - 1, CGRectGetWidth(blankView.frame), 1)];
                    [bottomLayer setBackgroundColor:[UIColor whiteColor].CGColor];
                    [blankView.layer addSublayer:bottomLayer];

                    [blankView setTag:(600 + underscoreIndex)];
                    underscoreIndex++;

                    if (labelX + CGRectGetWidth(blankView.frame) >= fillContainerWidth) {
                        labelX = leading;
                        labelY += labelHeight;
                        [blankView setFrame:CGRectMake(labelX, labelY, answerWidth, labelHeight)];
                    }
                    [self.fillBlankContainerView addSubview:blankView];

                    [blankViewContainerList addObject:blankView];

                    labelX += answerWidth;
                }
                else {
                    CGRect labelFrame = textLabel.frame;
                    labelFrame.origin.x = labelX;
                    labelFrame.origin.y = labelY;
                    [textLabel setFrame:labelFrame];
                    [self.fillBlankContainerView addSubview:textLabel];

                    labelX += textLabel.frame.size.width;
                }

            }
            else{
                labelX = leading;
                labelY += labelHeight;

                if ([text containsString:@"__"]) {

                    CGFloat answerWidth = CGRectGetWidth(self.fillBlankContainerView.frame) *  FILL_FRAME_RATIO; 

                    UIView* blankView = [[UIView alloc] initWithFrame:CGRectMake(labelX,
                                                                                 labelY,
                                                                                 answerWidth,
                                                                                 labelHeight)];

                    CALayer* bottomLayer = [CALayer layer];
                    [bottomLayer setFrame:CGRectMake(0, labelHeight - 1, CGRectGetWidth(blankView.frame), 1)];
                    [bottomLayer setBackgroundColor:[UIColor whiteColor].CGColor];
                    [blankView.layer addSublayer:bottomLayer];

                    [blankView setTag:(600 + underscoreIndex)];
                    underscoreIndex++;

                    if (labelX + CGRectGetWidth(blankView.frame) >= fillContainerWidth) {
                        labelX = leading;
                        labelY += labelHeight;
                        [blankView setFrame:CGRectMake(labelX, labelY, answerWidth, labelHeight)];
                    }

                    [self.fillBlankContainerView addSubview:blankView];
                    [blankViewContainerList addObject:blankView];
                    labelX += answerWidth;

                }
                else {

                    CGRect labelFrame = textLabel.frame;
                    labelFrame.origin.x = labelX;
                    labelFrame.origin.y = labelY;
                    [textLabel setFrame:labelFrame];
                    [self.fillBlankContainerView addSubview:textLabel];

                    labelX += textLabel.frame.size.width;
                }
            }
        }

        self.blankViewContaninerList = blankViewContainerList;


        // It has all the options which will dragged on empty spaces
        NSMutableArray* optionList = [NSMutableArray new];

        if ([NSRUtilities isNilOREmptyString:self.card.questionDataModel.A] == NO) {
            NSAttributedString* attributedStringA = [[ConstantTools sharedData] convertHTMLToString:self.card.questionDataModel.A];
            NSString* optionA = [NSRUtilities trimNewlineCharacterFromString:attributedStringA.string];
            [optionList addObject:optionA];
        }


        if ([NSRUtilities isNilOREmptyString:self.card.questionDataModel.B] == NO) {
            NSAttributedString* attributedStringB = [[ConstantTools sharedData] convertHTMLToString:self.card.questionDataModel.B];
            NSString* optionB = [NSRUtilities trimNewlineCharacterFromString:attributedStringB.string];
            [optionList addObject:optionB];
        }


        if ([NSRUtilities isNilOREmptyString:self.card.questionDataModel.C] == NO) {
            NSAttributedString* attributedStringC = [[ConstantTools sharedData] convertHTMLToString:self.card.questionDataModel.C];
            NSString* optionC = [NSRUtilities trimNewlineCharacterFromString:attributedStringC.string];
            [optionList addObject:optionC];
        }


        if ([NSRUtilities isNilOREmptyString:self.card.questionDataModel.D] == NO) {
            NSAttributedString* attributedStringD = [[ConstantTools sharedData] convertHTMLToString:self.card.questionDataModel.D];
            NSString* optionD = [NSRUtilities trimNewlineCharacterFromString:attributedStringD.string];
            [optionList addObject:optionD];
        }


        if ([NSRUtilities isNilOREmptyString:self.card.questionDataModel.E] == NO) {
            NSAttributedString* attributedStringE = [[ConstantTools sharedData] convertHTMLToString:self.card.questionDataModel.E];
            NSString* optionE = [NSRUtilities trimNewlineCharacterFromString:attributedStringE.string];
            [optionList addObject:optionE];
        }

        CGFloat optionsYPoint = labelY + 50;

        for (NSInteger optionIndex = 0 ; optionIndex < optionList.count ; optionIndex++) {

            NSString* option = optionList[optionIndex];
            UILabel* optionLabel = [self optionLabelFromText:option index:optionIndex];

            if (optionIndex % 2 == 0) {
                // Fall out on left side

                CGFloat centerPoint = (CGRectGetWidth(self.fillBlankContainerView.frame) / 4);
                CGFloat optionsCenterPoint = (CGRectGetWidth(optionLabel.frame) / 2);

                CGFloat x = centerPoint - optionsCenterPoint;
                CGFloat y = optionsYPoint;
                CGFloat width = CGRectGetWidth(optionLabel.frame);
                CGFloat height = 25;

                [optionLabel setFrame:CGRectMake(x, y, width, height)];
                [self.fillBlankContainerView addSubview:optionLabel];

                // struct optionRect
                optionLabel.tag                     = optionIndex;
                optionRect[optionIndex].origin      = optionLabel.frame.origin;
                optionRect[optionIndex].size        = optionLabel.frame.size;
                optionRect[optionIndex].center      = optionLabel.center;

            }
            else {
                // Fall out on right side

                CGFloat centerPoint = CGRectGetWidth(self.fillBlankContainerView.frame) -(CGRectGetWidth(self.fillBlankContainerView.frame) / 4);
                CGFloat optionsCenterPoint = (CGRectGetWidth(optionLabel.frame) / 2);

                CGFloat x = centerPoint - optionsCenterPoint;
                CGFloat y = optionsYPoint;
                CGFloat width = CGRectGetWidth(optionLabel.frame);
                CGFloat height = 25;

                [optionLabel setFrame:CGRectMake(x, y, width, height)];
                [self.fillBlankContainerView addSubview:optionLabel];

                // struct optionRect
                optionLabel.tag                     = optionIndex;
                optionRect[optionIndex].origin      = optionLabel.frame.origin;
                optionRect[optionIndex].size        = optionLabel.frame.size;
                optionRect[optionIndex].center      = optionLabel.center;

                optionsYPoint += 40;
            }
        }      
    }
}

These methods will prepare UILabel out NSString

- (UILabel*)labelFromText:(NSString*)text {

    UIFont* boldFont = [UIFont boldSystemFontOfSize:15];
    NSAttributedString* labelString = [[NSAttributedString alloc ] initWithString:text
                                                                       attributes:@{NSFontAttributeName:boldFont}];

    CGRect cellRect = [labelString boundingRectWithSize:CGSizeMake(MAXFLOAT, 30)
                                                options:NSStringDrawingUsesLineFragmentOrigin context:nil];

    CGFloat width = cellRect.size.width + 10;

    UILabel* textLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, width, 25)];
    [textLabel setText:text];
    [textLabel setTextColor:[UIColor whiteColor]];   
    return textLabel;
}

- (UILabel*)optionLabelFromText:(NSString*)text index:(NSInteger)index{

    UIFont* regularFont = [UIFont systemFontOfSize:14];
    CGFloat width = CGRectGetWidth(self.view.frame) * FILL_FRAME_RATIO; // 0.4;

    UILabel* optionLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, width, 25)];
    [optionLabel setText:text];
    [optionLabel setFont:regularFont];
    [optionLabel setTextColor:[UIColor whiteColor]];
    [optionLabel setTextAlignment:NSTextAlignmentCenter];
    [optionLabel setBackgroundColor:[UIColor lightTextColor]];

    [optionLabel.layer setBorderColor:[UIColor whiteColor].CGColor];
    [optionLabel.layer setBorderWidth:1.0];
    [optionLabel.layer setCornerRadius:2.0];
    [optionLabel.layer setMasksToBounds:YES];
    [optionLabel setUserInteractionEnabled:YES];

    UIPanGestureRecognizer* panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGestureForFillInBlank:)];
    [optionLabel addGestureRecognizer:panGesture];

    return optionLabel;
}

This Method will help in drag & drop functionality

    - (void)handlePanGestureForFillInBlank:(UIPanGestureRecognizer*)sender{

        __block BOOL isDragOnBlank = NO;

        NSInteger tag = sender.view.tag;
        OptionsFrame senderRect = optionRect[tag];

        if (sender.state == UIGestureRecognizerStateBegan) {

        }
        else if (sender.state == UIGestureRecognizerStateChanged) {
            CGPoint translation = [sender translationInView:sender.view];
            sender.view.center = CGPointMake(sender.view.center.x + translation.x, sender.view.center.y + translation.y);
            [sender setTranslation:CGPointZero inView:sender.view];
        }
        else if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateCancelled) {

            for (UIView* blankView in self.blankViewContaninerList) {
                if (CGRectIntersectsRect(sender.view.frame, blankView.frame) && blankView.tag != -999) {
                    __weak typeof(self) weakSelf = self;
                    UILabel* selectedLabel = (UILabel*)sender.view;

                    [self computeAnswerFromSelectedLabel:selectedLabel blankView:blankView onCompletion:^(BOOL isCorrectAnswer, NSString *selectedAnswer) {
                        if (isCorrectAnswer) {

                            [selectedLabel setUserInteractionEnabled:NO];

                            blankView.tag = -999;   // Indicates that blank view already contains one label (blank space is already filled)

                            [UIView animateWithDuration:0.25 animations:^{
                                [sender.view setCenter:blankView.center];
                            } completion:^(BOOL finished) {
                                if (weakSelf.correctAnswerCount == weakSelf.totalOptionCount) {
// Your custom logic goes here, after all option dragged on their respective places.
                                }
                            }];
                        }
                        else{
                            [UIView animateWithDuration:0.25 animations:^{
                                [sender.view setFrame:CGRectMake(senderRect.origin.x, senderRect.origin.y, senderRect.size.width, senderRect.size.height)];
                            }];
                        }
                        isDragOnBlank = YES;
                    }];
                }
            }
            if(!isDragOnBlank) {
                [UIView animateWithDuration:0.25 animations:^{
                    [sender.view setFrame:CGRectMake(senderRect.origin.x, senderRect.origin.y, senderRect.size.width, senderRect.size.height)];
                }];
            }
        }
    }

This Method will tell you dragged option empty space is correct or wrong

- (void)computeAnswerFromSelectedLabel:(UILabel*)selectedLabel
                             blankView:(UIView*)blankView
                          onCompletion:(void(^)(BOOL isCorrectAnswer, NSString* selectedAnswer))onCompletion {

    NSInteger tagIndex = (blankView.tag - 600);

    NSString* correctAnswer  = self.card.questionDataModel.fillAnswers[tagIndex];
    NSString* selectedAnswer = selectedLabel.text;

    if ([correctAnswer isEqualToString:selectedAnswer]) {

        self.correctAnswerCount++;

        if (onCompletion) {
            onCompletion(YES, selectedAnswer);
        }
    }
    else {
        self.wrongAnswerCount++;

        // Shake the container view if dragging is wrong.
        [self shakeView:self.fillBlankContainerView withOffest:8];
        if(onCompletion){
            onCompletion(NO, selectedAnswer);
        }        
    }
}

enter image description here

Please feel free to use code & improvements are happily welcome :)

Keep coding!

Nasir
  • 1,617
  • 2
  • 19
  • 34
  • can you provide a full source code plz ! and if in swift will be so good ! – Ghassan May 06 '20 at 11:14
  • I don't have, It was written long back. – Nasir May 07 '20 at 11:12
  • can you check my question here if you help me https://stackoverflow.com/questions/61155553/fill-in-blank-from-collectionview-cell?noredirect=1#comment108192887_61155553 – Ghassan May 07 '20 at 11:15
0

You can use UIWebView. And for updating the contents of the blanks you can write javascript inside the HTML pages and bind it with onselect event of the blanks.

You can have a look at my answer iOS UIWebView Javascript - insert data -receive callbacks?

to know more about communicating with a webview. (i.e. executing javascript from Objective C and vice versa)


Update for the comment

Yes, you can pass a string to the webview from outside ,

[webViewInstance stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"displayTheStringInBlank(%@)", stringValue]];

and write a JS function is your HTML file to get and display the string in the blank,

function displayTheStringInBlank(stringValue)
{
//Write code to use the string in the blank
document.getElementById("yourInputIdElement").value = stringValue;

}
Community
  • 1
  • 1
sElanthiraiyan
  • 6,000
  • 1
  • 31
  • 38
  • Okay. Now is it possible to pass a string from outside of the uiwebview to reflect it in one of those blanks? When I tap the blanks, I will slide down a view that contains the words(choices) tapping in these words will reflect on the blank that was selected. Can I do that? I know I will be loading the html via loadHtmlString for the uiwebview, does that mean I will update the htmlString and refresh the uiwebview? what will happen to the previous inputs if I do so? – Diffy Oct 27 '11 at 07:39
  • You don't have to reload the whole webview using new HTML string. You can use Javascript methods there. See my updated my answer. – sElanthiraiyan Oct 27 '11 at 08:27
  • thanks dude! you've been a great help. Everything works fine. Now my problem is how not to show the keyboard when tapping a textfield? coz I want to show a custom view when tapping the textfield from the uiwebview. – Diffy Oct 27 '11 at 09:08
0

I sometimes use special tags in NSMutableStrings as placeholders for replacement strings, then replace them as needed with code like...

if ([cStr rangeOfString:@"MYTAG"].location != NSNotFound)
    [cStr replaceCharactersInRange:[cStr rangeOfString:@"MYTAG"] withString:myNewString];

If you need any kind of nice formatting, using UIWebView would be the way to go.

Simon
  • 8,981
  • 2
  • 26
  • 32