Change attributed String dynamically - ios

I have a TableViewCell in which I have a clickable textView:
let linkTextView: UITextView = {
let v = UITextView()
v.backgroundColor = .clear
v.textAlignment = .left
v.isScrollEnabled = false
let padding = v.textContainer.lineFragmentPadding
v.textContainerInset = UIEdgeInsets(top: 0, left: -padding, bottom: 0, right: -padding)
v.tintColor = .darkCustom
v.isEditable = false
v.isSelectable = true
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let interactableText = NSMutableAttributedString(string: "Link öffnen")
In Cell I also have these two functions to style it and make it clickable:
func setupTextView(){
interactableText.addAttributes([.underlineStyle: NSUnderlineStyle.single.rawValue,
NSAttributedString.Key.font: UIFont(name: "AvenirNext-Medium", size: 15)!,
NSAttributedString.Key.underlineColor: UIColor.darkCustom],
range: NSRange(location: 0, length: interactableText.length))
interactableText.addAttribute(NSAttributedString.Key.link,
value: "https://www.google.de/?hl=de",
range: NSRange(location: 0, length: interactableText.length))
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
self.linkTappedCallback!(URL)
return false
}
This setup works. However it's not what I really want. I would like to be able to change the value of the link for each cell. I tried it like this in cellForRowAt:
print(currentWish.link)
cell.interactableText.addAttribute(NSAttributedString.Key.link,
value: currentWish.link,
range: NSRange(location: 0, length: cell.interactableText.length))
But when having it like this and dont set the link inside Cell the textView is no longer clickable. What am I missing here?

With the help of #elarcoiris I am now using this function and it works exactly the way I want it to:
extension UITextView {
func hyperLink(originalText: String, hyperLink: String, urlString: String) {
let style = NSMutableParagraphStyle()
style.alignment = .left
let attributedOriginalText = NSMutableAttributedString(string: originalText)
let linkRange = attributedOriginalText.mutableString.range(of: hyperLink)
let fullRange = NSMakeRange(0, attributedOriginalText.length)
attributedOriginalText.addAttribute(NSAttributedString.Key.link, value: urlString, range: linkRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.darkCustom, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.underlineColor, value: UIColor.darkCustom, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.font, value: UIFont(name: "AvenirNext-Medium", size: 15)!, range: fullRange)
self.linkTextAttributes = [
kCTForegroundColorAttributeName: UIColor.darkCustom,
kCTUnderlineStyleAttributeName: NSUnderlineStyle.single.rawValue,
] as [NSAttributedString.Key : Any]
self.attributedText = attributedOriginalText
}
}
Usage in cellForRowAt:
let cell = tableView.dequeueReusableCell(withIdentifier: WhishCell.reuseID, for: indexPath) as! WhishCell
cell.linkTextView.hyperLink(originalText: "Link öffnen", hyperLink: "Link öffnen", urlString: currentWish.link)

Related

Clickable Link on tableview controller

Hey guys how do I make a link clickable in a table view cell as shown in the image link above ?
The information is from a Json File.
func tableView( tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "albumID", for: indexPath)
let album :[String:Any] = albums[indexPath.row]
cell.textLabel?.text = album["collectionName"] as? String
cell.detailTextLabel?.text = album["artistName"] as? String
return cell
}
You need to have UITextView as detailTextLabel
attributedString.addAttribute(.link, value: "https://developer.apple.com", range: NSRange(location: 30, length: 50))
cell.detailTextLabel?.attributedText = attributedString
and then implement this method
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
UIApplication.shared.open(URL)
return false
}
You can use the below-mentioned statements to make a link of textView or label either inside tableView, collection view, or using them separately inside viewController.
If you are using textView then you can use this code mentioned below:-
let attributedString = NSMutableAttributedString(string: "Just click here to `register")
let url = URL(string: "https://www.apple.com")!
// Set the 'click here' substring to be the link
attributedString.setAttributes([.link: url], range: NSMakeRange(5, 10))
self.textView.attributedText = attributedString
self.textView.isUserInteractionEnabled = true
self.textView.isEditable = false
// Set how links should appear: blue and underlined
self.textView.linkTextAttributes = [
.foregroundColor: UIColor.blue,
.underlineStyle: NSUnderlineStyle.single.rawValue
]
or
if you are using labels then you can use this code mentioned below:-
var attributedString = NSMutableAttributedString(string: "String with a link", ``attributes: nil)
let linkRange = NSRange(location: 14, length: 4) // for the word "link" in the string above
let linkAttributes = [
NSAttributedString.Key.foregroundColor: UIColor(red: 0.05, green: 0.4, blue: 0.65, alpha: 1.0),
NSAttributedString.Key.underlineStyle: NSNumber(value: NSUnderlineStyle.single.rawValue)
]
attributedString.setAttributes(linkAttributes, range: linkRange)
// Assign attributedText to UILabel
label.attributedText = attributedString```

Getting the range of links in attributed string

I would like to find the range of links in attributed text, so I could apply custom underline only to the relevant words.
At the moment, the underline is under all of the text.
I want it to be only under the links.
The code is a bit complex as the requested underline is super customised.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let text = "random text <a href='http://www.google.com'>http://www.google.com </a> more random text"
let storage = NSTextStorage()
let layout = UnderlineLayout()
storage.addLayoutManager(layout)
let container = NSTextContainer()
layout.addTextContainer(container)
let textView = UITextView(frame: CGRect(x: 30, y: 380, width: 300, height: 200), textContainer: container)
textView.isUserInteractionEnabled = true
textView.isEditable = false
textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
textView.attributedText = htmlStyleAttributeText(text: text)
textView.backgroundColor = UIColor.white
textView.textColor = UIColor.black
let underLineColor: UIColor = UIColor(red: 245/255, green: 190/255, blue: 166/255, alpha: 1)
let attributes = [NSAttributedString.Key.underlineStyle.rawValue: 0x15,
NSAttributedString.Key.underlineColor: underLineColor,
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 25),
NSAttributedString.Key.baselineOffset:0] as! [NSAttributedString.Key : Any]
let rg = NSRange(location: 0, length: textView.attributedText!.string.count)
storage.addAttributes(attributes, range: rg)
view.addSubview(textView)
}
public func htmlStyleAttributeText(text: String) -> NSMutableAttributedString? {
if let htmlData = text.data(using: .utf8) {
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue]
let attributedString = try? NSMutableAttributedString(data: htmlData, options: options, documentAttributes: nil)
return attributedString
}
return nil
}
}
import UIKit
class UnderlineLayout: NSLayoutManager {
override func drawUnderline(forGlyphRange glyphRange: NSRange, underlineType underlineVal: NSUnderlineStyle, baselineOffset: CGFloat, lineFragmentRect lineRect: CGRect, lineFragmentGlyphRange lineGlyphRange: NSRange, containerOrigin: CGPoint) {
if let container = textContainer(forGlyphAt: glyphRange.location, effectiveRange: nil) {
let boundingRect = self.boundingRect(forGlyphRange: glyphRange, in: container)
let offsetRect = boundingRect.offsetBy(dx: containerOrigin.x, dy: containerOrigin.y)
let left = offsetRect.minX
let bottom = offsetRect.maxY
let width = offsetRect.width
let path = UIBezierPath()
path.lineWidth = 4
path.move(to: CGPoint(x: left, y: bottom))
path.addLine(to: CGPoint(x: left + width, y: bottom))
path.stroke()
}
}
}
With:
let attributedText = htmlStyleAttributeText(text: text)!
...
textView.attributedText = attributedText
Separate the attributes:
let underlinesAttributes: [NSAttributedString.Key: Any] = [.underlineStyle: 0x15,
.underlineColor: underLineColor]
let attributes: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 25),
.baselineOffset: 0]
Apply the "basic ones" to the whole text:
let wholeRange = NSRange(attributedText.string.startIndex..., in: attributedText.string)
storage.addAttributes(attributes, range: wholeRange)
We now enumerate looking for the links, and apply the effect for each one found:
attributedText.enumerateAttribute(.link, in: wholeRange, options: []) { (value, range, pointee) in
if value != nil {
storage.addAttributes(underlinesAttributes, range: range)
}
}

UITextContainerView hidding clickable link of UITextView

I made a clickable link in a UITextView using NSMutableAttributeString.
All it changes is that the text is highlighted
As we can see : floating over my UITextView there is a UIContainerView (I really don't know if its because of that.. I'm trying)
Here is my UIView code:
class InfoBox: UIView {
let Heading: UITextView = {
let textView = UITextView(frame: CGRect(x: 15, y: 0, width: 200, height: 35))
textView.font = UIFont.systemFont(ofSize: 20)
textView.textColor = UIColor.white
textView.isScrollEnabled = false
textView.backgroundColor = UIColor.clear
textView.isEditable = false
textView.isSelectable = true
return textView
}()
let TextContent: UITextView = {
let textView = UITextView(frame: CGRect(x: 15, y: 27, width: UIScreen.main.bounds.width, height: 30))
textView.font = UIFont.systemFont(ofSize: 17)
textView.textColor = UIColor.white
textView.isScrollEnabled = false
textView.backgroundColor = UIColor.clear
textView.isEditable = false
textView.isSelectable = true
return textView
}()}
The NSAttributedString code:
func transformText(text: String, underlined: Bool, linkURL: String) -> NSAttributedString {
let textRange = NSMakeRange(0, text.characters.count)
let attributedText = NSMutableAttributedString(string: text)
if underlined{
attributedText.addAttribute(NSUnderlineStyleAttributeName , value: NSUnderlineStyle.styleSingle.rawValue, range: textRange)
attributedText.addAttribute(NSUnderlineColorAttributeName , value: UIColor.lightGray, range: textRange)
}
attributedText.addAttribute(NSFontAttributeName , value: UIFont(name: "Helvetica-Light", size: 17)!, range: textRange)
attributedText.addAttribute(NSForegroundColorAttributeName , value: UIColor.lightGray, range: textRange)
if(linkURL != "")
{
let attrib = [NSLinkAttributeName: NSURL(string: linkURL)!]
attributedText.addAttributes(attrib, range: textRange)
}
return attributedText
}
And this is how it is called:
self.TelBox.TextContent.attributedText = transformText(text: self.TelBox.TextContent.text, underlined: true, linkURL: "https://www.google.fr")
Secondary question : is it possible to make a clickable link in a UITextView for a telephone number so that when clicked it calls that number? Did it with a UIButton .
I am not sure what is wrong with your UIContainerView, as far as i can see there's nothing wrong there.
Here's the method to make the link call a number:
func transformText(text: String, underlined: Bool, phoneNumber: String) -> NSAttributedString {
let textRange = NSMakeRange(0, text.characters.count)
let attributedText = NSMutableAttributedString(string: text)
if underlined{
attributedText.addAttribute(NSAttributedStringKey.underlineStyle , value: NSUnderlineStyle.styleSingle.rawValue, range: textRange)
attributedText.addAttribute(NSAttributedStringKey.underlineColor , value: UIColor.lightGray, range: textRange)
}
attributedText.addAttribute(NSAttributedStringKey.font , value: UIFont(name: "Helvetica-Light", size: 17)!, range: textRange)
attributedText.addAttribute(NSAttributedStringKey.foregroundColor , value: UIColor.lightGray, range: textRange)
if(phoneNumber != "")
{
let attrib = [
NSAttributedStringKey.link: URL(string: "tel://" + phoneNumber.replacingOccurrences(of: " ", with: ""))]
attributedText.addAttributes(attrib, range: textRange)
}
return attributedText
}
You can use it like this: TextContent.attributedText = transformText(text: "12345", underlined: true, phoneNumber: "12345")

Adjust font size of NSMutableAttributedString proportional to UILabel's frame height

In my project, I am using swift 3.0. Right now I am using following class (UILabel subclass) to adjust font size based on UILabel frame height. When UILabel frame change occurs, layoutSubviews recalculates proportional font size.
class Label: UILabel {
// FIXME: - properties
var fontSize: CGFloat = 0
var frameHeight: CGFloat = 0
// FIXME: - proportional font size adjustment
override func layoutSubviews() {
super.layoutSubviews()
font = font.withSize(frame.size.height * (fontSize / frameHeight))
}
}
HOW TO USE:
private let id: Label = {
let label = Label()
label.textAlignment = .left
label.numberOfLines = 1
label.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
label.textColor = UIColor(hex: 0x212121, alpha: 1)
label.fontSize = 17
label.frameHeight = 20
label.clipsToBounds = true
return label
}()
Now I want to show some part of String in UILabel as BOLD TEXT and remaining in REGULAR TEXT. So I have found some help on this thread: Making text bold using attributed string in swift
I am using "Prajeet Shrestha's" extension for NSMutableAttributedString.
// "Prajeet Shrestha's" extension
extension NSMutableAttributedString {
func bold(_ text:String) -> NSMutableAttributedString {
let attrs:[String:AnyObject] = [NSFontAttributeName : UIFont(name: "AvenirNext-Medium", size: 12)!]
let boldString = NSMutableAttributedString(string:"\(text)", attributes:attrs)
self.append(boldString)
return self
}
func normal(_ text:String)->NSMutableAttributedString {
let normal = NSAttributedString(string: text)
self.append(normal)
return self
}
}
But I am not getting how I can change font size of this NSMutableAttributedString, when UILabel frame change occurs?
Any help appeciated.
Try this
Source Looping Through NSAttributedString Attributes to Increase Font SIze
mutableStringObj?.enumerateAttribute(NSFontAttributeName, in: NSRange(location: 0, length: mutableStringObj?.length), options: [], usingBlock: {(_ value: Any, _ range: NSRange, _ stop: Bool) -> Void in
if value {
var oldFont: UIFont? = (value as? UIFont)
var newFont: UIFont? = oldFont?.withSize(CGFloat(oldFont?.pointSize * 2))
res?.removeAttribute(NSFontAttributeName, range: range)
res?.addAttribute(NSFontAttributeName, value: newFont, range: range)
}
})
Finally come up with an answer.
I created seperate custom UILabel subclass as follows:
class AttrLabel: UILabel {
// FIXME: - properties
var fontSize: CGFloat = 0
var frameHeight: CGFloat = 0
// FIXME: - proportional font size adjustment
override func layoutSubviews() {
super.layoutSubviews()
guard let oldAttrText = attributedText else {
return
}
let mutableAttributedText = NSMutableAttributedString(attributedString: oldAttrText)
mutableAttributedText.beginEditing()
mutableAttributedText.enumerateAttribute(NSFontAttributeName, in: NSRange(location: 0, length: mutableAttributedText.length), options: []) { (_ value: Any?, _ range: NSRange, _ stop: UnsafeMutablePointer<ObjCBool>) in
if let attributeFont = value as? UIFont {
let newFont = attributeFont.withSize(self.frame.size.height * (self.fontSize / self.frameHeight))
mutableAttributedText.removeAttribute(NSFontAttributeName, range: range)
mutableAttributedText.addAttribute(NSFontAttributeName, value: newFont, range: range)
}
}
mutableAttributedText.endEditing()
attributedText = mutableAttributedText
}
}
HOW TO USE:
private let id: AttrLabel = {
let label = AttrLabel()
label.textAlignment = .left
label.numberOfLines = 1
label.fontSize = 17
label.frameHeight = 20
label.clipsToBounds = true
return label
}()
SETTING ATTRIBUTED TEXT
let idStr = NSMutableAttributedString()
id.attributedText = idStr.attrStr(text: "BOLD TEXT: ", font: UIFont.systemFont(ofSize: 17, weight: .semibold), textColor: UIColor(hex: 0x212121, alpha: 1)).attrStr(text: "REGULAR WEIGHT TEXT.", font: UIFont.systemFont(ofSize: 17, weight: .regular), textColor: UIColor(hex: 0x212121, alpha: 1))
"Prajeet Shrestha's" extension for NSMutableAttributedString modified by me
extension NSMutableAttributedString {
func attrStr(text: String, font: UIFont, textColor: UIColor) -> NSMutableAttributedString {
let attributes: [String: Any] = [
NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor
]
let string = NSMutableAttributedString(string: text, attributes: attributes)
self.append(string)
return self
}
}
Try Using label property adjustsFontSizeToFitWidth AND minimumScaleFactor like this:
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.2
then you also need to increase number of lines like this any number instead of 10
label.numberOfLines = 10

Attributed text with two text alignments

Does anyone know how to achieve two different text alignments in one string?
This is what I want the textView to show:
label value
My code:
let txtView = cell.viewWithTag(77) as! UITextView
let leftStyle = NSMutableParagraphStyle()
leftStyle.alignment = NSTextAlignment.Left
let rightStyle = NSMutableParagraphStyle()
rightStyle.alignment = NSTextAlignment.Right
let attText = NSMutableAttributedString(string: "label", attributes: [NSParagraphStyleAttributeName: leftStyle])
attText.appendAttributedString(NSAttributedString(string: " "))
attText.appendAttributedString(NSAttributedString(string: "value", attributes: [NSParagraphStyleAttributeName: rightStyle]))
txtView.attributedText = attText
What I get instead:
label value
Using NSMutableParagraphStyle with NSTextTab:
let paragraph = NSMutableParagraphStyle()
paragraph.tabStops = [
NSTextTab(textAlignment: .Right, location: 100, options: [:]),
]
let str = "Label\tValue\n"
+ "foo\tbar\n"
let attributed = NSAttributedString(
string: str,
attributes: [NSParagraphStyleAttributeName: paragraph]
)
let view = UITextView(frame: CGRectMake(0, 0, 120, 120))
view.textContainer.lineFragmentPadding = 10
view.attributedText = attributed
Of course, this aligns to "tabstop", but not to the edge of UITextView. When you modify the size of the view, you have to also modify the location of NSTextTab.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
let cell = Placestableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath);
//first text
let attributestitle = [NSAttributedStringKey.font:
UIFont(name: "Helvetica-Bold", size: 15.0)!,
NSAttributedStringKey.foregroundColor: UIColor.black] as [NSAttributedStringKey: Any]
//second text
let attributedString = NSMutableAttributedString(string: "\t"+String(places[indexPath.row].distance!)+" miles", attributes: [NSAttributedStringKey.font: UIFont(name: "Helvetica-Bold", size: 8.0)!,NSAttributedStringKey.foregroundColor: UIColor.black])
let myParagraphStyle = NSMutableParagraphStyle()
myParagraphStyle.alignment = .right
myParagraphStyle.tabStops = [
NSTextTab(textAlignment: .right, location: 300, options: [:]),
]
let attributedStringtitle = NSMutableAttributedString(string: places[indexPath.row].title!, attributes: attributestitle)
//adding the right alignment to the second text alone
attributedString.addAttributes([.paragraphStyle: myParagraphStyle], range: NSRange(location: 0, length: attributedString.length))
//combining two texts with different alignments.
let combination = NSMutableAttributedString()
combination.append(attributedStringtitle)
combination.append(attributedString)
cell.textLabel?.attributedText = combination
return cell;
}

Resources