I have received response from back-end in the below mentioned form, I want to display content in Punjabi, Gurmukhi Punjabi and English language simultaneously,
{
"AK_Index" = "APNE SEVAK KEE AAPE RAKE";
Ang = 747;
Author = "Guru Arjan Dev Ji";
Bani = "<null>";
"Bani_ID" = "<null>";
English = "Guru Nanak has met the Supreme Lord God; I am a sacrifice to Your Feet. ||4||1||47||";
"English_Initials" = gnmptckb;
Gurmukhi = "guru nwnku imilAw pwrbRhmu qyirAw crxw kau bilhwrw ]4]1]47]";
"Gurmukhi_Initials" = "gnmpqckb]";
ID = 32133;
Kirtan = Kirtan;
"Kirtan_ID" = 2853;
Punjabi = "gurU nwnk Awpxy prm pRBU nUM iml ipAw hY Aqy aus dy pYrW qoN GolI jWdw hY[";
Raag = "Raag Soohee";
"Teeka_Arth" = "hy pRBU! mYN qyry crnW qoN sdky jWdw hW[ ijs mnu`K nUM gurU nwnk iml ipAw, aus nUM prmwqmw iml ipAw ]4]1]47]";
"Teeka_Pad_Arth" = "kau = nUM, qoN ]4]1]47]";
}
Attached is screenshot what the result would look like. The screenshot is from android. I want exact same result in iOS.
How to achieve this in iOS.
Any help will be appreciated.
I have created an extension to change color font of some particular string.
extension NSMutableAttributedString {
func setFontColorAndSize(_ textToFind : String,withColor color: UIColor,andFont font :UIFont){
let range = self.mutableString.range(of: textToFind, options: .caseInsensitive)
if range.location != NSNotFound {
setAttributes([NSAttributedStringKey.font : font,NSAttributedStringKey.foregroundColor :color], range: range)
}
}
// This is how you change
let resultString = NSMutableAttributedString(string: "hello") // convert any string to mutable attributed
resultString.setFontColorAndSize("hel", withColor: UIColor.grey, andFont: UIFont.boldSystemFont(ofSize: 17)) // pass the string you want to set particular color and font
UILabel.attributedText = resultString // than update the label
There is nothing much you can do on the mobile end. You can ask server to send UTF-8 encoded data instead of plain English string.
English = UTF-8 encoded English string data//data for hello
Gurmukhi = UTF-8 encoded Punjabi string data //data for ਪਜੌਬਹਗਕਤਚਜ
Now you can create the string to display in that language as:-
let englishString = String(data: English, encoding: .utf8)
let punjabiSring = String(data: Gurmukhi, encoding: .utf8)
Initializer is
init?(data: Data, encoding: String.Encoding)
Finally got it working using "Gurblipi.ttf" font which internally changes the English text to Punjabi language.
let attributedString = NSMutableAttributedString()
for shabadDetail in shabadDetailList {
attributedString.append(NSMutableAttributedString(string: shabadDetail.gurmukhi, attributes: [NSAttributedStringKey.font : FontHelper.guruLippi(fontsize: textview.font!.pointSize), NSAttributedStringKey.foregroundColor: TextFormatting.gurbaniColor]))
attributedString.append(NSAttributedString(string: TextFormatting.doubleLineBreak))
}
textview.attributedText = attributedString
textview.textAlignment = .center
No encoding is required from native or back-end.
Related
I have a UILabel with text "hello world, hello". There are 2 hello words.
And I want to replace the only 'bold hello' to 'thanks' without bold.
I use this code:
uiLabel1.text = "hello world, hello"
let target = "hello"
let replace = "thanks"
uiLabel1.text.replacingOccurrences(of: target, with: replace, options:
NSString.CompareOptions.literal, range: nil)
And the result is: "thanks world, thanks"
The result I want: "hello world, thanks"
Okay, so there's probably an easier way to do this...
So, I went through the API (like super quick) and looked for something like lastIndexOf, which lead me on a little trail to String#range(of:options), which allows you to search backwards, hmmm, interesting.
This returns a Range<String.Index> ... okay, so how can I use that?! Hmm, maybe String#replacingOccurrences(of:with:options:range:) 🤔
So, crack open a play ground and...
var str = "hello world, hello"
let lastIndexOf = str.range(of: "hello", options: .backwards)
str = str.replacingOccurrences(of: "hello", with: "thanks", options: .caseInsensitive, range: lastIndexOf)
str now equals "hello world, thanks"
Hi #MadProgrammer, your code is to replace the last hello word to thanks, right? But my question is to replace hello with the bold attribute, it may in the first, middle or at the end of a string.
Okay, so clearly we're missing some context...
Assuming, now, you're using a NSAttributedString, it becomes slightly more complicated
Building the string itself is not hard, figuring out how to find string components by attribute, a little more difficult.
Lucky for us, we have the Internet. So, the following is based on ideas I got from:
NSAttributedString by example
Detect whether a font is bold/italic on iOS?
One of the important things to remember when trying to solve an issue, you'll be lucky to find a single answer which does it all, instead, you need to break your issue down and focus on solving individual elements, and be prepared to go back to the start 😉
So, once again, unto the play ground...
import UIKit
var str = "hello world, "
//let lastIndexOf = str.range(of: "hello", options: .backwards)
//str = str.replacingOccurrences(of: "hello", with: "thanks", options: .caseInsensitive, range: lastIndexOf)
extension UIFont {
var isBold: Bool {
return fontDescriptor.symbolicTraits.contains(.traitBold)
}
var isItalic: Bool {
return fontDescriptor.symbolicTraits.contains(.traitItalic)
}
}
// Just so I can see that the style ;)
let fontSize = CGFloat(24.0)
let boldAttrs = [
NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: fontSize),
NSAttributedString.Key.foregroundColor: UIColor.white // Playground
]
// Playground only
let plainAttrs = [
NSAttributedString.Key.foregroundColor: UIColor.white // Playground
]
let boldText = NSMutableAttributedString(string: "hello", attributes: boldAttrs)
let styledText = NSMutableAttributedString(string: str, attributes: plainAttrs)
let someMoreBoldText = NSMutableAttributedString(string: "not to be replaced", attributes: boldAttrs)
// Attributes can be combined with their appear together ;)
styledText.append(boldText)
styledText.append(NSMutableAttributedString(string: " ", attributes: plainAttrs))
styledText.append(someMoreBoldText)
styledText.append(NSMutableAttributedString(string: " ", attributes: plainAttrs))
styledText.append(boldText)
styledText.enumerateAttribute(NSAttributedString.Key.font, in: NSRange(0..<styledText.length)) { (value, range, stop) in
guard let font = value as? UIFont, font.isBold else {
return;
}
let subText = styledText.attributedSubstring(from: range)
guard subText.string == "hello" else {
return
}
styledText.replaceCharacters(in: range, with: "thanks")
}
styledText
Which outputs...
The important things for me are:
The style has not be changed
Only the individual "hello" values, which are bolded, have been changed
Here is the code. But actually this is hardcoded. If the target enclosed in between <b></b>, it will work.
var text = "hello world, <b>hello</b>"
let target = "hello"
let replace = "thanks"
text = text.replacingOccurrences(of: "<b>\(target)</b>", with: replace, options: .literal, range: nil) //hello world, thanks
I have JSON file with text and label. In JSON file I want to add this characters <b> or <i> to some words. For example:
"this is my text <b>bold<b> or <i>italic<i>"
And before when I show text in label I want to analyse my text and if some words have this characters <b> or <i> I want to make this word bold or italic in the text of the label. How to do it? Or there are another way to make bold text from JSON?
UPDATE:
Yours code:
/* Set Tagged Text & Fonts */
let taggedTextString = myText.text
let tagFont = UIFont.boldSystemFont(ofSize: 17.0)
let normalFont = UIFont.systemFont(ofSize: 17.0)
/* You can simply assign the AttributedText to the modified String, which may return nil. */
textLabel.attributedText = taggedTextString.modifyFontWithTags(openingTag: "<tag>", closingTag: "</tag>", taggedFont: tagFont, unTaggedFont: normalFont)
My code:
var attributedString = NSMutableAttributedString(string: myText.text)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 5
attributedString.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
textLabel.attributedText = attributedString;
Is this code the best way to union your and my code?
for i in stride(from: 0, to: allTagsSplitArray.count, by: 1) {
finalAttrStr.append(NSMutableAttributedString(string: allTagsSplitArray[i], attributes: [kCTFontAttributeName as NSAttributedStringKey : i % 2 == 0 ? untaggedTextFont : taggedTextFont]))
// my code
finalAttrStr.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, finalAttrStr.length))
}
The simplest way I seem to have accomplished this is by running through the string and scanning for your key tags - <b>, <i>, etc.
To make things easier (and more reusable), we should make an extension of our String or NSAttributedString classes. For this example, I've extended the String class, and and I return a NSAttributedString:
func modifyFontWithTags(openingTag: String, closingTag: String, taggedFont: UIFont?, unTaggedFont: UIFont?) -> NSMutableAttributedString? {
/* Make sure we have everything we need. */
guard let taggedTextFont = taggedFont,
let untaggedTextFont = unTaggedFont,
!self.isEmpty,
self.contains(openingTag),
self.contains(closingTag) else { return nil }
/* Split the string up using our closing tag. */
let closingTagSplitArray = self.components(separatedBy: closingTag)
/* Make a placeholder array. */
var allTagsSplitArray = [String]()
/* Iterate through our split array. */
for item in closingTagSplitArray {
if item.contains(openingTag) {
/* Strip the opening tag & append. */
allTagsSplitArray.append(contentsOf: item.components(separatedBy: openingTag))
} else {
/* Just append. */
allTagsSplitArray.append(item)
}
}
/* Instantiate our attributed string. */
let finalAttrStr = NSMutableAttributedString()
for i in stride(from: 0, to: allTagsSplitArray.count, by: 1) {
/* Add our font to every-other item in the array (the tagged portions). */
finalAttrStr.append(NSMutableAttributedString(string: allTagsSplitArray[i], attributes: [NSFontAttributeName : i % 2 == 0 ? untaggedTextFont : taggedTextFont]))
}
return finalAttrStr
}
Note: In this example, I take in an UIFont parameter, but you can modify this function to take in whatever attribute types you want.
I'm having some search option in my app, which will highlight given word in UISearchBar. Given word may occurs multiple time in label I need t highlight all those words. How it is possible, I have tried with some set of codes but It will highlight only one occurrence of that word, Here is my sample code:
var SearchAttributeText = "The"
let range = (TextValue as NSString).range(of: SearchAttributeText)
let attribute = NSMutableAttributedString.init(string: TextValue)
attribute.addAttribute(NSForegroundColorAttributeName, value: UIColor.red , range: range)
self.label.attributedText = attribute
Need to support with both Upper and lower cases. Word The may occurs multiple time need to highlight all.
You can use following code to search in string
//Text need to be searched
let SearchAttributeText = "the"
//Store label text in variable as NSString
let contentString = lblContent.text! as NSString
//Create range of label text
var rangeString = NSMakeRange(0, contentString.length)
//Convert label text into attributed string
let attribute = NSMutableAttributedString.init(string: contentString as String)
while (rangeString.length != NSNotFound && rangeString.location != NSNotFound) {
//Get the range of search text
let colorRange = (lblContent.text?.lowercased() as! NSString).range(of: SearchAttributeText, options: NSString.CompareOptions(rawValue: 0), range: rangeString)
if (colorRange.location == NSNotFound) {
//If location is not present in the string the loop will break
break
} else {
//This line of code colour the searched text
attribute.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.red , range: colorRange)
lblContent.attributedText = attribute
//This line of code increment the rangeString variable
rangeString = NSMakeRange(colorRange.location + colorRange.length, contentString.length - (colorRange.location + colorRange.length))
}
}
The below line of code update the range by incrementing the location and length parameter of NSRange
rangeString = NSMakeRange(colorRange.location + colorRange.length, contentString.length - (colorRange.location + colorRange.length))
I'm working on a macOS application. I need to syntax-highlight text that is placed over TextView (NSTextView) with a list of selected words. For simplicity, I'm actually testing the same feature on the iPhone Simulator. Anyway, a list of words to highlight comes as a form of an array. The following is what I have.
func HighlightText {
let tagArray = ["let","var","case"]
let style = NSParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
style.alignment = NSTextAlignment.Left
let words = textView.string!.componentsSeparatedByString(" ") // textView.text (UITextView) or textView.string (NSTextView)
let attStr = NSMutableAttributedString()
for i in 0..<words.count {
let word = words[i]
if HasElements.containsElements(tagArray,text: word,ignore: true) {
let attr = [
NSForegroundColorAttributeName: syntaxcolor,
NSParagraphStyleAttributeName: style,
]
let str = (i != words.count-1) ? NSAttributedString(string: word.stringByAppendingString(" "), attributes: attr) : NSAttributedString(string: word, attributes: attr)
attStr.appendAttributedString(str)
} else {
let attr = [
NSForegroundColorAttributeName: NSColor.blackColor(),
NSParagraphStyleAttributeName: style,
]
let str = (i != words.count-1) ? NSAttributedString(string: word.stringByAppendingString(" "), attributes: attr) : NSAttributedString(string: word, attributes: attr)
attStr.appendAttributedString(str)
}
}
textView.textStorage?.setAttributedString(attStr)
}
class HasElements {
static func containsElements(array:Array<String>,text:String,ignore:Bool) -> Bool {
var has = false
for str in array {
if str == text {
has = true
}
}
return has
}
}
The simple methodology here is to separate the entire string of text into words with a white space (" ") and puts each word in an array (words). The containsElements function simply tells whether or not the selected word contains one of the keywords in the array (tagArray). If it returns true, the word is put in an NSMutableAttributedString with a highlight color. Otherwise, it's put in the same attributed string with a plain color.
The problem with this simple methodology is that a separated word puts the last word and /n and the next word together. For example, if I have a string like
let base = 3
let power = 10
var answer = 1
, only the first 'let' will be highlighted as the code puts 3 and the next let together like '3\nlet.' If I separate any word containing \n with a fast enumeration, the code won't detect each new paragraph well. I appreciate any advice to make it better. Just FYI, I'm going to leave this topic open to both macOS and iOS.
Muchos thankos
Couple different options. String has a function called componentsSeparatedByCharactersInSet that allows you to separate by a character set you define. Unfortunately this won't work since you want to separate by \n which is more than one character.
You could split the words twice.
let firstSplit = textView.text!.componentsSeparatedByString(" ")
var words = [String]()
for word in firstSplit {
let secondSplit = word.componentsSeparatedByString("\n")
words.appendContentsOf(secondSplit)
}
But then you wouldn't have any sense of the line breaks.. You'd need to re add them back in.
Finally, the easiest hack is simply:
let newString = textView.text!.stringByReplacingOccurrencesOfString("\n", withString: "\n ")
let words = newString.componentsSeparatedByString(" ")
So basically you add your own spaces.
Our app Api returns a field with custom format for user mentions just like:
"this is a text with mention for #(steve|user_id)".
So before display it on UITextView, need to process the text, find the pattern and replace with something more user friendly.
Final result would be "this is a text with mention for #steve" where #steve should have a link attribute with user_id. Basically the same functionality as Facebook.
First I've created an UITextView extension, with a match function for the regex pattern.
extension UITextView {
func processText(pattern: String) {
let inString = self.text
let regex = try? NSRegularExpression(pattern: pattern, options: [])
let range = NSMakeRange(0, inString.characters.count)
let matches = (regex?.matchesInString(inString, options: [], range: range))! as [NSTextCheckingResult]
let attrString = NSMutableAttributedString(string: inString, attributes:attrs)
//Iterate over regex matches
for match in matches {
//Properly print match range
print(match.range)
//A basic idea to add a link attribute on regex match range
attrString.addAttribute(NSLinkAttributeName, value: "\(schemeMap["#"]):\(must_be_user_id)", range: match.range)
//Still text it's in format #(steve|user_id) how could replace it by #steve keeping the link attribute ?
}
}
}
//To use it
let regex = ""\\#\\(([\\w\\s?]*)\\|([a-zA-Z0-9]{24})\\)""
myTextView.processText(regex)
This is what I have right now, but I'm stucked trying to get final result
Thanks a lot !
I changed your regex a bit, but got a pretty good result. Modified the code a little as well, so you can test it directly in Playgrounds.
func processText() -> NSAttributedString {
let pattern = "(#\\(([^|]*)([^#]*)\\))"
let inString = "this is a text with mention for #(steve|user_id1) and #(alan|user_id2)."
let regex = try? NSRegularExpression(pattern: pattern, options: [])
let range = NSMakeRange(0, inString.characters.count)
let matches = (regex?.matchesInString(inString, options: [], range: range))!
let attrString = NSMutableAttributedString(string: inString, attributes:nil)
print(matches.count)
//Iterate over regex matches
for match in matches.reverse() {
//Properly print match range
print(match.range)
//Get username and userid
let userName = attrString.attributedSubstringFromRange(match.rangeAtIndex(2)).string
let userId = attrString.attributedSubstringFromRange(match.rangeAtIndex(3)).string
//A basic idea to add a link attribute on regex match range
attrString.addAttribute(NSLinkAttributeName, value: "\(userId)", range: match.rangeAtIndex(1))
//Still text it's in format #(steve|user_id) how could replace it by #steve keeping the link attribute ?
attrString.replaceCharactersInRange(match.rangeAtIndex(1), withString: "#\(userName)")
}
return attrString
}