Bold part of the text in UITextView in Swift using NSMutableAttributedString - ios

I'm trying to bold part of a text in a UITextView.
Here is the code I'm using (though I simplified the text):
#IBOutlet weak var textview: UITextView!
/*...*/
var str : NSMutableAttributedString = NSMutableAttributedString(string:"this is a test", attributes: [NSFontAttributeName:textview.font!] )
str.addAttribute(NSFontAttributeName, value: UIFont(name: "GillSans-Bold", size: 16)!, range: NSMakeRange(10, 4))
textview.attributedText = str
However it is not working and I don't understand why... All the examples I've seen use this. There is no error, but nothing in the text is in bold.
Sorry if it's a duplicate, I found plenty of posts talking about putting text in bold in a uitextview, some used this technique but none had problems with it, as far as I know. Is there something I missed? Did I read something wrong?
Thanks in advance.

One reason might be that the font you're using doesn't exist or is not properly named in the code. Here's a neat trick to show the list of all available fonts:
//credits to Chris: http://codewithchris.com/common-mistakes-with-adding-custom-fonts-to-your-ios-app/#includefonts
func printFontFamilyNames(){
for family: String in UIFont.familyNames()
{
print("\(family)")
for names: String in UIFont.fontNamesForFamilyName(family)
{
print("== \(names)")
}
}
}
This may not directly answer your question but I'm sure you'll find it helpful. And also check the link on the top of this code block, from which I've extracted this function.
UPDATE
Here's a piece of code I wrote, which properly changes the font of attributed strings within a UILabel:
if let terms = self.terms {
let text = NSMutableAttributedString(string: terms.text!)
let textAsString = text.string as NSString
let range = NSMakeRange(0, textAsString.length)
textAsString.enumerateSubstringsInRange(range, options: NSStringEnumerationOptions.ByWords, usingBlock: { (substring, substringRange, enclosingRange, _) -> () in
if ["Create", "Account", "Terms", "of", "Service", "Privacy", "policy"].contains(substring!) {
if let font = UIFont(name: "OpenSans-Semibold", size: 11) {
text.addAttribute(NSFontAttributeName, value: font, range: substringRange)
} else {
Services.log("Invalid font")
}
}
//...
})
terms.attributedText = text
}
NOTE: This code contains project specific classes and variables, but I'm sure you can extract what you need.

Related

Put two words in a same line if they fit, if not put them in a new line

So I have the following question:
I have attributed string contained in a UITextView. Attributed string is contained of two parts - the normal one is ordinary text, and the second part has NSURL in it - so for better visualisation it would look like:
For more information READ HERE
The problem that I am having is that if the text fits in the one line I must keep it in one line, and if "HERE" falls in second line I must put READ also in the second line.
So, the first case - if all fits
For more information READ HERE
All other cases -
For more information
READ HERE
I tried to do it with checking if size of the screen is bigger than textfield bounds but it didn't work:
if (label.bounds.size.width < size.width) ...
I tried also other similar solutions but i think they are all addable on normal UILabels and not modified attributed texts.
If you have any idea how to deal with this I would appreciate it.
Thanks :)
There are multiple solution to resolve this.
First one is to use non-breakable space
simply add "\u{00a0}" in between click here like CLICK\u{00a0}HERE
here is the link
Second one is instead of adding space you can add "_" into it.
e.g.
"CLICK_HERE" and you can replace the color of "_" with clear.
here is the code
class ViewController: UIViewController {
#IBOutlet weak var temp : UILabel!
override func viewDidLoad() {
let myString = "For more detail information READ_HERE"
let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: myString)
attributedString.setColorForText(textForAttribute: "_", withColor: .clear)
temp.attributedText = attributedString
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
extension NSMutableAttributedString {
func setColorForText(textForAttribute: String, withColor color: UIColor) {
let range: NSRange = self.mutableString.range(of: textForAttribute, options: .caseInsensitive)
self.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: range)
}
}

How to use NSAttributedStrings in iOS with Swift?

