ios - Create tap-able "links" in the NSAttributedString of a UILabel? -
i have been searching hours i've failed. don't know should looking for.
many applications have text , in text web hyperlinks in rounded rect. when click them uiwebview
opens. puzzles me have custom links, example if words starts # clickable , application responds opening view. how can that? possible uilabel
or need uitextview
or else?
in general, if want have clickable link in text displayed uilabel, need resolve 2 independent tasks:
- changing appearance of portion of text link
- detecting , handling touches on link (opening url particular case)
the first 1 easy. starting ios 6 uilabel supports display of attributed strings. need create , configure instance of nsmutableattributedstring:
nsmutableattributedstring *attributedstring = [[nsmutableattributedstring alloc] initwithstring:@"string link" attributes:nil]; nsrange linkrange = nsmakerange(14, 4); // word "link" in string above nsdictionary *linkattributes = @{ nsforegroundcolorattributename : [uicolor colorwithred:0.05 green:0.4 blue:0.65 alpha:1.0], nsunderlinestyleattributename : @(nsunderlinestylesingle) }; [attributedstring setattributes:linkattributes range:linkrange]; // assign attributedtext uilabel label.attributedtext = attributedstring;
that's it! code above makes uilabel display string link
now should detect touches on link. idea catch taps within uilabel , figure out whether location of tap close enough link. catch touches can add tap gesture recognizer label. make sure enable userinteraction label, it's turned off default:
label.userinteractionenabled = yes; [label addgesturerecognizer:[[uitapgesturerecognizer alloc] initwithtarget:self action:@selector(handletaponlabel:)]];
now sophisticated stuff: finding out whether tap on link displayed , not on other portion of label. if had single-lined uilabel, task solved relatively easy hardcoding area bounds link displayed, let's solve problem more elegantly , general case - multiline uilabel without preliminary knowledge link layout.
one of approaches use capabilities of text kit api introduced in ios 7:
// create instances of nslayoutmanager, nstextcontainer , nstextstorage nslayoutmanager *layoutmanager = [[nslayoutmanager alloc] init]; nstextcontainer *textcontainer = [[nstextcontainer alloc] initwithsize:cgsizezero]; nstextstorage *textstorage = [[nstextstorage alloc] initwithattributedstring:attributedstring]; // configure layoutmanager , textstorage [layoutmanager addtextcontainer:textcontainer]; [textstorage addlayoutmanager:layoutmanager]; // configure textcontainer textcontainer.linefragmentpadding = 0.0; textcontainer.linebreakmode = label.linebreakmode; textcontainer.maximumnumberoflines = label.numberoflines;
save created , configured instances of nslayoutmanager, nstextcontainer , nstextstorage in properties in class (most uiviewcontroller's descendant) - we'll need them in other methods.
now, each time label changes frame, update textcontainer's size:
- (void)viewdidlayoutsubviews { [super viewdidlayoutsubviews]; self.textcontainer.size = self.label.bounds.size; }
and finally, detect whether tap on link:
- (void)handletaponlabel:(uitapgesturerecognizer *)tapgesture { cgpoint locationoftouchinlabel = [tapgesture locationinview:tapgesture.view]; cgsize labelsize = tapgesture.view.bounds.size; cgrect textboundingbox = [self.layoutmanager usedrectfortextcontainer:self.textcontainer]; cgpoint textcontaineroffset = cgpointmake((labelsize.width - textboundingbox.size.width) * 0.5 - textboundingbox.origin.x, (labelsize.height - textboundingbox.size.height) * 0.5 - textboundingbox.origin.y); cgpoint locationoftouchintextcontainer = cgpointmake(locationoftouchinlabel.x - textcontaineroffset.x, locationoftouchinlabel.y - textcontaineroffset.y); nsinteger indexofcharacter = [self.layoutmanager characterindexforpoint:locationoftouchintextcontainer intextcontainer:self.textcontainer fractionofdistancebetweeninsertionpoints:nil]; nsrange linkrange = nsmakerange(14, 4); // it's better save range somewhere when used marking link in attributed string if (nslocationinrange(indexofcharacter, linkrange)) { // open url, or handle tap on link in other way [[uiapplication sharedapplication] openurl:[nsurl urlwithstring:@"https://stackoverflow.com/"]]; } }
Comments
Post a Comment