Reputation: 54212
Here is an interesting scenario. I have the following text sending from server:
I go to school by #option#
In iOS App, I have to convert it into:
I go to school by [UIButton]
The challenge is to measure the length of the previous text ("I go to school by "), in order to set the location (the origin
) of the UIButton
. The problem will be more problematic when the UILabel
has multiple lines, and multiple appearance of UIButton
, such as:
I go to school by #option#. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus fringilla, massa ut pharetra ultricies, elit nunc accumsan libero, ut elementum est justo sed lacus. Proin mi tellus, suscipit in magna a, auctor laoreet eros. #option# Quisque rhoncus ac mauris ac interdum. Etiam a odio augue. Mauris porttitor purus ac maximus faucibus. Suspendisse velit felis, varius lobortis turpis venenatis, tempor placerat magna. Integer in semper justo, ut pretium nunc. Aliquam laoreet vehicula finibus.#option#
Here are some of my solutions I can think of :
Method 1
NSString * testText = @"demo title";
CGSize stringSize = [testText sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:17.0f]}];
This method can measure the size of a single line text, but for multiple lines text, the CGSize
will be incorrect.
Method 2
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithString:@"hello"];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
NSTextContainer *textContainer = [[NSTextContainer alloc] init];
[layoutManager addTextContainer:textContainer];
[textStorage addLayoutManager:layoutManager];
//Figure out the bounding rectangle
NSRect stringRect = [layoutManager boundingRectForGlyphRange:NSMakeRange(0, [layoutManager numberOfGlyphs]) inTextContainer:textContainer];
This is much more complex. (Still figuring out how to build the layout)
My question is: Any simpler method to achieve my goal?
Upvotes: 5
Views: 3680
Reputation: 17
func addButtons(btnTitle: String, textView: UITextView) {
for subview in textView.subviews {
if subview is UIButton {
// this is a button
subview.removeFromSuperview()
}
}
var searchRange = NSRange(location: 0, length: textView.text.count)
if searchRange.location != NSNotFound {
searchRange = NSString(string: textView.text).range(of: btnTitle, options: String.CompareOptions.caseInsensitive)
if searchRange.location != NSNotFound {
let beginning: UITextPosition = textView.beginningOfDocument
let start: UITextPosition? = textView.position(from: beginning, offset: searchRange.location)
var end: UITextPosition?
if let start = start {
end = textView.position(from: start, offset: searchRange.length)
}
var range: UITextRange?
if let start = start, let end = end {
range = textView.textRange(from: start, to: end)
}
var requiredFrame: CGRect?
if let range = range {
requiredFrame = textView.firstRect(for: range)
}
let baseButton = BaseButton()
baseButton.addTarget(self, action: #selector(handleButton(_:)), for: .touchUpInside)
if let reqFrame = requiredFrame {
baseButton.frame = CGRect(x: reqFrame.origin.x, y: reqFrame.origin.y + 3, width: reqFrame.size.width, height: reqFrame.size.height) // result1 ?? CGRect.zero
}
textView.addSubview(baseButton)
}
}
}
Here BaseButton is the custom button...you can use native or your own custom.
Upvotes: 1
Reputation: 506
Using UITextView
you can solve your problem just add below method
-(void)addButtons{
NSRange searchRange = NSMakeRange(0, [self.localTextView.text length]);
NSLog(@"%d", self.localTextView.text.length);
while(searchRange.location != NSNotFound){
searchRange = [self.localTextView.text rangeOfString:@"#option#" options:NSCaseInsensitiveSearch range:searchRange];
if (searchRange.location != NSNotFound)
{
NSLog(@"%d", searchRange.location);
UITextPosition *beginning = self.localTextView.beginningOfDocument;
UITextPosition *start = [self.localTextView positionFromPosition:beginning offset:searchRange.location];
UITextPosition *end = [self.localTextView positionFromPosition:start offset:searchRange.length];
UITextRange *range = [self.localTextView textRangeFromPosition:start toPosition:end];
CGRect result1 = [self.localTextView firstRectForRange:range];
NSLog(@"%f, %f", result1.origin.x, result1.origin.y);
UIButton* btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn addTarget:self action:@selector(doAction:) forControlEvents:UIControlEventTouchUpInside];
[btn setTitle:@"Click" forState:UIControlStateNormal];
[btn setBackgroundColor:[UIColor blackColor]];
[btn setFrame:result1];
[self.localTextView addSubview:btn];
searchRange = NSMakeRange(searchRange.location + searchRange.length, self.localTextView.text.length - (searchRange.location + searchRange.length));
}
}
}
localTextView is IBOutlet
taken from storyboard, finally just call [self addButtons];
in your viewDidAppear
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self addButtons];
}
set the button appearance and action method as you wish i just made a black background with a title. Here is the Output which i got
Upvotes: 3
Reputation: 54212
@vienvu's deleted answer is closest to solve the problem by making use of YYText library.
NSString *text = @"Test adding UIButton #option# inline";
NSMutableAttributedString *method2text = [[NSMutableAttributedString alloc] initWithString:text];
UIFont *font = [UIFont systemFontOfSize:20];
NSMutableAttributedString *attachment = nil;
NSRange range = [[method2text string] rangeOfString:@"#option#"];
if(range.location != NSNotFound) {
UITextField *tf = [[UITextField alloc]initWithFrame:CGRectMake(0, 0, 100, 30)];
tf.borderStyle = UITextBorderStyleRoundedRect;
attachment = [NSMutableAttributedString yy_attachmentStringWithContent:tf contentMode:UIViewContentModeBottom attachmentSize:tf.frame.size alignToFont:font alignment:YYTextVerticalAlignmentCenter];
[method2text replaceCharactersInRange:range withString:@""];
[method2text insertAttributedString:attachment atIndex:range.location];
}
theLabel = [YYLabel new];
theLabel.userInteractionEnabled = YES;
theLabel.numberOfLines = 0;
theLabel.frame = CGRectMake(0, 100, 320, 320);
theLabel.center = self.view.center;
theLabel.attributedText = method2text;
[self.view addSubview:theLabel];
where theLabel
is a YYLabel
. I found that YYTextView
cannot select the UI elements. Similar codes are workable for UIButton
too.
Upvotes: 0
Reputation: 49354
Using UIButton is very cumbersom. I suggest you check out the UITextViewDelegate
's two methods:
- textView:shouldInteractWithTextAttachment:inRange:
and
- textView:shouldInteractWithURL:inRange:
By using these two you can easily track where user has tapped and based on the text/url tapped you can take appropriate actions. This has an advantage of allowing you to vary the font/size of the text without worrying about the positioning of the buttons.
Upvotes: 0
Reputation: 8504
For UITextView:
NSMutableAttributedString * fullStringValue = [[NSMutableAttributedString alloc] initWithString:@"I go to school by option"];
NSRange range = [[fullStringValue string] rangeOfString:@"option"];
[fullStringValue addAttribute: NSLinkAttributeName value: @"http://www.EnterUrlHere.com" range: NSMakeRange(range.location, range.length)];
yourTextView.attributedText = fullStringValue;
In this way, you can find multiple ranges and add attributes. Set dataDetectorTypes value of your UITextView to UIDataDetectorTypeLink or UIDataDetectorTypeAll to open URLs when clicked.
For UILabel:
Use TTTAttributedLabel.
The usage is clearly explained here.
Demo project available here.
Upvotes: 2