I would like to have a label with an attributed string that changes dynamically at runtime. Something like this
Is there a better way to dynamically change the price value without hardcoding the attributed keys dictionaries and manually building the NSAttributedString?
The best way to approach Attributed Strings on iOS is by using the built-in Attributed Text editor in the interface builder and avoid uneccessary hardcoding NSAtrributedStringKeys in your source files.
You can later dynamically replace placehoderls at runtime by using this extension:
extension NSAttributedString {
func replacing(placeholder:String, with valueString:String) -> NSAttributedString {
if let range = self.string.range(of:placeholder) {
let nsRange = NSRange(range,in:valueString)
let mutableText = NSMutableAttributedString(attributedString: self)
mutableText.replaceCharacters(in: nsRange, with: valueString)
return mutableText as NSAttributedString
}
return self
}
}
Add a storyboard label with attributed text looking like this.
Then you simply update the value each time you need like this:
label.attributedText = initalAttributedString.replacing(placeholder: "<price>", with: newValue)
Make sure to save into initalAttributedString the original value.
You can better understand this approach by reading this article:
https://medium.com/mobile-appetite/text-attributes-on-ios-the-effortless-approach-ff086588173e
May not know, this is what you're looking for but will solve your problem.
You may use labels for each and update amount easily without touching attributed string.
Here is result:
Sample code, I've tried for this demo:
#IBOutlet weak var lblAmount: UILabel!
func pricelabel() -> Void {
var amount = 0
Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { (timer) in
amount += 10
self.lblAmount.text = "\(amount)"
}.fire()
}

Changing font of strings separated by spaces

I'm trying to make the words split by spaces green in a UITextField, kind of like the way it works when you compose of a new iMessage. I commented out the part of my code that's giving me a runtime error. Please let me know if you have any ideas:
func textChanged(sender : UITextField) {
var myMutableString = NSMutableAttributedString()
let arr = sender.text!.componentsSeparatedByString(" ")
var c = 0
for i in arr {
/*
myMutableString.addAttribute(NSForegroundColorAttributeName, value: UIColor.greenColor(), range: NSRange(location:c,length:i.characters.count))
sender.attributedText = myMutableString
*/
print(c,i.characters.count)
c += i.characters.count + 1
}
}
Your code has at least two parts needed to be fixed.
var myMutableString = NSMutableAttributedString()
This line creates an empty NSMutableAttributedString. Any access to the content may cause runtime error.
The other is i.characters.count. You should not use Character based locations and counts, when the APIs you want use is based on the behaviour of NSString. Use UTF-16 based count.
And one more, this is not critical, but you should use sort of meaningful names for variables.
So, all included:
func textChanged(sender: UITextField) {
let text = sender.text ?? ""
let myMutableString = NSMutableAttributedString(string: text)
let components = text.componentsSeparatedByString(" ")
var currentPosition = 0
for component in components {
myMutableString.addAttribute(NSForegroundColorAttributeName, value: UIColor.greenColor(), range: NSRange(location: currentPosition,length: component.utf16.count))
sender.attributedText = myMutableString
print(currentPosition, component.utf16.count)
currentPosition += component.utf16.count + 1
}
}
But whether this works as you expect or not depends on when this method is called.
You create an empty attributed string but never install any text into it.
The addAttribute call apples attributes to text in a string. If you try to apply attributes to a range that does not contain text, you will crash.
You need to install the content of the unattributed string into the attributed string, then apply attributes.
Note that you should probably move the line
sender.attributedText = myMutableString
Outside of your for loop. There is no good reason to install the attributed string to the text field repeatedly as you add color attributes to each word.
Note this bit from the Xcode docs on addAttribute:
Raises... an NSRangeException if any part of aRange lies beyond the
end of the receiver’s characters.
If you are getting an NSRangeException that would be a clue as to what is wrong with your current code. Pay careful attention to the error messages you get. They usually offer important clues as to what's going wrong.

Multiple lines UILabel with multiple buttons and fonts

