After understanding your needs, The main issue you are facing is not knowing where to add the select lists.
I have created 2 categories for your case, for UILabel and for UITextView,
following these posts which contain the relevant answers for that:
How do I locate the CGRect for a substring of text in a UILabel?
Get X and Y coordinates of a word in UITextView
These categories find the CGRect for a string inside, which is where you should position your pickers.
The down-part of this for UILabel, is that it doesn't handle wordWrap line breaking mode well, and therefore it won't find the correct location, to handle this correctly, you should add line breaks when needed in case you use the UILabel.
UILabel:
extension UILabel
{
func rectFor(string str : String, fromIndex: Int = 0) -> (CGRect, NSRange)?
{
// Find the range of the string
guard self.text != nil else { return nil }
let subStringToSearch : NSString = (self.text! as NSString).substring(from: fromIndex) as NSString
var stringRange = subStringToSearch.range(of: str)
if (stringRange.location != NSNotFound)
{
guard self.attributedText != nil else { return nil }
// Add the starting point to the sub string
stringRange.location += fromIndex
let storage = NSTextStorage(attributedString: self.attributedText!)
let layoutManager = NSLayoutManager()
storage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: self.frame.size)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = .byWordWrapping
layoutManager.addTextContainer(textContainer)
var glyphRange = NSRange()
layoutManager.characterRange(forGlyphRange: stringRange, actualGlyphRange: &glyphRange)
let resultRect = layoutManager.boundingRect(forGlyphRange: glyphRange, in:textContainer)
return (resultRect, stringRange)
}
return nil
}
}
Usage for infinite searching all available substring (I recommend adding it in viewDidLayoutSubviews() in case you use auto-layout:
var lastFoundIndex : Int = 0
while let result = self.label.rectFor(string: "#OPTION", fromIndex: lastFoundIndex)
{
let view : UIView = UIView(frame: result.0)
view.backgroundColor = UIColor.red
self.label.addSubview(view)
lastFoundIndex = result.1.location + 1
}
And the same one for UITextView:
extension UITextView
{
func rectFor(string str : String, fromIndex: Int = 0) -> (CGRect, NSRange)?
{
// Find the range of the string
guard self.text != nil else { return nil }
let subStringToSearch : NSString = (self.text! as NSString).substring(from: fromIndex) as NSString
var stringRange = subStringToSearch.range(of: str)
if (stringRange.location != NSNotFound)
{
guard self.attributedText != nil else { return nil }
// Add the starting point to the sub string
stringRange.location += fromIndex
// Find first position
let startPosition = self.position(from: self.beginningOfDocument, offset: stringRange.location)
let endPosition = self.position(from: startPosition!, offset: stringRange.length)
let resultRange = self.textRange(from: startPosition!, to: endPosition!)
let resultRect = self.firstRect(for: resultRange!)
return (resultRect, stringRange)
}
return nil
}
}
Usage:
var lastFoundTextIndex : Int = 0
while let result = self.textView.rectFor(string: "#OPTION", fromIndex: lastFoundTextIndex)
{
let view : UIView = UIView(frame: result.0)
view.backgroundColor = UIColor.red
self.textView.addSubview(view)
lastFoundTextIndex = result.1.location + 1
}
In your case, textview gives the best results and uses included methods for that, the sample code uses a label & a text view, initialized in the code:
self.label.text = "The best football player in the world is\n#OPTION, and the best basketball player\n is #OPTION?"
self.textView.text = "The best football player in the world is #OPTION, and the best basketball player is #OPTION?"
And the output just adds views on top of the "#OPTION" strings:

Hope this helps
EDIT - Added Objective-C Variation:
Create 2 extensions - 1 for UITextView and 1 for UILabel:
UILabel:
@interface UILabel (UILabel_SubStringRect)
- (NSDictionary*)rectForString:(NSString*)string fromIndex:(int)index;
@end
#import "UILabel+SubStringRect.h"
@implementation UILabel (UILabel_SubStringRect)
- (NSDictionary*)rectForString:(NSString*)string fromIndex:(int)index
{
if (string != nil)
{
NSString* subStringToSearch = [self.text substringFromIndex:index];
NSRange stringRange = [subStringToSearch rangeOfString:string];
if (stringRange.location != NSNotFound)
{
if (self.attributedText != nil)
{
stringRange.location += index;
NSTextStorage* storage = [[NSTextStorage alloc] initWithAttributedString:self.attributedText];
NSLayoutManager* layoutManager = [NSLayoutManager new];
[storage addLayoutManager:layoutManager];
NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:self.frame.size];
textContainer.lineFragmentPadding = 0;
textContainer.lineBreakMode = NSLineBreakByWordWrapping;
[layoutManager addTextContainer:textContainer];
NSRange glyphRange;
[layoutManager characterRangeForGlyphRange:stringRange actualGlyphRange:&glyphRange];
CGRect resultRect = [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
return @{ @"rect" : [NSValue valueWithCGRect:resultRect], @"range" : [NSValue valueWithRange:stringRange] };
}
}
}
return nil;
}
@end
UITextView:
@interface UITextView (SubStringRect)
- (NSDictionary*)rectForString:(NSString*)string fromIndex:(int)index;
@end
#import "UITextView+SubStringRect.h"
@implementation UITextView (SubStringRect)
- (NSDictionary*)rectForString:(NSString*)string fromIndex:(int)index
{
if (string != nil)
{
NSString* subStringToSearch = [self.text substringFromIndex:index];
NSRange stringRange = [subStringToSearch rangeOfString:string];
if (stringRange.location != NSNotFound)
{
if (self.attributedText != nil)
{
stringRange.location += index;
UITextPosition* startPosition = [self positionFromPosition:self.beginningOfDocument offset:stringRange.location];
UITextPosition* endPosition = [self positionFromPosition:startPosition offset:stringRange.length];
UITextRange* resultRange = [self textRangeFromPosition:startPosition toPosition:endPosition];
CGRect resultRect = [self firstRectForRange:resultRange];
return @{ @"rect" : [NSValue valueWithCGRect:resultRect], @"range" : [NSValue valueWithRange:stringRange] };
}
}
}
return nil;
}
@end
Usage Sample - UILabel:
int lastFoundIndex = 0;
NSDictionary* resultDict = nil;
do
{
resultDict = [self.label rectForString:@"#OPTION" fromIndex:lastFoundIndex];
if (resultDict != nil)
{
NSLog(@"result: %@", resultDict[@"rect"]);
UIView* view = [[UIView alloc] initWithFrame:[resultDict[@"rect"] CGRectValue]];
[view setBackgroundColor:[UIColor redColor]];
[self.label addSubview:view];
lastFoundIndex = (int)[resultDict[@"range"] rangeValue].location + 1;
}
} while (resultDict != nil);
UITextView:
int lastFoundTextIndex = 0;
NSDictionary* resultTextDict = nil;
do
{
resultTextDict = [self.textview rectForString:@"#OPTION" fromIndex:lastFoundTextIndex];
if (resultTextDict != nil)
{
NSLog(@"result: %@", resultTextDict[@"rect"]);
UIView* view = [[UIView alloc] initWithFrame:[resultTextDict[@"rect"] CGRectValue]];
[view setBackgroundColor:[UIColor redColor]];
[self.textview addSubview:view];
lastFoundTextIndex = (int)[resultTextDict[@"range"] rangeValue].location + 1;
}
} while (resultTextDict != nil);