UITextContainerView hidding clickable link of UITextView - ios

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")

Related

Change attributed String dynamically

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)

Wrong height for UILabel when using custom lineSpacing and kern

I am getting wrong height for an UILabel if I use NSAttributedString that has custom kern and lineSpacing.
Here is how I set the custom kern and line spacing:
override func viewDidLoad() {
super.viewDidLoad()
let shortText = "Single line"
self.label.attributedText = self.getAttributedText(text: shortText, kern: 0.2, lineSpacing: 8)
self.label2.attributedText = self.getAttributedText(text: shortText, kern: 0, lineSpacing: 8)
}
private func getAttributedText(text: String, kern: CGFloat, lineSpacing: CGFloat) -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: text)
let style = NSMutableParagraphStyle()
style.lineSpacing = lineSpacing
let attributes: [NSAttributedStringKey : Any] =
[.paragraphStyle : style,
.kern: kern]
attributedString.addAttributes(attributes,
range: NSMakeRange(0, attributedString.length))
return attributedString
}
And here is what I get:
The first label (the one that has custom kern), has its height wrong. It's exactly 8 points taller than it should be - that's the custom line height that I am using.
This only happens for single line labels. If I use text that is on a couple of lines, it works as expected.
This is a bug with NSAttributedStringKey.kern. As a workaround, you can calculate the number of lines of your UILabel with the suggestions in this answer. If it has one line only, set lineSpacing to 0.
private func getAttributedText(text: String, kern: CGFloat, lineSpacing: CGFloat) -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: text)
let font = UIFont.systemFont(ofSize: 16)
let attributes: [NSAttributedStringKey : Any] = [.kern: kern,
.font: font]
attributedString.addAttributes(attributes, range: NSMakeRange(0, attributedString.length))
let maxSize = CGSize(width: [custom width], height: CGFloat.greatestFiniteMagnitude)
let sizeOfLabel = attributedString.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, context: nil)
if sizeOfLabel.height > font.lineHeight {
let style = NSMutableParagraphStyle()
style.lineSpacing = lineSpacing
attributedString.addAttribute(.paragraphStyle, value: style, range: NSMakeRange(0, attributedString.length))
}
return attributedString
}

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;
}

How to concatenate two UITextView

I'm trying to concatenate two UItextView and it work.
They have different properties (for example different UIFont) but in the final UITextView they have the same properties. How to fix this?
textViewFirst!.text = "\n Example"
textViewFirst!.font = UIFont(name: "Helvetica Neue", size: 10);
textViewSecond.text = textViewSecond.text + textViewFirst.text
for example : this makes your text bold from the 4th char to 7th
let myFullString:String = textViewSecond.text + textViewFirst.text as String
var attributedText: NSMutableAttributedString = NSMutableAttributedString(string: myFullString)
attributedText.addAttributes([NSFontAttributeName: UIFont.boldSystemFontOfSize(14)], range: NSRange(location: 3, length: 3))
textViewSecond.attributedText = attributedText
Try this:
var attributedString = NSMutableAttributedString(string: textView2.text, attributes: [NSFontAttributeName : textView2.font])
attributedString.appendAttributedString(NSAttributedString(string: textView1.text, attributes: [NSFontAttributeName : textView1.font]))
textView2.attributedText = attributedString
In order to preserve both fonts and maybe other attributes (like text color) you must make use of NSAttributedString
let font = UIFont(name: "Helvetica Neue", size: 10.0) ?? UIFont.systemFontOfSize(18.0)
let textFont = [NSFontAttributeName:font]
// Create a string that will be our paragraph
let para1 = NSMutableAttributedString()
let para2 = NSMutableAttributedString()
// Create locally formatted strings
let attrString1 = NSAttributedString(string: "Hello ", attributes:textFont)
let attrString2 = NSAttributedString(string: "World ", attributes:textFont)
// Add locally formatted strings to paragraph
para1.appendAttributedString(attrString1)
para2.appendAttributedString(attrString2)
// Define paragraph styling
let paraStyle = NSMutableParagraphStyle()
paraStyle.firstLineHeadIndent = 15.0
paraStyle.paragraphSpacingBefore = 10.0
// Apply paragraph styles to paragraph
para1.addAttribute(NSParagraphStyleAttributeName, value: paraStyle, range: NSRange(location: 0,length: para1.length))
para2.addAttribute(NSParagraphStyleAttributeName, value: paraStyle, range: NSRange(location: 0,length: para1.length))
// Create UITextView
let view1 = UITextView(frame: CGRect(x: 0, y: 20, width: CGRectGetWidth(self.view.frame), height: 100))
let view2 = UITextView(frame: CGRect(x: 0, y: 100, width: CGRectGetWidth(self.view.frame), height: 100))
let view3 = UITextView(frame: CGRect(x: 0, y: 200, width: CGRectGetWidth(self.view.frame), height: 100))
// Add string to UITextView
view1.attributedText = para1
view2.attributedText = para2
var attributedString = NSMutableAttributedString(string: view1.text, attributes: [NSFontAttributeName : view1.font])
attributedString.appendAttributedString(NSAttributedString(string: view2.text, attributes: [NSFontAttributeName : view2.font]))
view3.attributedText = attributedString
// Add UITextView to main view
self.view.addSubview(view1)
self.view.addSubview(view2)
self.view.addSubview(view3)

Resources