How to use NSAttributedStrings in iOS with Swift? - ios

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()
}

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 convert smileys to emoticon?

Is there a way to convert ":)" to 😊 like : detection and conversion, I have a UITextView in a chat application which should convert the smiley to the respective emoticon.
Get the offical list: emoji
Just an example: yourTextView.text = "your smiley: \u{1f642}"
If you want to convert emoticons to emoji on the fly either you need to specify it by yourself and analyze the input string or using a 3rd-party lib e.g. pods for converting and watching input string through text change events e.g.: docs
You can use the logic in this package npm, you find also a map of smile and the respective emoji:
https://www.npmjs.com/package/smile2emoji
I have created this simple class based on the npm package suggested by #emish89 https://www.npmjs.com/package/smile2emoji.
https://gist.github.com/lorenzOliveto/f20a89e9f68276cae21497a177ad8a4c
Swift 5
You should implement delegate textViewDidChange for your UITextView and find all need substrings in its text then replace them inside with textStorage property:
extension ViewController : UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
guard let text = textView.text else { return }
var index = text.startIndex
while let range = text.range(of: ":)", range: index..<text.endIndex) {
index = range.upperBound
textView.textStorage.replaceCharacters(in: NSRange(range, in: text), with: "😀")
}
}
}
It works while editing or paste text.

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

How to get the current word being typed?

I am making a custom keyboard extension for iOS 8 and am unsuccessful at trying to reflect the current word being typed on a UILabel sitting on top of the keyboard (think autocorrect). So far the code I wrote reflects the sentence before the cursor and not as it's being written, but as the cursor is moved from one position to another. What I am trying to achieve is exactly like the first autocorrect box in the native keyboard. Would anyone mind telling me what I am doing wrong?
Code:
override func textWillChange(textInput: UITextInput) {
var tokens = (self.textDocumentProxy as! UITextDocumentProxy).documentContextBeforeInput .componentsSeparatedByString(" ") as NSArray
var lastWord = tokens.lastObject as! String
println(lastWord)
bannerView?.btn1.setTitle(lastWord, forState: .Normal)
}
I've tried setting a condition whereby if beforeCursor contained either a space/period/comma to set the button title as "" but that is not efficient in the long run as I need to obtain words in order to be able to make an autocorrect feature.
Edit:
I've figured out how to get the word before the cursor (updated the code above), but not how to update the label as each letter is being added. func textWillChange(textInput: UITextInput)isn't working out. It's not me it's her.
Thanks!
You should use the textDocumentProxy property of your UIInputViewController:
let proxy = self.textDocumentProxy as! UITextDocumentProxy
To get the word being typed, I would suggest something like this:
var lastWordTyped: String? {
if let documentContext = proxy.documentContextBeforeInput as NSString? {
let length = documentContext.length
if length > 0 && NSCharacterSet.letterCharacterSet().characterIsMember(documentContext.characterAtIndex(length - 1)) {
let components = documentContext.componentsSeparatedByCharactersInSet(NSCharacterSet.alphanumericCharacterSet().invertedSet) as! [String]
return components[components.endIndex - 1]
}
}
return nil
}

Resources