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);
}
}
}

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