I'm making a UITableViewCell for some activity feed objects, to give you an idea they're going to be like the Facebook posts where you have multiple links in one post.
In my case there are going to be two links to other UIViewController for each post and one plain UILabel that connects the two and explains the connection (such as "X has commented on Y's post") where you could tap both X and Y for some actions to happen.
As right now, I just made 3 separate UILabels. The problem with that is that I'm not sure how to handle names that are too long in multiple lines.
Meaning if for example instead of X, the post was "XXXXXXXXXXXXXXXXX has commented on Y's post", then "has commented on Y's post" would need to go on another line.
As right now I just link the 3 UILabels with constraints such that they're next to each other but that wouldn't work when they're too long.
If you have any idea on how could I approach this issue, it would be really appreciated if you could let me know.
Thanks in advance.
There are too many labels, I think you can use this extension:
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
}
}
Then you can do:
let labelFont = UIFont(name: "HelveticaNeue-Bold", size: 18)
let attributes :[String:AnyObject] = [NSFontAttributeName : labelFont!]
let attrString = NSAttributedString(string:"foo www.google.com", attributes: attributes)
let urlPath: String = "http://www.google.com"
let url: NSURL = NSURL(string: urlPath)!
attrString.setAsLink("www.google.com", linkURL:url)
myLabel.attributedText = attrString
UPDATE: (after your comments)
If you need to intercept urllink you can transform your label to textView (UITextView), set it the delegate and handle the shouldInteractWithURL method:
class ViewController: UIViewController, UITextViewDelegate {
... // on your code do:
myTextView.delegate = self
...
func textView(textView: UITextView!, shouldInteractWithURL URL: NSURL!, inRange characterRange: NSRange) -> Bool {
if URL.scheme == "http://www.google.com" {
//do whatever you want
launchMyMethodForThisUrl()
}
}
Set number of line of label to zero like,
label.numberOfLines = 0 // it will increase line when needed
And use attribute string to for different fonts and size or links etc.
and then set that string to label as,
label.attributedText = attributedString
Update :
If you want some action event on particular text then you should use UITextview instead of label. it will be more easier by implementing shouldInteractWithURL delegate of it.
Another good solution is use thirdparty library like TTTAttributedLabel. It is exact what you want i think. have a look once.
Update 2 :
refer this link or this link for your desired output
Hope this will help :)

Getting my font to work as a "fall-back" without calling it

I'm working with Chinese characters. Some of my characters do not exist in Unicode. I'm using a PUA .ttf to support my non-universal characters.
When I call
UIFont fontWithName:#"BabelStoneHanPUA"
everything is hunky-dory and I get my nice beautiful characters [the second one to be precise].
When I don't call anything I just get a:
�
(read: big ugly question mark).
Although UIFont fontWithName:#"BabelStoneHanPUA" doesn't seem to mess anything up, I would really just love my already included, available font to work as a "fall-back" or to work without having to call it.
Is there anyway I can get my PUA font to work without sepecifically setting it as the font?
Before using a font, call this method to add a fallback.
func addFallbackToFont(font: UIFont, fallbackFontName: String) -> UIFont {
let originalDescriptor = font.fontDescriptor
let fallbackDescriptor = originalDescriptor.addingAttributes([.name: fallbackFontName])
let repairedDescriptor = originalDescriptor.addingAttributes([.cascadeList: [fallbackDescriptor]])
return UIFont(descriptor: repairedDescriptor, size: 0.0)
}
If your characters is already exists in the original font, call this method to remove these characters after add fallback.
func removeCharacters(characterString: String, font: UIFont) -> UIFont {
let originalDescriptor = font.fontDescriptor
var characterSet = (originalDescriptor.object(forKey: .characterSet) as! NSCharacterSet).mutableCopy() as! CharacterSet
characterSet.remove(charactersIn: characterString)
let removedDescriptor = originalDescriptor.addingAttributes([.characterSet: characterSet])
return UIFont(descriptor: removedDescriptor, size: 0.0)
}

Resources