Raptor
Raptor

Reputation: 54212

Is it possible to add a UIButton within a UILabel / UITextView?

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

Answers (5)

umair farid
umair farid

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

harsha yarabarla
harsha yarabarla

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

enter image description here

Upvotes: 3

Raptor
Raptor

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

Eimantas
Eimantas

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

pkc
pkc

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

Related Questions