TTTAttributedLabel links are being styled but not clickable - ios

I've been looking into a solution to getting clickable links working. I can get this working when using UITextView + NSAttributedString but it just doesn't autolayout properly when it's a UITableViewCell.
Now I've added the TTTAttributedLabel to my project and it styles the views just perfectly. The links also turn blue and are underlined.
However clicking them does nothing. I did implement TTTAttributedLabelDelegate on my controller, made the label in the storyboard implement MyLabel (Which just extends TTTAttributedLabel and has the delegate options since I want them to fire inside the same function). For now I've set the controller to be the delegate I was thinking it might not work pointing to itself.
But none of these functions get fired, I got breakpoints and logs in it.
I Implemented didSelectLinkWithUrl and didLongPressLinkWithUrl.
func attributedLabel(label: TTTAttributedLabel!, didSelectLinkWithURL url: NSURL!) {
Debug.log("link clicked")
}
func attributedLabel(label: TTTAttributedLabel!, didLongPressLinkWithURL url: NSURL!, atPoint point: CGPoint) {
Debug.log("link long clicked")
}
Outlet
#IBOutlet weak var content: MyLabel!
MyLabel
import UIKit
import TTTAttributedLabel
class MyLabel : TTTAttributedLabel, TTTAttributedLabelDelegate {
override func didMoveToSuperview() {
if (self.delegate == nil) {
self.delegate = self
}
self.enabledTextCheckingTypes = NSTextCheckingType.Link.rawValue
self.userInteractionEnabled = true
}
func attributedLabel(label: TTTAttributedLabel!, didSelectLinkWithURL url: NSURL!) {
Debug.log("link clicked")
}
func attributedLabel(label: TTTAttributedLabel!, didLongPressLinkWithURL url: NSURL!, atPoint point: CGPoint) {
Debug.log("link long clicked")
}
Anyone know what I could be missing?
Update
I found out that just pasting in an url f/e http://example.com becomes active and is actually clickable and the didSelectLinkWithUrl becomes clickable, allthough I need an attributed string and it's based on a HTML String.

The implementation of setAttributedText: doesn't update the linkModels array, while the implementation of setText: does. I believe this is what causes your issue.
To resolve, set the label's text property instead of the attributedText property.
The docs also include this warning:
TTTAttributedLabel can display both plain and attributed text: just pass an NSString or NSAttributedString to the setText: setter. Never assign to the attributedText property.
The docs also show this example usage:
TTTAttributedLabel *attributedLabel = [[TTTAttributedLabel alloc] initWithFrame:CGRectZero];
NSAttributedString *attString = [[NSAttributedString alloc] initWithString:#"Tom Bombadil"
attributes:#{
(id)kCTForegroundColorAttributeName : (id)[UIColor redColor].CGColor,
NSFontAttributeName : [UIFont boldSystemFontOfSize:16],
NSKernAttributeName : [NSNull null],
(id)kTTTBackgroundFillColorAttributeName : (id)[UIColor greenColor].CGColor
}];
// The attributed string is directly set, without inheriting any other text
// properties of the label.
attributedLabel.text = attString;

Aaron Brager is correct!
I used to have the same problem, but I got it fixed in Swift by replacing the line:
label.attributedText = attributedString
with the line:
label.setText(attributedString)
This is accepted by the compiler because the setText method accepts AnyObject.
I also increased the font of the attributed string of the link so it captures my tap and it is working now!
I used this for the truncation link, this is the whole part:
label.lineBreakMode = .ByTruncatingHead
label.attributedTruncationToken = NSMutableAttributedString(string: "... Show more", attributes: [NSForegroundColorAttributeName: UIColor.cueCyan(), NSLinkAttributeName: readMoreLink, NSFontAttributeName: UIFont.formFont(.Light, size: fontSize+24)!])
label.userInteractionEnabled = true
label.delegate = self

Related

UiTextField changes font while editing in Swift 1.2 & 2.0

I have a UITextField with a custom font, everything worked fine until Swift update to 1.2 and 2.0. Afterwards, each time I try to edit a text field, it changes its font to a different one that seems a sort of Times New Roman. Does anyone have experience of that?
I came across this same issue and figured out a solution. The problem boils down to setSecureTextEntry changing the font when it is set, and not changing it back when it is unset. In fact, you can never change the font back as long as your UITextField has first responder.
The trick is to resignFirstResponder before you call setSecureTextEntry: and then becomeFirstResponder again. This will work (as of iOS 9.2), but it triggers the keyboard show/hide animation and will cause the screen to "shake". To get around that, you'll need to kill the keyboard animation as well.
Here's my full solution:
- (void)setSecureTextEntry:(BOOL)secureTextEntry {
__weak typeof(self) weakSelf = self;
[UIView performWithoutAnimation:^{
BOOL resumeResponder = NO;
if([[weakSelf textEntryField] isFirstResponder]) {
resumeResponder = YES;
[[weakSelf textEntryField] resignFirstResponder];
}
[[weakSelf textEntryField] setSecureTextEntry:secureTextEntry];
if(resumeResponder) {
[[weakSelf textEntryField] becomeFirstResponder];
}
}];
}
PS: This isn't a Swift bug. It's a UIKit bug. I had the same issue with Objective-C.
I had a weird case of fonts changing its size and font type, when secureTextEntry for an UiTextField was toggled by using a button action.
Had to explicitly manage font for the UiTextField by using these lines of code:
password.font = UIFont(name: "systemFont", size: 14)
password.font = UIFont.systemFontOfSize(14)
Complete Code used in the Show Password Button:
//Function associated with the button for show password option
#IBAction func switchShowPasswordAction(sender: AnyObject) {
if showPassword{
showPassword = false
password.secureTextEntry = !showPassword
}else{
showPassword = true
password.secureTextEntry = !showPassword
}
//Changing font fix
password.font = UIFont(name: "systemFont", size: 14)
password.font = UIFont.systemFontOfSize(14)
}
Post applying this change:
Since I used custom fonts we need to preserve the original font. Create an extension to UITextField:
extension UITextField {
func enablePasswordModeWithShowHide() {
secureTextEntry = false
let showButton = UIButton(type: UIButtonType.System)
showButton.setTitle("HIDE", forState: .Normal)
showButton.titleLabel?.textAlignment = .Right
showButton.sizeToFit()
rightView = showButton
rightViewMode = .Always
showButton.addTarget(self, action: "handleShowHideTapped", forControlEvents: .TouchUpInside)
showButton.tintColor = UIColor.blackColor()
}
func handleShowHideTapped() {
secureTextEntry = !secureTextEntry
let font = self.font
self.font = nil
self.font = font
if let oldText = text {
text = "";
text = oldText;
}
if let button = rightView as? UIButton {
button.setTitle(secureTextEntry ? "SHOW" : "HIDE", forState: .Normal)
button.sizeToFit()
}
}
}
Where it could be implemented like this:
passwordTextField.enablePasswordModeWithShowHide()
All of these answers pretty much work, but I had to use a different solution to achieve the results I needed, considering I'm using a custom font.
You need to make the text field attributed in the storyboard inspector pane for the UITextField, as follows:
Then, in code, you need to manage the toggling of the visibility, setting the attributed text each time, to ensure its properly formatted. I also resignFirstResponder() on the field just to take care of some positioning glitch that I still haven't figured out yet.
func toggleShowPass() {
self.showing = !showing
txtpassword.secureTextEntry = !showing
textFieldPassword.resignFirstResponder()
let string = textFieldPassword.text!
let attrString = NSMutableAttributedString(string: string)
textFieldPassword.addAttribute(NSFontAttributeName, value: UIFont(name: "AvenirNext-Regular", size: 16.0)!, range: NSMakeRange(0, string.characters.count))
textFieldPassword.attributedText = attrString
}
Set defaultTextAttributes with custom font attribute after toggling the secureTextEntry flag
NSDictionary *attrsDictionary =
#{ NSFontAttributeName://customfont};
_passwordtextfield.defaultTextAttributes = attrsDictionary;
I had to apply the following solution with latest Xcode 7.1.1 which actually worked in my case I suspect this issue is of framework.
- (IBAction)btnPasswordShowAction:(id)sender {
self.txtPassword.secureTextEntry = !self.txtPassword.secureTextEntry;
NSString *tmpString = self.txtPassword.text;
self.txtPassword.text = #" ";
self.txtPassword.text = tmpString;
[self.txtPassword setFont:nil];
self.txtPassword.font = [UIFont fontWithName:#"OpenSans-Regular" size:16.0];
}
#pragma mark - Textfield Delegate Methods
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[self.txtPassword setFont:nil];
self.txtPassword.font = [UIFont fontWithName:#"OpenSans-Regular" size:16.0];
}
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
[self.txtPassword setFont:nil];
self.txtPassword.font = [UIFont fontWithName:#"OpenSans-Regular" size:16.0];
return YES;
}

Hyperlinks in a UITextView

I am trying to create a UITextView with a hyperlink so that when the user clicks on the link, they are taken to safari to open the webpage. I have read on link detectors for a textview but those samples always show link detection working if an actual url is present in the text (ie. www.google.com). I want it to be regular text that, when clicked, opens an associated URL. (ie. Google is the text and when clicked, opens up a url www.google.com). How can I accomplish this in iOS7/8?
Use NSAttributedString
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:#"Google"
attributes:#{ NSLinkAttributeName: [NSURL URLWithString:#"http://www.google.com"] }];
self.textView.attributedText = attributedString;
Sure, you can set just a portion of the text to be the link. Please read more about the NSAttributedString here.
If you want to have more control and do something before opening the link. You can set the delegate to the UITextView.
- (void)viewDidLoad {
...
self.textView.delegate = self; // self must conform to UITextViewDelegate protocol
}
...
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange {
// Do whatever you want here
NSLog(#"%#", URL); // URL is an instance of NSURL of the tapped link
return YES; // Return NO if you don't want iOS to open the link
}
Swift 3, iOS10 , Xcode 9
#sikhapol's answer is really nice if you knew exactly the words you want to parse like "word dictionary" somehow
it's all about the string itself that displayed
in UITextView
My solution is based on the text rendering
if you make the UITextView render HTML tags you may use href tag
Here is some Code references you may use
first, you need to configure UITextView from interface builder or form code
to
Selectable
Data detectores
Note: do not make the textview editable
Interface builder
programmatically
let htmlData = NSString(string: "go to google and search for it").data(using: String.Encoding.unicode.rawValue)
let attributedString = try! NSAttributedString(data: htmlData!, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil)
yourUIViewView.isSelectable = true
yourUIViewView.dataDetectorTypes = .link
yourUIViewView.attributedText = attributedString
yourUIViewView.delegate = self
for the UITextViewDelegate
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
// check for the url string for performing your own custom actions here
let urlString = URL.absoluteString
// Return NO if you don't want iOS to open the link
return true
}
A nifty little extension that I wrote and use (Swift 4.2, tested on iOS 12.1)
extension NSAttributedString {
func replace(placeholder: String, with hyperlink: String, url: String) -> NSAttributedString {
let mutableAttr = NSMutableAttributedString(attributedString: self)
let hyperlinkAttr = NSAttributedString(string: hyperlink, attributes: [NSAttributedString.Key.link: URL(string: url)!])
let placeholderRange = (self.string as NSString).range(of: placeholder)
mutableAttr.replaceCharacters(in: placeholderRange, with: hyperlinkAttr)
return mutableAttr
}
}
Usage:
//Set through code or through interface builder
footerText.isSelectable = true
footerText.dataDetectorTypes = .link
//Keeps the original formatting from xib or storyboard
footerText.text = "By continuing, you are indicating that you accept our #Terms# and #Privacy#."
footerText.attributedText = footerText.attributedText?
.replace(placeholder: "#Terms#", with: "Terms and Conditions", url: AppUrl.terms)
.replace(placeholder: "#Privacy#", with: "Privacy Policy", url: AppUrl.privacy)
This code sample has two different links in the same label and URL color is set to avoid the default blue.
UITextView * textTerm = [UITextView new];
NSMutableAttributedString *attrRight = [[NSMutableAttributedString alloc] initWithString:#"Terms of Service"
attributes:#{ NSLinkAttributeName: [NSURL URLWithString:#"http://www.google.com"] }];
NSMutableAttributedString *attrLeft = [[NSMutableAttributedString alloc] initWithString:#"Privacy Policy"
attributes:#{ NSLinkAttributeName: [NSURL URLWithString:#"http://www.google.com"] }];
[attrRight appendAttributedString:attrLeft];
textTerm.attributedText = attrRight;
textTerm.editable = NO;
textTerm.dataDetectorTypes = UIDataDetectorTypeAll;
textTerm.linkTextAttributes = [UIColor whiteColor];
textTerm.backgroundColor = [UIColor clearColor];
if you want active substring in your UITextView then you can use my extended TextView... its short and simple. You can edit it as you want.
how to use (range = substring location):
[self.textView addTapActionWithRange:range withActionBlock:^{
// anything you want to do - show something
}];
result:
source code: https://github.com/marekmand/ActiveSubstringTextView
Since the edit queue was full, I'll post my version of an answer here. It's based on Amr Angry's answer.
I'm using DispatchQueue.main.async { ... } since the app will crash if the NSAttributedString is running on a background thread.
guard let htmlData = NSString(string: "go to google and search for it").data(using: String.Encoding.unicode.rawValue) else { return }
DispatchQueue.main.async {
do {
let attributedString = try NSAttributedString(data: htmlData, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil)
yourUIViewView.isSelectable = true
yourUIViewView.dataDetectorTypes = .link
yourUIViewView.attributedText = attributedString
yourUIViewView.delegate = self
} catch {
print("Cannot setup link with \(htmlData)!")
}
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
// Return NO if you don't want iOS to open the link
return true
}

How can I make a clickable link in an NSAttributedString?

It's trivial to make hyperlinks clickable in a UITextView. You just set the "detect links" checkbox on the view in IB, and it detects HTTP links and turns them into hyperlinks.
However, that still means that what the user sees is the "raw" link. RTF files and HTML both allow you to set up a user-readable string with a link "behind" it.
It's easy to install attributed text into a text view (or a UILabel or UITextField, for that matter.) However, when that attributed text includes a link, it is not clickable.
Is there a way to make user-readable text clickable in a UITextView, UILabel or UITextField?
The markup is different on SO, but here is the general idea. What I want is text like this:
This morph was generated with Face Dancer, Click to view in the app store.
The only thing I can get is this:
This morph was generated with Face Dancer, Click on http://example.com/facedancer to view in the app store.
Use NSMutableAttributedString.
NSMutableAttributedString * str = [[NSMutableAttributedString alloc] initWithString:#"Google"];
[str addAttribute: NSLinkAttributeName value: #"http://www.google.com" range: NSMakeRange(0, str.length)];
yourTextView.attributedText = str;
Edit:
This is not directly about the question but just to clarify, UITextField and UILabel does not support opening URLs. If you want to use UILabel with links you can check TTTAttributedLabel.
Also you should set dataDetectorTypes value of your UITextView to UIDataDetectorTypeLink or UIDataDetectorTypeAll to open URLs when clicked. Or you can use delegate method as suggested in the comments.
I found this really useful but I needed to do it in quite a few places so I've wrapped my approach up in a simple extension to NSMutableAttributedString:
Swift 3
extension NSMutableAttributedString {
public func setAsLink(textToFind:String, linkURL:String) -> Bool {
let foundRange = self.mutableString.range(of: textToFind)
if foundRange.location != NSNotFound {
self.addAttribute(.link, value: linkURL, range: foundRange)
return true
}
return false
}
}
Swift 2
import Foundation
extension NSMutableAttributedString {
public func setAsLink(textToFind:String, linkURL:String) -> Bool {
let foundRange = self.mutableString.rangeOfString(textToFind)
if foundRange.location != NSNotFound {
self.addAttribute(NSLinkAttributeName, value: linkURL, range: foundRange)
return true
}
return false
}
}
Example usage:
let attributedString = NSMutableAttributedString(string:"I love stackoverflow!")
let linkWasSet = attributedString.setAsLink("stackoverflow", linkURL: "http://stackoverflow.com")
if linkWasSet {
// adjust more attributedString properties
}
Objective-C
I've just hit a requirement to do the same in a pure Objective-C project, so here's the Objective-C category.
#interface NSMutableAttributedString (SetAsLinkSupport)
- (BOOL)setAsLink:(NSString*)textToFind linkURL:(NSString*)linkURL;
#end
#implementation NSMutableAttributedString (SetAsLinkSupport)
- (BOOL)setAsLink:(NSString*)textToFind linkURL:(NSString*)linkURL {
NSRange foundRange = [self.mutableString rangeOfString:textToFind];
if (foundRange.location != NSNotFound) {
[self addAttribute:NSLinkAttributeName value:linkURL range:foundRange];
return YES;
}
return NO;
}
#end
Example usage:
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:"I love stackoverflow!"];
BOOL linkWasSet = [attributedString setAsLink:#"stackoverflow" linkURL:#"http://stackoverflow.com"];
if (linkWasSet) {
// adjust more attributedString properties
}
Make Sure that the NSTextField's Behavior attribute is set as Selectable.
I just created a subclass of UILabel to specially address such use cases. You can add multiple links easily and define different handlers for them. It also supports highlighting the pressed link when you touch down for touch feedback. Please refer to https://github.com/null09264/FRHyperLabel.
In your case, the code may like this:
FRHyperLabel *label = [FRHyperLabel new];
NSString *string = #"This morph was generated with Face Dancer, Click to view in the app store.";
NSDictionary *attributes = #{NSFontAttributeName: [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]};
label.attributedText = [[NSAttributedString alloc]initWithString:string attributes:attributes];
[label setLinkForSubstring:#"Face Dancer" withLinkHandler:^(FRHyperLabel *label, NSString *substring){
[[UIApplication sharedApplication] openURL:aURL];
}];
Sample Screenshot (the handler is set to pop an alert instead of open a url in this case)
Minor improvement to ujell's solution: If you use NSURL instead of a NSString, you can use any URL (e.g. custom urls)
NSURL *URL = [NSURL URLWithString: #"whatsapp://app"];
NSMutableAttributedString * str = [[NSMutableAttributedString alloc] initWithString:#"start Whatsapp"];
[str addAttribute: NSLinkAttributeName value:URL range: NSMakeRange(0, str.length)];
yourTextField.attributedText = str;
Have fun!
Swift 4:
var string = "Google"
var attributedString = NSMutableAttributedString(string: string, attributes:[NSAttributedStringKey.link: URL(string: "http://www.google.com")!])
yourTextView.attributedText = attributedString
Swift 3.1:
var string = "Google"
var attributedString = NSMutableAttributedString(string: string, attributes:[NSLinkAttributeName: URL(string: "http://www.google.com")!])
yourTextView.attributedText = attributedString
I too had a similar requirement, initially I used UILabel and then I realized that UITextView is better. I made UITextView behave like UILabel by disabling interaction and scrolling and made a category method for NSMutableAttributedString to set link to text same as what Karl had done (+1 for that) this is my obj c version
-(void)setTextAsLink:(NSString*) textToFind withLinkURL:(NSString*) url
{
NSRange range = [self.mutableString rangeOfString:textToFind options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound) {
[self addAttribute:NSLinkAttributeName value:url range:range];
[self addAttribute:NSForegroundColorAttributeName value:[UIColor URLColor] range:range];
}
}
you can use the below delegate then to handle the action
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)url inRange:(NSRange)characterRange
{
// do the task
return YES;
}
Use UITextView it supports clickable Links.
Create attributed string using the following code
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:strSomeTextWithLinks];
Then set UITextView text as follows
NSDictionary *linkAttributes = #{NSForegroundColorAttributeName: [UIColor redColor],
NSUnderlineColorAttributeName: [UIColor blueColor],
NSUnderlineStyleAttributeName: #(NSUnderlinePatternSolid)};
customTextView.linkTextAttributes = linkAttributes; // customizes the appearance of links
textView.attributedText = attributedString;
Make sure that you enable "Selectable" behavior of the UITextView in XIB.
The quick answer is using UITextView instead of UILabel. You need to enable Selectable and disable Editable.
Then disable scroll indicators and bounces.
My solution using NSMutableAttributedString from html string NSHTMLTextDocumentType
NSString *s = #"<p><a href='https://itunes.apple.com/us/app/xxxx/xxxx?mt=8'>https://itunes.apple.com/us/app/xxxx/xxxx?mt=8</a></p>";
NSMutableAttributedString *text = [[NSMutableAttributedString alloc]
initWithData: [s dataUsingEncoding:NSUnicodeStringEncoding]
options: #{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType }
documentAttributes: nil
error: nil
];
cell.content.attributedText = text;
The heart of my question was that I wanted to be able to create clickable links in text views/fields/labels without having to write custom code to manipulate the text and add the links. I wanted it to be data-driven.
I finally figured out how to do it. The issue is that IB doesn't honor embedded links.
Furthermore, the iOS version of NSAttributedString doesn't let you initialize an attributed string from an RTF file. The OS X version of NSAttributedString does have an initializer that takes an RTF file as input.
NSAttributedString conforms to the NSCoding protocol, so you can convert it to/from NSData
I created an OS X command line tool that takes an RTF file as input and outputs a file with the extension .data that contains the NSData from NSCoding. I then put the .data file into my project and add a couple of lines of code that loads the text into the view. The code looks like this (this project was in Swift) :
/*
If we can load a file called "Dates.data" from the bundle and convert it to an attributed string,
install it in the dates field. The contents contain clickable links with custom URLS to select
each date.
*/
if
let datesPath = NSBundle.mainBundle().pathForResource("Dates", ofType: "data"),
let datesString = NSKeyedUnarchiver.unarchiveObjectWithFile(datesPath) as? NSAttributedString
{
datesField.attributedText = datesString
}
For apps that use a lot of formatted text, I create a build rule that tells Xcode that all the .rtf files in a given folder are source and the .data files are the output. Once I do that, I simply add .rtf files to the designated directory, (or edit existing files) and the build process figures out that they are new/updated, runs the command line tool, and copies the files into the app bundle. It works beautifully.
I wrote a blog post that links to a sample (Swift) project demonstrating the technique. You can see it here:
Creating clickable URLs in a UITextField that open in your app
Swift 3 example to detect actions on attributed text taps
https://stackoverflow.com/a/44226491/5516830
let termsAndConditionsURL = TERMS_CONDITIONS_URL;
let privacyURL = PRIVACY_URL;
override func viewDidLoad() {
super.viewDidLoad()
self.txtView.delegate = self
let str = "By continuing, you accept the Terms of use and Privacy policy"
let attributedString = NSMutableAttributedString(string: str)
var foundRange = attributedString.mutableString.range(of: "Terms of use") //mention the parts of the attributed text you want to tap and get an custom action
attributedString.addAttribute(NSLinkAttributeName, value: termsAndConditionsURL, range: foundRange)
foundRange = attributedString.mutableString.range(of: "Privacy policy")
attributedString.addAttribute(NSLinkAttributeName, value: privacyURL, range: foundRange)
txtView.attributedText = attributedString
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "WebView") as! SKWebViewController
if (URL.absoluteString == termsAndConditionsURL) {
vc.strWebURL = TERMS_CONDITIONS_URL
self.navigationController?.pushViewController(vc, animated: true)
} else if (URL.absoluteString == privacyURL) {
vc.strWebURL = PRIVACY_URL
self.navigationController?.pushViewController(vc, animated: true)
}
return false
}
Like wise you can add any action you want with shouldInteractWith URLUITextFieldDelegate method.
Cheers!!
I have written a method, that adds a link(linkString) to a string (fullString) with a certain url(urlString):
- (NSAttributedString *)linkedStringFromFullString:(NSString *)fullString withLinkString:(NSString *)linkString andUrlString:(NSString *)urlString
{
NSRange range = [fullString rangeOfString:linkString options:NSLiteralSearch];
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:fullString];
NSMutableParagraphStyle *paragraphStyle = NSMutableParagraphStyle.new;
paragraphStyle.alignment = NSTextAlignmentCenter;
NSDictionary *attributes = #{NSForegroundColorAttributeName:RGB(0x999999),
NSFontAttributeName:[UIFont fontWithName:#"HelveticaNeue-Light" size:10],
NSParagraphStyleAttributeName:paragraphStyle};
[str addAttributes:attributes range:NSMakeRange(0, [str length])];
[str addAttribute: NSLinkAttributeName value:urlString range:range];
return str;
}
You should call it like this:
NSString *fullString = #"A man who bought the Google.com domain name for $12 and owned it for about a minute has been rewarded by Google for uncovering the flaw.";
NSString *linkString = #"Google.com";
NSString *urlString = #"http://www.google.com";
_youTextView.attributedText = [self linkedStringFromFullString:fullString withLinkString:linkString andUrlString:urlString];
I needed to keep using a pure UILabel, so called this from my tap recognizer (this is based on malex's response here: Character index at touch point for UILabel )
UILabel* label = (UILabel*)gesture.view;
CGPoint tapLocation = [gesture locationInView:label];
// create attributed string with paragraph style from label
NSMutableAttributedString* attr = [label.attributedText mutableCopy];
NSMutableParagraphStyle* paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.alignment = label.textAlignment;
[attr addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, label.attributedText.length)];
// init text storage
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attr];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];
// init text container
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(label.frame.size.width, label.frame.size.height+100) ];
textContainer.lineFragmentPadding = 0;
textContainer.maximumNumberOfLines = label.numberOfLines;
textContainer.lineBreakMode = label.lineBreakMode;
[layoutManager addTextContainer:textContainer];
// find tapped character
NSUInteger characterIndex = [layoutManager characterIndexForPoint:tapLocation
inTextContainer:textContainer
fractionOfDistanceBetweenInsertionPoints:NULL];
// process link at tapped character
[attr enumerateAttributesInRange:NSMakeRange(characterIndex, 1)
options:0
usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
if (attrs[NSLinkAttributeName]) {
NSString* urlString = attrs[NSLinkAttributeName];
NSURL* url = [NSURL URLWithString:urlString];
[[UIApplication sharedApplication] openURL:url];
}
}];
Use UITextView and set dataDetectorTypes for Link.
like this:
testTextView.editable = false
testTextView.dataDetectorTypes = .link
If you want to detect link, phone number,address etc..then
testTextView.dataDetectorTypes = .all
Just find a code-free solution for UITextView:
Enable Detection->Links options, the URL and also email will be detected and clickable!
Update:
There were 2 key parts to my question:
How to make a link where the text shown for the clickable link is different than the actual link that is invoked:
How to set up the links without having to use custom code to set the attributes on the text.
It turns out that iOS 7 added the ability to load attributed text from NSData.
I created a custom subclass of UITextView that takes advantage of the #IBInspectable attribute and lets you load contents from an RTF file directly in IB. You simply type the filename into IB and the custom class does the rest.
Here are the details:
In iOS 7, NSAttributedString gained the method initWithData:options:documentAttributes:error:. That method lets you load an NSAttributedString from an NSData object. You can first load an RTF file into NSData, then use initWithData:options:documentAttributes:error: to load that NSData into your text view. (Note that there is also a method initWithFileURL:options:documentAttributes:error: that will load an attributed string directly from a file, but that method was deprecated in iOS 9. It's safer to use the method initWithData:options:documentAttributes:error:, which wasn't deprecated.
I wanted a method that let me install clickable links into my text views without having to create any code specific to the links I was using.
The solution I came up with was to create a custom subclass of UITextView I call RTF_UITextView and give it an #IBInspectable property called RTF_Filename. Adding the #IBInspectable attribute to a property causes Interface Builder to expose that property in the "Attributes Inspector." You can then set that value from IB wihtout custom code.
I also added an #IBDesignable attribute to my custom class. The #IBDesignable attribute tells Xcode that it should install a running copy of your custom view class into Interface builder so you can see it in the graphical display of your view hierarchy. ()Unfortunately, for this class, the #IBDesignable property seems to be flaky. It worked when I first added it, but then I deleted the plain text contents of my text view and the clickable links in my view went away and I have not been able to get them back.)
The code for my RTF_UITextView is very simple. In addition to adding the #IBDesignable attribute and an RTF_Filename property with the #IBInspectable attribute, I added a didSet() method to the RTF_Filename property. The didSet() method gets called any time the value of the RTF_Filename property changes. The code for the didSet() method is quite simple:
#IBDesignable
class RTF_UITextView: UITextView
{
#IBInspectable
var RTF_Filename: String?
{
didSet(newValue)
{
//If the RTF_Filename is nil or the empty string, don't do anything
if ((RTF_Filename ?? "").isEmpty)
{
return
}
//Use optional binding to try to get an URL to the
//specified filename in the app bundle. If that succeeds, try to load
//NSData from the file.
if let fileURL = NSBundle.mainBundle().URLForResource(RTF_Filename, withExtension: "rtf"),
//If the fileURL loads, also try to load NSData from the URL.
let theData = NSData(contentsOfURL: fileURL)
{
var aString:NSAttributedString
do
{
//Try to load an NSAttributedString from the data
try
aString = NSAttributedString(data: theData,
options: [:],
documentAttributes: nil
)
//If it succeeds, install the attributed string into the field.
self.attributedText = aString;
}
catch
{
print("Nerp.");
}
}
}
}
}
Note that if the #IBDesignable property isn't going to reliably allow you to preview your styled text in Interface builder then it might be better to set the above code up as an extension of UITextView rather than a custom subclass. That way you could use it in any text view without having to change the text view to the custom class.
See my other answer if you need to support iOS versions prior to iOS 7.
You can download a sample project that includes this new class from gitHub:
DatesInSwift demo project on Github
Swift Version :
// Attributed String for Label
let plainText = "Apkia"
let styledText = NSMutableAttributedString(string: plainText)
// Set Attribuets for Color, HyperLink and Font Size
let attributes = [NSFontAttributeName: UIFont.systemFontOfSize(14.0), NSLinkAttributeName:NSURL(string: "http://apkia.com/")!, NSForegroundColorAttributeName: UIColor.blueColor()]
styledText.setAttributes(attributes, range: NSMakeRange(0, plainText.characters.count))
registerLabel.attributedText = styledText
In case you're having issues with what #Karl Nosworthy and #esilver had provided above, I've updated the NSMutableAttributedString extension to its Swift 4 version.
extension NSMutableAttributedString {
public func setAsLink(textToFind:String, linkURL:String) -> Bool {
let foundRange = self.mutableString.range(of: textToFind)
if foundRange.location != NSNotFound {
_ = NSMutableAttributedString(string: textToFind)
// Set Attribuets for Color, HyperLink and Font Size
let attributes = [NSFontAttributeName: UIFont.bodyFont(.regular, shouldResize: true), NSLinkAttributeName:NSURL(string: linkURL)!, NSForegroundColorAttributeName: UIColor.blue]
self.setAttributes(attributes, range: foundRange)
return true
}
return false
}
}
A quick addition to Duncan C's original description vis-รก-vie IB behavior. He writes: "It's trivial to make hyperlinks clickable in a UITextView. You just set the "detect links" checkbox on the view in IB, and it detects http links and turns them into hyperlinks."
My experience (at least in xcode 7) is that you also have to unclick the "Editable" behavior for the urls to be detected & clickable.
In Swift 5.5
Since Swift 5.5 NSAttributedString is completely localizable and easy to use without even defining the number of characters.
func attributedStringBasics(important: Bool) {
var buy = AttributedString("Buy a new iPhone!")
buy.font = .body.bold()
var website = AttributedString("Visit Apple")
website.font = .body.italic()
website.link = URL(string: "http://www.apple.com")
var container = AttributeContainer()
if important {
container.foregroundColor = .red
container.underlineColor = .primary
} else {
container.foregroundColor = .primary
}
buy.mergeAttributes(container)
website.mergeAttributes(container)
print(buy)
print(website)
}
The excellent library from #AliSoftware OHAttributedStringAdditions makes it easy to add links in UILabel here is the documentation: https://github.com/AliSoftware/OHAttributedStringAdditions/wiki/link-in-UILabel
If you want to use the NSLinkAttributeName in a UITextView, then you may consider using the AttributedTextView library. It's a UITextView subclass that makes it very easy to handle these. For more info see: https://github.com/evermeer/AttributedTextView
You can make any part of the text interact like this (where textView1 is a UITextView IBoutlet):
textView1.attributer =
"1. ".red
.append("This is the first test. ").green
.append("Click on ").black
.append("evict.nl").makeInteract { _ in
UIApplication.shared.open(URL(string: "http://evict.nl")!, options: [:], completionHandler: { completed in })
}.underline
.append(" for testing links. ").black
.append("Next test").underline.makeInteract { _ in
print("NEXT")
}
.all.font(UIFont(name: "SourceSansPro-Regular", size: 16))
.setLinkColor(UIColor.purple)
And for handling hashtags and mentions you can use code like this:
textView1.attributer = "#test: What #hashtags do we have in #evermeer #AtributedTextView library"
.matchHashtags.underline
.matchMentions
.makeInteract { link in
UIApplication.shared.open(URL(string: "https://twitter.com\(link.replacingOccurrences(of: "#", with: ""))")!, options: [:], completionHandler: { completed in })
}
if you want active substring in your UITextView then you can use my extended TextView... its short and simple. You can edit it as you want.
result:
code:
https://github.com/marekmand/ActiveSubstringTextView
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:strSomeTextWithLinks];
NSDictionary *linkAttributes = #{NSForegroundColorAttributeName: [UIColor redColor],
NSUnderlineColorAttributeName: [UIColor blueColor],
NSUnderlineStyleAttributeName: #(NSUnderlinePatternSolid)};
customTextView.linkTextAttributes = linkAttributes; // customizes the appearance of links
textView.attributedText = attributedString;
KEY POINTS:
Make sure that you enable "Selectable" behavior of the UITextView in XIB.
Make sure that you disable "Editable" behavior of the UITextView in XIB.

UITextField attributedPlaceholder has no effect

I'm trying to make the placeholders in my textfields italic, and since my app is targeting iOS 6.0 or newer, decided to use attributedPlaceholder property instead of rolling something more custom. The code goes as follows:
NSString *plString = #"optional";
NSAttributedString *placeholder = [[NSAttributedString alloc] initWithString:plString
attributes:#{NSFontAttributeName : [UIFont fontWithName:#"HelveticaNeue-LightItalic" size:15]}];
for (UITextField *t in myTextfields){
t.placeholder = plString;
t.attributedPlaceholder = placeholder;
}
Yet the styling of the placeholder still is not italic, but the same as regular text, just dimmer. What am I missing to make the NSAttributedString work?
As noted by warren, the styling currently can't be accomplished the way you're trying. A good workaround would be to set up your textfield's font attributes the way you would like your placeholder to look and then change the font of the textfield whenever the user begins typing. It will look like the placeholder and text are different fonts.
You can do this by creating a delegate of the textfield and utilizing shouldChangeCharactersinRange like this:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
// If there is text in the text field
if (textField.text.length + (string.length - range.length) > 0) {
// Set textfield font
textField.font = [UIFont fontWithName:#"Font" size:14];
} else {
// Set textfield placeholder font (or so it appears)
textField.font = [UIFont fontWithName:#"PlaceholderFont" size:14];
}
return YES;
}
This is almost certainly a bug. The documentation for the attributedPlaceholder property claims that the string will be drawn using a gray color regardless of the foreground color attribute, but this is not the case: you can set both the foreground and background colors. Unfortunately, the font attribute appears to get stripped out and reverted to the system font.
As a workaround, I recommend overriding drawPlaceholderInRect: and drawing the placeholder yourself. Additionally, you should file a Radar on this and include a minimal sample project that demonstrates the bug.
I just stumbled upon this issue myself. Apparently, the placeholder will take whatever font the textfield is being assigned with. Just set the textfield's font and you are good.
For everything else, like the colour of the placeholder, I'd still go back to attributedPlaceholder
iOS8/9/Swift 2.0 - working example
func colorPlaceholderText(){
var multipleAttributes = [String : NSObject]()
multipleAttributes[NSForegroundColorAttributeName] = UIColor.appColorCYAN()
//OK - comment in if you want background color
//multipleAttributes[NSBackgroundColorAttributeName] = UIColor.yellowColor()
//OK - Adds underline
//multipleAttributes[NSUnderlineStyleAttributeName] = NSUnderlineStyle.StyleDouble.rawValue
let titleString = "Search port/country/vessel..."
let titleAttributedString = NSAttributedString(string: titleString,
attributes: multipleAttributes)
self.textFieldAddSearch.attributedPlaceholder = titleAttributedString
}

UIAppearance not taking effect on UILabels created programmatically

We have extended UILabel to be able to apply standard fonts and colors for all uses of a given label type in our apps. Eg.
#interface UILabelHeadingBold : UILabel
#end
In our AppDelegate, we apply fonts and colors like this
[[UILabelHeadingBold appearance] setTextColor:<some color>];
[[UILabelHeadingBold appearance] setFont:<some font>];
When adding a UILabel in our XIB's, we can now select the class to be of type UILabelHeadingBold, and it works as expected. The label is shown with the correct font and color, as specified in our AppDelegate.
However, if we create a label programmatically, eg.
UILabelHeadingBold *headingLabel = [[UILabelHeadingBold alloc] initWithFrame:CGRectMake(10, 10, 100, 30)];
[self.mainView addSubview:headingLabel];
the UILabel does not get the expected font/color applied. We have to manually apply these attributes.
Is there a way to make UIAppearance take effect on programatically created UI elements, or does it only work when used within XIB's?
From Apple documentation :
To support appearance customization, a class must conform to the
UIAppearanceContainer protocol and relevant accessor methods must be
marked with UI_APPEARANCE_SELECTOR.
For example in UINavigationBar.h, tintColor is marked with UI_APPEARANCE_SELECTOR
#property(nonatomic,retain) UIColor *tintColor UI_APPEARANCE_SELECTOR;
But in UILabel.h you can see that the textColor and font propertys are not marked with UI_APPEARANCE_SELECTOR but somehow it works when added in Interface Builder (following the documentation it shouldn't work at all).
Simple hack that is working for me with no issues is to create a category with a UIAppearance setter that modifies UILabel properties.
Following UIAppearance conventions I created a method:
- (void)setTextAttributes:(NSDictionary *)numberTextAttributes;
{
UIFont *font = [numberTextAttributes objectForKey:UITextAttributeFont];
if (font) {
self.font = font;
}
UIColor *textColor = [numberTextAttributes objectForKey:UITextAttributeTextColor];
if (textColor) {
self.textColor = textColor;
}
UIColor *textShadowColor = [numberTextAttributes objectForKey:UITextAttributeTextShadowColor];
if (textShadowColor) {
self.shadowColor = textShadowColor;
}
NSValue *shadowOffsetValue = [numberTextAttributes objectForKey:UITextAttributeTextShadowOffset];
if (shadowOffsetValue) {
UIOffset shadowOffset = [shadowOffsetValue UIOffsetValue];
self.shadowOffset = CGSizeMake(shadowOffset.horizontal, shadowOffset.vertical);
}
}
In UILabel category:
#interface UILabel (UISS)
- (void)setTextAttributes:(NSDictionary *)numberTextAttributes UI_APPEARANCE_SELECTOR;
#end
I'm still trying to figure out why the original setter does not work.
I was having this exact same issue, but in Swift. A custom UILabel's appearance would work if added from a storyboard, but not if added from code.
Here's a solution I found in Swift that's working for me:
class MyLabel: UILabel { }
extension UILabel {
#objc dynamic var customFont: UIFont! {
get { return self.font }
set { self.font = newValue }
}
#objc dynamic var customColor: UIColor! {
get { return self.textColor }
set { self.textColor = newValue }
}
}
Then add these lines where you configure your app appearance:
MyLabel.appearance().customFont = UIFont.systemFont(ofSize: 20)
MyLabel.appearance().customColor = UIColor.magenta
#robert.wijas solution works great !
For iOS 7 and upwards I had to update the key since the one he used are deprecated for 7+ :
- (void)setTextAttributes:(NSDictionary *)numberTextAttributes;
{
UIFont *font = [numberTextAttributes objectForKey:NSFontAttributeName];
if (font) {
self.font = font;
}
UIColor *textColor = [numberTextAttributes objectForKey:NSForegroundColorAttributeName];
if (textColor) {
self.textColor = textColor;
}
UIColor *textShadowColor = [numberTextAttributes objectForKey:NSShadowAttributeName];
if (textShadowColor) {
self.shadowColor = textShadowColor;
}
NSValue *shadowOffsetValue = [numberTextAttributes objectForKey:NSShadowAttributeName];
if (shadowOffsetValue) {
UIOffset shadowOffset = [shadowOffsetValue UIOffsetValue];
self.shadowOffset = CGSizeMake(shadowOffset.horizontal, shadowOffset.vertical);
}
}
A workaround that I've used is to manually apply the color from the appearance that is set:
let label = UILabel()
label.textColor = UILabel.appearance().textColor
This way you don't need to reference anything new, or explicitly define the color. This also works for context specific coloring:
label.textColor = UILabel.appearance(whenContainedInInstancesOf:[MyView.self]).textColor

Resources